Repository: maharmstone/btrfs Branch: master Commit: b415418815f6 Files: 117 Total size: 4.0 MB Directory structure: gitextract_g2azv67f/ ├── .github/ │ └── workflows/ │ └── build.yml ├── .gitmodules ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENCE ├── README.md ├── btrfs-dump.pl ├── mingw-amd64.cmake ├── mingw-x86.cmake ├── msvc-aarch64.cmake ├── msvc-amd64.cmake ├── msvc-armv7.cmake ├── msvc-x86.cmake ├── send-dump.pl └── src/ ├── balance.c ├── blake2-impl.h ├── blake2b-ref.c ├── boot.c ├── btrfs-vol.inf ├── btrfs.c ├── btrfs.cdf ├── btrfs.h ├── btrfs.inf ├── btrfs.rc.in ├── btrfs_drv.h ├── btrfsioctl.h ├── cache.c ├── calcthread.c ├── compress.c ├── crc32c-aarch64.asm ├── crc32c-gas.S ├── crc32c-masm.asm ├── crc32c.c ├── crc32c.h ├── create.c ├── devctrl.c ├── dirctrl.c ├── extent-tree.c ├── fastio.c ├── fileinfo.c ├── flushthread.c ├── free-space.c ├── fsctl.c ├── fsrtl.c ├── galois.c ├── mkbtrfs/ │ ├── mkbtrfs.c │ ├── mkbtrfs.rc.in │ └── resource.h ├── pnp.c ├── read.c ├── registry.c ├── reparse.c ├── resource.h ├── scrub.c ├── search.c ├── security.c ├── send.c ├── sha256.c ├── shellext/ │ ├── balance.cpp │ ├── balance.h │ ├── contextmenu.cpp │ ├── contextmenu.h │ ├── devices.cpp │ ├── devices.h │ ├── factory.cpp │ ├── factory.h │ ├── iconoverlay.cpp │ ├── iconoverlay.h │ ├── main.cpp │ ├── mappings.cpp │ ├── mountmgr.cpp │ ├── mountmgr.h │ ├── propsheet.cpp │ ├── propsheet.h │ ├── recv.cpp │ ├── recv.h │ ├── resource.h │ ├── scrub.cpp │ ├── scrub.h │ ├── send.cpp │ ├── send.h │ ├── shellbtrfs.def │ ├── shellbtrfs.manifest │ ├── shellbtrfs.rc.in │ ├── shellext.h │ ├── volpropsheet.cpp │ └── volpropsheet.h ├── tests/ │ ├── create.cpp │ ├── cs.cpp │ ├── delete.cpp │ ├── ea.cpp │ ├── fileinfo.cpp │ ├── io.cpp │ ├── links.cpp │ ├── manifest.xml │ ├── mmap.cpp │ ├── oplock.cpp │ ├── overwrite.cpp │ ├── rename.cpp │ ├── reparse.cpp │ ├── security.cpp │ ├── streams.cpp │ ├── supersede.cpp │ ├── test.cpp │ ├── test.h │ └── test.rc.in ├── treefuncs.c ├── ubtrfs/ │ ├── resource.h │ ├── ubtrfs.c │ ├── ubtrfs.def │ └── ubtrfs.rc.in ├── volume.c ├── worker-thread.c ├── write.c ├── xor-gas.S ├── xor-masm.asm └── zstd-shim.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yml ================================================ name: build on: [push] env: PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/etc/eselect/wine/bin jobs: cmake: runs-on: msvc-wine container: volumes: - /var/run/pcscd/pcscd.comm:/var/run/pcscd/pcscd.comm steps: - run: echo "SHORT_SHA=`echo ${{ github.sha }} | cut -c1-8`" >> $GITHUB_ENV - run: git clone --recurse-submodules -j`nproc` ${{ github.server_url }}/${{ github.repository }} ${SHORT_SHA} - run: cd ${SHORT_SHA} && git checkout ${{ github.sha }} - run: mkdir -p build/debug/{amd64,x86,aarch64,arm} - run: mkdir -p build/release/{amd64,x86,aarch64,arm} - run: mkdir -p build/pdb/{debug,release}/{amd64,x86,aarch64,arm} - 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` - 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` - 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` - 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` - 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` - 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` - 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` - 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` - run: mv build/debug/amd64/*.pdb build/pdb/debug/amd64/ - run: mv build/debug/x86/*.pdb build/pdb/debug/x86/ - run: mv build/debug/aarch64/*.pdb build/pdb/debug/aarch64/ - run: mv build/debug/arm/*.pdb build/pdb/debug/arm/ - run: mv build/release/amd64/*.pdb build/pdb/release/amd64/ - run: mv build/release/x86/*.pdb build/pdb/release/x86/ - run: mv build/release/aarch64/*.pdb build/pdb/release/aarch64/ - run: mv build/release/arm/*.pdb build/pdb/release/arm/ - run: cp ${SHORT_SHA}/src/{btrfs,btrfs-vol}.inf build/debug/ - run: cp ${SHORT_SHA}/src/{btrfs,btrfs-vol}.inf build/release/ - run: stampinf -f build/debug/btrfs.inf -d \* -v \* - run: stampinf -f build/debug/btrfs-vol.inf -d \* -v \* - run: stampinf -f build/release/btrfs.inf -d \* -v \* - run: stampinf -f build/release/btrfs-vol.inf -d \* -v \* - run: cd build/debug && makecat ../../${SHORT_SHA}/src/btrfs.cdf - run: cd build/release && makecat ../../${SHORT_SHA}/src/btrfs.cdf - env: CERTIFICATE: ${{ secrets.CERTIFICATE }} run: echo "${CERTIFICATE}" > codesigning.crt - env: PKCS11CERT: ${{ secrets.PKCS11CERT }} PKCS11KEY: ${{ secrets.PKCS11KEY }} 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 - env: PKCS11CERT: ${{ secrets.PKCS11CERT }} PKCS11KEY: ${{ secrets.PKCS11KEY }} 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 - uses: actions/upload-artifact@v3 with: name: ${{ github.sha }} overwrite: true path: | build/**/btrfs.sys build/**/mkbtrfs.exe build/**/shellbtrfs.dll build/**/ubtrfs.dll build/**/*.inf build/**/*.cat build/pdb ================================================ FILE: .gitmodules ================================================ [submodule "src/zstd"] path = src/zstd url = https://github.com/facebook/zstd [submodule "src/zlib"] path = src/zlib url = https://github.com/madler/zlib ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(btrfs VERSION 1.9.0) option(WITH_TEST "Compile test program" ON) if(MSVC) # cmake bug 15170 if(MSVC_C_ARCHITECTURE_ID STREQUAL "X86") set(CMAKE_SYSTEM_PROCESSOR "x86") elseif(MSVC_C_ARCHITECTURE_ID STREQUAL "x64") set(CMAKE_SYSTEM_PROCESSOR "x86_64") elseif(MSVC_C_ARCHITECTURE_ID STREQUAL "ARMV7") set(CMAKE_SYSTEM_PROCESSOR "arm") elseif(MSVC_C_ARCHITECTURE_ID STREQUAL "ARM64") set(CMAKE_SYSTEM_PROCESSOR "aarch64") endif() endif() # zstd set(ZSTD_SRC_FILES src/zstd/lib/common/entropy_common.c src/zstd/lib/common/error_private.c src/zstd/lib/compress/fse_compress.c src/zstd/lib/common/fse_decompress.c src/zstd/lib/compress/hist.c src/zstd/lib/compress/huf_compress.c src/zstd/lib/decompress/huf_decompress.c src/zstd/lib/common/zstd_common.c src/zstd/lib/compress/zstd_compress.c src/zstd/lib/compress/zstd_compress_literals.c src/zstd/lib/compress/zstd_compress_sequences.c src/zstd/lib/compress/zstd_compress_superblock.c src/zstd/lib/decompress/zstd_ddict.c src/zstd/lib/decompress/zstd_decompress.c src/zstd/lib/decompress/zstd_decompress_block.c src/zstd/lib/compress/zstd_double_fast.c src/zstd/lib/compress/zstd_fast.c src/zstd/lib/compress/zstd_lazy.c src/zstd/lib/compress/zstd_ldm.c src/zstd/lib/compress/zstd_opt.c src/zstd/lib/common/xxhash.c) add_library(zstd STATIC ${ZSTD_SRC_FILES}) target_compile_definitions(zstd PRIVATE -DZSTD_DEPS_MALLOC -DXXH_NO_STDLIB) if(NOT MSVC) target_compile_options(zstd PRIVATE -ffunction-sections -include ${CMAKE_SOURCE_DIR}/src/zstd-shim.h) else() target_compile_options(zstd PRIVATE /Gy /FI ${CMAKE_SOURCE_DIR}/src/zstd-shim.h) set_property(TARGET zstd PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() if(MSVC AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86") target_compile_options(zstd PUBLIC /Gz) # stdcall endif() # zlib set(ZLIB_SRC_FILES src/zlib/adler32.c src/zlib/deflate.c src/zlib/inffast.c src/zlib/inflate.c src/zlib/inftrees.c src/zlib/trees.c src/zlib/zutil.c) add_library(zlib STATIC ${ZLIB_SRC_FILES}) target_compile_definitions(zlib PRIVATE -DNO_GZIP -DZ_SOLO) if(NOT MSVC) target_compile_options(zlib PRIVATE -ffunction-sections) else() target_compile_options(zlib PRIVATE /Gy) endif() if(MSVC AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86") target_compile_options(zlib PUBLIC /Gz) # stdcall endif() # btrfs.sys set(SRC_FILES src/balance.c src/blake2b-ref.c src/boot.c src/btrfs.c src/cache.c src/calcthread.c src/compress.c src/crc32c.c src/create.c src/devctrl.c src/dirctrl.c src/extent-tree.c src/fastio.c src/fileinfo.c src/flushthread.c src/free-space.c src/fsctl.c src/fsrtl.c src/galois.c src/pnp.c src/read.c src/registry.c src/reparse.c src/scrub.c src/search.c src/security.c src/send.c src/sha256.c src/treefuncs.c src/volume.c src/worker-thread.c src/write.c ${CMAKE_CURRENT_BINARY_DIR}/btrfs.rc) # Work around bug in MSVC version of cmake - see https://gitlab.kitware.com/cmake/cmake/-/merge_requests/4257 set(CMAKE_ASM_MASM_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreaded "") set(CMAKE_ASM_MASM_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDLL "") set(CMAKE_ASM_MASM_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDebug "") set(CMAKE_ASM_MASM_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDebugDLL "") set(CMAKE_ASM_MASM_FLAGS "/Zd") if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86") if(MSVC) enable_language(ASM_MASM) set(SRC_FILES ${SRC_FILES} src/crc32c-masm.asm src/xor-masm.asm) else() enable_language(ASM) set(SRC_FILES ${SRC_FILES} src/crc32c-gas.S src/xor-gas.S) endif() endif() if(MSVC AND (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")) # see cmake bug 24317 if armasm64 fails (should be fixed in CMake 3.29) enable_language(ASM_MARMASM) set(SRC_FILES ${SRC_FILES} src/crc32c-aarch64.asm) endif() configure_file(src/btrfs.rc.in btrfs.rc) if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") add_definitions(-D_AMD64_) set(MS_ARCH "x64") elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86") add_definitions(-D_X86_) set(MS_ARCH "x86") elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm") add_definitions(-D_ARM_) set(MS_ARCH "arm") elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") add_definitions(-D_ARM64_) set(MS_ARCH "arm64") endif() if(MSVC) include_directories("$ENV{WindowsSdkDir}Include\\$ENV{WindowsSDKLibVersion}km") link_directories("$ENV{WindowsSdkDir}Lib\\$ENV{WindowsSDKLibVersion}km\\${MS_ARCH}") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND WIN32) include_directories("${CMAKE_FIND_ROOT_PATH}/usr/include/ddk") endif() add_library(btrfs SHARED ${SRC_FILES}) target_link_libraries(btrfs zstd zlib) if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_definitions(-D_DEBUG) endif() if(NOT MSVC) target_compile_options(btrfs PUBLIC -U__NO_INLINE__) add_definitions(-D__USE_MINGW_ANSI_STDIO=0) endif() target_compile_definitions(btrfs PUBLIC _KERNEL_MODE WIN9X_COMPAT_SPINLOCK) if(MSVC) if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86") target_compile_options(btrfs PUBLIC /Gz) # stdcall endif() target_link_libraries(btrfs ntoskrnl hal) if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") target_link_libraries(btrfs bufferoverflowfastfailk) else() target_link_libraries(btrfs BufferOverflowK) endif() if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm") target_link_libraries(btrfs armrt) elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") target_link_libraries(btrfs arm64rt) endif() target_link_libraries(btrfs rtlver) target_link_options(btrfs PUBLIC /SUBSYSTEM:NATIVE /NODEFAULTLIB /MANIFEST:NO /Driver /ENTRY:DriverEntry) # strip out flags for MSVC's runtime checks string(REGEX REPLACE "/RTC(su|[1su])" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") string(REGEX REPLACE "/RTC(su|[1su])" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") else() target_compile_options(btrfs PUBLIC -Wall -Werror-implicit-function-declaration -Werror=incompatible-pointer-types -Wno-expansion-to-defined -Wunused-parameter -Wtype-limits -Wextra) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_compile_options(btrfs PUBLIC -Werror=cast-function-type -Wold-style-declaration) elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") target_compile_options(btrfs PUBLIC -Wno-pragma-pack) # ignore warning in mingw headers endif() target_link_libraries(btrfs ntoskrnl hal gcc) target_link_options(btrfs PUBLIC -nostdlib -Wl,--subsystem,native -Wl,--file-alignment,0x1000 -Wl,--section-alignment,0x1000 -Wl,--exclude-all-symbols) if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86") target_link_options(btrfs PUBLIC -Wl,--entry,_DriverEntry@8) else() target_link_options(btrfs PUBLIC -Wl,--entry,DriverEntry) endif() endif() set_target_properties(btrfs PROPERTIES PREFIX "") set_target_properties(btrfs PROPERTIES SUFFIX ".sys") # -------------------------------------- # shellbtrfs.dll set(SHELLEXT_SRC_FILES src/shellext/balance.cpp src/shellext/contextmenu.cpp src/shellext/devices.cpp src/shellext/factory.cpp src/shellext/iconoverlay.cpp src/shellext/main.cpp src/shellext/mappings.cpp src/shellext/mountmgr.cpp src/shellext/propsheet.cpp src/shellext/recv.cpp src/shellext/scrub.cpp src/shellext/send.cpp src/shellext/volpropsheet.cpp src/crc32c.c src/shellext/shellbtrfs.def ${CMAKE_CURRENT_BINARY_DIR}/shellbtrfs.rc) if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86") if(MSVC) enable_language(ASM_MASM) set(SHELLEXT_SRC_FILES ${SHELLEXT_SRC_FILES} src/crc32c-masm.asm) else() enable_language(ASM) set(SHELLEXT_SRC_FILES ${SHELLEXT_SRC_FILES} src/crc32c-gas.S) endif() endif() set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_VISIBILITY_PRESET hidden) configure_file(src/shellext/shellbtrfs.rc.in shellbtrfs.rc) add_library(shellbtrfs SHARED ${SHELLEXT_SRC_FILES}) if(NOT MSVC) target_link_options(shellbtrfs PUBLIC -static -static-libgcc) target_link_libraries(shellbtrfs pthread) target_compile_options(shellbtrfs PUBLIC -Wall -Wno-expansion-to-defined -Wunused-parameter -Wtype-limits -Wextra) else() target_compile_options(shellbtrfs PUBLIC /EHsc) target_link_options(shellbtrfs PUBLIC /MANIFEST:NO) endif() target_link_libraries(shellbtrfs comctl32 ntdll setupapi uxtheme shlwapi windowscodecs gdi32 advapi32 shell32 ole32) if(MSVC AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86") target_compile_options(shellbtrfs PUBLIC /Gz) # stdcall endif() set_target_properties(shellbtrfs PROPERTIES PREFIX "") if(MSVC) set_property(TARGET shellbtrfs PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() # -------------------------------------- # ubtrfs.dll set(UBTRFS_SRC_FILES src/ubtrfs/ubtrfs.c src/crc32c.c src/sha256.c src/blake2b-ref.c src/ubtrfs/ubtrfs.def ${CMAKE_CURRENT_BINARY_DIR}/ubtrfs.rc) if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86") if(MSVC) enable_language(ASM_MASM) set(UBTRFS_SRC_FILES ${UBTRFS_SRC_FILES} src/crc32c-masm.asm) else() enable_language(ASM) set(UBTRFS_SRC_FILES ${UBTRFS_SRC_FILES} src/crc32c-gas.S) endif() endif() configure_file(src/ubtrfs/ubtrfs.rc.in ubtrfs.rc) add_library(ubtrfs SHARED ${UBTRFS_SRC_FILES}) if(MSVC) set_property(TARGET ubtrfs PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() target_compile_definitions(ubtrfs PUBLIC _USRDLL) target_link_libraries(ubtrfs ntdll advapi32) target_link_libraries(ubtrfs zstd) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") target_compile_options(ubtrfs PUBLIC -Wno-pragma-pack) # ignore warning in mingw headers endif() if(MSVC AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86") target_compile_options(ubtrfs PUBLIC /Gz) # stdcall endif() if(NOT MSVC) target_compile_options(ubtrfs PUBLIC -Werror-implicit-function-declaration) target_link_options(ubtrfs PUBLIC -static -static-libgcc -static-libstdc++) endif() set_target_properties(ubtrfs PROPERTIES PREFIX "") # -------------------------------------- # mkbtrfs.exe set(MKBTRFS_SRC_FILES src/mkbtrfs/mkbtrfs.c ${CMAKE_CURRENT_BINARY_DIR}/mkbtrfs.rc) configure_file(src/mkbtrfs/mkbtrfs.rc.in mkbtrfs.rc) add_executable(mkbtrfs ${MKBTRFS_SRC_FILES}) if(MSVC) set_property(TARGET mkbtrfs PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") else() target_link_options(mkbtrfs PUBLIC -static -static-libgcc) endif() # -------------------------------------- # test.exe if(WITH_TEST) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) configure_file(src/tests/test.rc.in test.rc) set(TEST_SRC_FILES src/tests/test.cpp ${CMAKE_CURRENT_BINARY_DIR}/test.rc src/tests/create.cpp src/tests/supersede.cpp src/tests/overwrite.cpp src/tests/io.cpp src/tests/mmap.cpp src/tests/rename.cpp src/tests/delete.cpp src/tests/links.cpp src/tests/oplock.cpp src/tests/cs.cpp src/tests/reparse.cpp src/tests/streams.cpp src/tests/ea.cpp src/tests/fileinfo.cpp src/tests/security.cpp) add_executable(test ${TEST_SRC_FILES}) if(MSVC) set_property(TARGET test PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") else() target_link_options(test PUBLIC -static -static-libgcc -municode) target_compile_options(test PUBLIC -Wall -Wno-expansion-to-defined -Wunused-parameter -Wtype-limits -Wextra) endif() target_link_libraries(test ntdll version advapi32) endif() # -------------------------------------- # install install(TARGETS btrfs DESTINATION bin) install(TARGETS shellbtrfs DESTINATION bin) install(TARGETS ubtrfs DESTINATION bin) install(TARGETS mkbtrfs DESTINATION bin) ================================================ FILE: CMakeSettings.json ================================================ { "configurations": [ { "name": "x64-Debug", "generator": "Ninja", "configurationType": "Debug", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-v", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x64_x64" ], "variables": [] }, { "name": "x86-Debug", "generator": "Ninja", "configurationType": "Debug", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-v", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x86_x64" ], "variables": [] }, { "name": "arm-Debug", "generator": "Ninja", "configurationType": "Debug", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-v", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_arm_x64" ], "variables": [] }, { "name": "arm64-Debug", "generator": "Ninja", "configurationType": "Debug", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-v", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_arm64_x64" ], "variables": [] }, { "name": "x64-Release", "generator": "Ninja", "configurationType": "RelWithDebInfo", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-v", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x64_x64" ], "variables": [] }, { "name": "x86-Release", "generator": "Ninja", "configurationType": "RelWithDebInfo", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-v", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x86_x64" ], "variables": [] }, { "name": "arm-Release", "generator": "Ninja", "configurationType": "RelWithDebInfo", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-v", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_arm_x64" ], "variables": [] }, { "name": "arm64-Release", "generator": "Ninja", "configurationType": "RelWithDebInfo", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-v", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_arm64_x64" ], "variables": [] } ] } ================================================ FILE: LICENCE ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: README.md ================================================ WinBtrfs v1.9 ------------- WinBtrfs is a Windows driver for the next-generation Linux filesystem Btrfs. A reimplementation from scratch, it contains no code from the Linux kernel, and should work on any version from Windows XP onwards. It is also included as part of the free operating system [ReactOS](https://www.reactos.org/). If your Btrfs filesystem is on a MD software RAID device created by Linux, you will also need [WinMD](https://github.com/maharmstone/winmd) to get this to appear under Windows. See also [Quibble](https://github.com/maharmstone/quibble), an experimental bootloader allowing Windows to boot from Btrfs, and [Ntfs2btrfs](https://github.com/maharmstone/ntfs2btrfs), a tool which allows in-place conversion of NTFS filesystems. First, a disclaimer: You use this software at your own risk. I take no responsibility for any damage it may do to your filesystem. It ought to be suitable for day-to-day use, but make sure you take backups anyway. Everything here is released under the GNU Lesser General Public Licence (LGPL); see the file LICENCE for more info. You are encouraged to play about with the source code as you will, and I'd appreciate a note (mark@harmstone.com) if you come up with anything nifty. See at the end of this document for copyright details of third-party code that's included here. Features -------- * Reading and writing of Btrfs filesystems * Basic RAID: RAID0, RAID1, and RAID10 * Advanced RAID: RAID5 and RAID6 * Caching * Discovery of Btrfs partitions, even if Windows would normally ignore them * Getting and setting of Access Control Lists (ACLs), using the xattr security.NTACL * Alternate Data Streams (e.g. :Zone.Identifier is stored as the xattr user.Zone.Identifier) * Mappings from Linux users to Windows ones (see below) * Symlinks and other reparse points * Shell extension to identify and create subvolumes, including snapshots * Hard links * Sparse files * Free-space cache * Preallocation * Asynchronous reading and writing * Partition-less Btrfs volumes * Per-volume registry mount options (see below) * zlib compression * LZO compression * LXSS ("Ubuntu on Windows") support * Balancing (including resuming balances started on Linux) * Device addition and removal * Creation of new filesystems with `mkbtrfs.exe` and `ubtrfs.dll` * Scrubbing * TRIM/DISCARD * Reflink copy * Subvol send and receive * Degraded mounts * Free space tree (compat_ro flag `free_space_cache`) * Shrinking and expanding * Passthrough of permissions etc. for LXSS * Zstd compression * Windows 10 case-sensitive directory flag * Oplocks * Metadata UUID incompat flag (Linux 5.0) * Three- and four-disk RAID1 (Linux 5.5) * New checksum types (xxhash, sha256, blake2) (Linux 5.5) * Block group tree (Linux 6.1) Todo ---- * Full fs-verity support (Linux 5.15) * Zoned support (Linux 5.11) (HM-SMR not supported on Windows?) * Defragmentation * Support for Btrfs quotas * Full transaction log support * Support for Windows transactions (TxF) Installation ------------ To install the driver, [download and extract the latest release](https://github.com/maharmstone/btrfs/releases), right-click btrfs.inf, and choose Install. The driver is signed, so should work out of the box on modern versions of Windows. If you using Windows 10 or 11 and have Secure Boot enabled, you may have to make a Registry change in order for the driver to be loaded - see [below](#secureboot). It's easier though just to turn off Secure Boot in your BIOS, unless you have a particular need for it. Bear in mind that Windows 11 soft-requires Secure Boot to be installed, but will work fine afterwords with it turned off. WinBtrfs is also available on the following package managers: * [Chocolatey](https://chocolatey.org/packages/winbtrfs) ``` choco install winbtrfs ``` * [Scoop](https://scoop.sh/#/apps?q=%22winbtrfs-np%22&s=0&d=1&o=true) ``` scoop bucket add nonportable scoop install winbtrfs-np -g ``` Uninstalling ------------ If you want to uninstall, from a command prompt run: ``` RUNDLL32.EXE SETUPAPI.DLL,InstallHinfSection DefaultUninstall 132 btrfs.inf ``` You may need to give the full path to btrfs.inf. You can also go to Device Manager, find "Btrfs controller" under "Storage volumes", right click and choose "Uninstall". Tick the checkbox to uninstall the driver as well, and let Windows reboot itself. If you need to uninstall via the registry, open regedit and set the value of HKLM\SYSTEM\CurrentControlSet\services\btrfs\Start to 4, to disable the service. After you reboot, you can then delete the btrfs key and remove C:\Windows\System32\drivers\btrfs.sys. Compilation ----------- To compile with Visual C++ 2019, open the directory and let CMake do its thing. If you have the Windows DDK installed correctly, it should just work. To compile with GCC on Linux, you will need a cross-compiler set up, for either `i686-w64-mingw32` or `x86_64-w64-mingw32`. Create a build directory, then use either `mingw-x86.cmake` or `mingw-amd64.cmake` as CMake toolchain files to generate your Makefile. Mappings -------- The user mappings are stored in the registry key HKLM\SYSTEM\CurrentControlSet\services\btrfs\Mappings. Create a DWORD with the name of your Windows SID (e.g. S-1-5-21-1379886684-2432464051-424789967-1001), and the value of your Linux uid (e.g. 1000). It will take effect next time the driver is loaded. You can find your current SID by running `wmic useraccount get name,sid`. Similarly, the group mappings are stored in under GroupMappings. The default entry maps Windows' Users group to gid 100, which is usually "users" on Linux. You can also specify user SIDs here to force files created by a user to belong to a certain group. The setgid flag also works as on Linux. Note that processes running under User Access Control tokens create files as the BUILTIN\Administrators SID (S-1-5-32-544), rather as a user account. LXSS ("Ubuntu on Windows" / "Windows Subsystem for Linux") ---------------------------------------------------------- The driver will passthrough Linux metadata to recent versions of LXSS, but you will have to let Windows know that you wish to do this. From a Bash prompt on Windows, edit `/etc/wsl.conf` to look like the following: ``` [automount] enabled = true options = "metadata" mountFsTab = false ``` It will then take effect next time you reboot. Yes, you should be able to chroot into an actual Linux installation, if you wish. Commands -------- The DLL file shellbtrfs.dll provides the GUI interface, but it can also be used with rundll32.exe to carry out some tasks from the command line, which may be useful if you wish to schedule something to run periodically. Bear in mind that rundll32 provides no mechanism to return any error codes, so any of these commands may fail silently. * `rundll32.exe shellbtrfs.dll,CreateSubvol ` * `rundll32.exe shellbtrfs.dll,CreateSnapshot ` * `rundll32.exe shellbtrfs.dll,ReflinkCopy ` This also accepts wildcards, and any number of source files. The following commands need various privileges, and so must be run as Administrator to work: * `rundll32.exe shellbtrfs.dll,SendSubvol [-p ] [-c ] ` The -p and -c flags are as `btrfs send` on Linux. You can specify any number of clone subvolumes. * `rundll32.exe shellbtrfs.dll,RecvSubvol ` * `rundll32.exe shellbtrfs.dll,StartScrub ` * `rundll32.exe shellbtrfs.dll,StopScrub ` Troubleshooting --------------- * How do I debug this? On the releases page, there's zip files to download containing the PDBs. Or you can try the symbols server http://symbols.burntcomma.com/ - in windbg, set your symbol path to something like this: ```symsrv*symsrv.dll*C:\symbols*http://msdl.microsoft.com/download/symbols;symsrv*symsrv.dll*C:\symbols*http://symbols.burntcomma.com``` * The filenames are weird! or * I get strange errors on certain files or directories! The driver assumes that all filenames are encoded in UTF-8. This should be the default on most setups nowadays - if you're not using UTF-8, it's probably worth looking into converting your files. * How do I get this working with Secure Boot turned on? For the later versions of Windows 10, Microsoft introduced more onerous requirements for signing, which seemingly aren't available for open-source drivers. To work around this, go to `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\CI\Policy` in Regedit, create a new DWORD value called `UpgradedSystem` and set to 1, and reboot. Or you could always just turn off Secure Boot in your BIOS settings. * The root of the drive isn't case-sensitive in LXSS This is something Microsoft hardcoded into LXSS, presumably to stop people hosing their systems by running `mkdir /mnt/c/WiNdOwS`. * How do I change the drive letter? With the shell extension installed, right-click the drive in Explorer, click Properties, and go to the Btrfs tab. There should be a button which allows you to change the drive letter. * I'm still having problems with drive letters In Regedit, try deleting the relevant entries in `HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices`, then rebooting. * How do I format a partition as Btrfs? Use the included command line program mkbtrfs.exe. We can't add Btrfs to Windows' own dialog box, unfortunately, as its list of filesystems has been hardcoded. You can also run `format /fs:btrfs`, if you don't need to set any Btrfs-specific options. * I can't reformat a mounted Btrfs filesystem If Windows' Format dialog box refuses to appear, try running format.com with the /fs flag, e.g. `format /fs:ntfs D:`. * I can't mount a Synology NAS Synology seems to use LVM for its block devices. Until somebody writes an LVM driver for Windows, you're out of luck. * I can't mount a Thecus NAS Thecus uses Linux's MD raid for its block devices. You will need to install [WinMD](https://github.com/maharmstone/winmd) as well. * 64-bit Windows 7 won't load the driver Make sure that you have [KB3033929](https://www.microsoft.com/en-gb/download/details.aspx?id=46148) installed. Or consider installing from an "escrow" ISO which includes all updates. * The drive doesn't show up and Paragon software has been installed Paragon's filesystem-reading software is known to disable automount. Disable or uninstall Paragon, then re-enable automount by running `diskpart` and typing `automount enable`. * The drive doesn't show up on very old versions of Windows On very old versions of Windows (XP, Server 2003?), Windows ignores Linux partitions entirely. If this is the case for you, try running `fdisk` on Linux and changing your partition type from 83 to 7. * I can edit files on Windows that I shouldn't be able to There's no mapping between Windows and POSIX permission models, they're too different for this to be practical. If this bothers you, you can create a Windows ACL on files that you don't want to be able to edit. Changelog --------- v1.9 (2024-03-15): * Added support for block group tree (Linux 6.1) * Fixed hang when system under heavy load * Added /blockgrouptree and /freespacetree options to mkbtrfs * Follow Linux in defaulting /noholes to on in mkbtrfs * Added support for CRC32C instructions on aarch64 v1.8.2 (2023-01-10): * Fixed UAC not working * Fixed Smartlocker crash on Windows 11 22H2 * Rejigged INF file to work better on Windows 11 * Files now signed with SHA256 hash rather than SHA1 v1.8.1 (2022-08-23): * Fixed use-after-free when flushing * Fixed crash when opening volume when AppLocker installed * Compression now disabled for no-COW files, as on Linux * Flushing now scales better on very fast drives * Fixed small files getting padded to 4,096 bytes by lazy writer * Added NoDataCOW registry option v1.8 (2022-03-12): * Added minimal support for fs-verity * Added test suite * Fixed incorrect disk usage statistics * Fixed potential crashes when renaming stream to file or file to stream * Fixed potential crashes when querying hard links on file * Fixed potential hang when opening oplocked file * Fixed minor issues also uncovered by test suite v1.7.9 (2021-10-02): * Fixed deadlock when mounting on Windows 11 * Added support for BitLocker-encrypted volumes * Improved filename checks when renaming or creating hard links * Miscellaneous bug fixes v1.7.8.1 (2021-06-13): * Fixed bug preventing new directories from appearing in listings * Fixed Release version of driver still not working on XP v1.7.8 (2021-06-09): * Upgraded zstd to version 1.5.0 * Fixed regression stopping driver from working under XP * Fixed compilation on clang * Fixed corruption issue when Linux mount option `inode_cache` had been used * Fixed recursion issue involving virtual directory \\$Root v1.7.7 (2021-04-12): * Fixed deadlock on high load * Fixed free space issue when installing Genshin Impact * Fixed issue when copying files with wildcards in command prompt * Increased speed of directory lookups v1.7.6 (2021-01-14): * Fixed race condition when booting with Quibble * No longer need to restart Windows after initial installation * Forced maximum file name to 255 UTF-8 characters, to match Linux driver * Fixed issue where directories could be created with trailing backslash * Fixed potential deadlock when Windows calls NtCreateSection during flush * Miscellaneous bug fixes v1.7.5 (2020-10-31): * Fixed text display issue in shell extension * Added support for mingw 8 * Fixed LXSS permissions not working in new versions of Windows * Fixed issue where truncating an inline file wouldn't change its size * Fixed crash with Quibble where driver would try to use AVX2 before Windows had enabled it v1.7.4 (2020-08-23): * Fixed issue when running compressed EXEs * Changed build system to cmake * Upgraded zstd to version 1.4.5 * Added support for FSCTL_GET_RETRIEVAL_POINTERS * Miscellaneous bug fixes v1.7.3 (2020-05-24): * Fixed crash when sending file change notifications * Improved symlink handling with LXSS * Added support for undocumented flag SL_IGNORE_READONLY_ATTRIBUTE * Fixed corruption caused by edge case, where address allocated and freed in same flush * Improved handling of free space tree * Improved handling of very full volumes * Fixed spurious warnings raised by GCC 10 static analyser * Replaced multiplications and divisions with bit shift operations where appropriate * Fixed combobox stylings in shell extension v1.7.2 (2020-04-10): * Added more fixes for booting from Btrfs on Windows 10 * Fixed occasional deadlock when deleting or closing files on Windows 10 1909 * Fixed crash when reading large ADSes * Fixed occasional crash when writing files on RAID5/6 * Miscellaneous bug fixes v1.7.1 (2020-03-02): * Fixed crash when reading beyond end of file * Fixed spurious checksum errors when doing unaligned read v1.7 (2020-02-26): * Added support for metadata_uuid incompat flag (Linux 5.0) * Added support for three- and four-disk RAID1 (Linux 5.5) * Added support for new checksum types: xxhash, sha256, blake2 (Linux 5.5) * Greatly increased checksumming speed * Greatly increased compression and decompression speed * Fixed bug causing incorrect free-space reporting when data is DUP * Fixed issue creating directories on LXSS when `case=dir` option set v1.6 (2020-02-04): * Added experimental (i.e. untested) ARM support (thanks to [DjArt](https://github.com/DjArt) for this) * Added fixes for booting from Btrfs on Windows 10 * Volumes will now get remounted if changed while Windows is asleep or hibernating * Fixed corruption when mounting volume that hasn't been unmounted cleanly by Linux * Fixed crash when deleting subvolume v1.5 (2019-11-10): * More fixes for booting from Btrfs * Added virtual $Root directory (see "NoRootDir" below) * Added support for Windows XP * Added support for renaming alternative data streams * Added oplock support * Fixed potential deadlock on boot * Fixed possible crash on shutdown * Fixed a bunch of memory leaks * Many other miscellaneous bug fixes v1.4 (2019-08-31): * Added fragmentation percentage to property sheet * Added support for Windows Server 2003 and Windows Vista * Added pagefile support * Improved support for file locking * Added support for booting from Btrfs on Windows Server 2003 (see https://www.youtube.com/watch?v=-5E2CHmHEUs) * Fixed issue where driver could open same inode twice * Other miscellaneous bug fixes v1.3 (2019-06-10): * Added support for new rename and delete functions introduced to Windows 10 * Added support for Windows 10's flag for case-sensitive directories * Changed free-space calculation method to be more like that of the Linux driver * Added more support for 128-bit file IDs * Fixed bug causing outdated root items * Fixed bug preventing writing to VHDs v1.2.1 (2019-05-06): * Reverted commit affecting the creation of streams v1.2 (2019-05-05): * Dramatic speed increase when opening many small files, such as with a Git repository * Fixed crash on surprise removals of removable devices * Added ability to change drive letters easily * No longer creates free-space cache for very small chunks, so as not to confuse the Linux driver * Fixed corruption when very large file created and then immediately deleted * Minor bug fixes v1.1 (2018-12-15): * Support for Zstd compression * Passthrough of Linux metadata to LXSS * Refactored shell extension * Fixed memory leaks * Many other bug fixes v1.0.2 (2018-05-19): * Minor bug fixes v1.0.1 (2017-10-15): * Fixed deadlock * Binaries now signed * Minor bug fixes v1.0 (2017-09-04): * First non-beta release! * Degraded mounts * New free space cache (compat_ro flag `free_space_cache`) * Shrinking and expanding of volumes * Registry options now re-read when changed, rather than just on startup * Improved balancing on very full filesystems * Fixed problem preventing user profile directory being stored on btrfs on Windows 8 and above * Better Plug and Play support * Miscellaneous bug fixes v0.10 (2017-05-02): * Reflink copy * Sending and receiving subvolumes * Group mappings (see Mappings section above) * Added commands for scripting etc. (see Commands section above) * Fixed an issue preventing mounting on non-PNP devices, such as VeraCrypt * Fixed an issue preventing new versions of LXSS from working * Fixed problem with the ordering of extent refs, which caused problems on Linux but wasn't picked up by `btrfs check` * Added support for reading compressed inline extents * Many miscellaneous bug fixes v0.9 (2017-03-05): * Scrubbing * TRIM/DISCARD * Better handling of multi-device volumes * Performance increases when reading from RAID filesystems * No longer lies about being NTFS, except when it has to * Volumes will now go readonly if there is an unrecoverable error, rather than blue-screening * Filesystems can now be created with Windows' inbuilt format.com * Zlib upgraded to version 1.2.11 * Miscellaneous performance increases * Miscellaneous bug fixes v0.8 (2016-12-30): * Volume property sheet, for: * Balances * Adding and removing devices * Showing disk usage, i.e. the equivalent to `btrfs fi usage` * Checksums now calculated in parallel where appropriate * Creation of new filesystems, with mkbtrfs.exe * Plug and play support for RAID devices * Disk usage now correctly allocated to processes in taskmgr * Performance increases * Miscellaneous bug fixes v0.7 (2016-10-24): * Support for RAID5/6 (incompat flag `raid56`) * Seeding support * LXSS ("Ubuntu on Windows") support * Support for Windows Extended Attributes * Improved removable device support * Better snapshot support * Recovery from RAID checksum errors * Fixed issue where creating a lot of new files was taking a long time * Miscellaneous speed increases and bug fixes v0.6 (2016-08-21): * Compression support (both zlib and lzo) * Mixed groups support * No-holes support * Added inode property sheet to shell extension * Many more mount options (see below) * Better support for removable devices * Page file support * Many miscellaneous bug fixes v0.5 (2016-07-24): * Massive speed increases (from "sluggish" to "blistering") * Massive stability improvements * RAID support: RAID0, RAID1, and RAID10 * Asynchronous reading and writing * Partition-less Btrfs volumes * Windows sparse file support * Object ID support * Beginnings of per-volume mount options * Security improvements * Notification improvements * Miscellaneous bug fixes v0.4 (2016-05-02): * Subvolume creation and deletion * Snapshots * Preallocation * Reparse points * Hard links * Plug and play * Free-space cache * Fix problems preventing volume from being shared over the network * Miscellaneous bug fixes v0.3 (2016-03-25): * Bug fixes: * Fixed crashes when metadata blocks were SINGLE, such as on SSDs * Fixed crash when splitting an internal tree * Fixed tree traversal failing when first item in tree had been deleted * Fixed emptying out of whole tree (probably only relevant to checksum tree) * Fixed "incorrect local backref count" message appearing in `btrfs check` * Miscellaneous other fixes * Added beginnings of shell extension, which currently only changes the icon of subvolumes v0.2 (2016-03-13): * Bug fix release: * Check memory allocations succeed * Check tree items are the size we're expecting * Added rollbacks, so failed operations are completely undone * Fixed driver claiming all unrecognized partitions (thanks Pierre Schweitzer) * Fixed deadlock within `CcCopyRead` * Fixed changing properties of a JPEG within Explorer * Lie about FS type, so UAC works * Many, many miscellaneous bug fixes * Rudimentary security support * Debug log support (see below) v0.1 (2016-02-21): * Initial alpha release. Debug log --------- WinBtrfs has three levels of debug messages: errors and FIXMEs, warnings, and traces. The release version of the driver only displays the errors and FIXMEs, which it logs via `DbgPrint`. You can view these messages via the Microsoft program DebugView, available at https://technet.microsoft.com/en-gb/sysinternals/debugview. If you want to report a problem, it'd be of great help if you could also attach a full debug log. To do this, you will need to use the debug versions of the drivers; copy the files in Debug\x64 or Debug\x86 into x64 or x86. You will also need to set the registry entries in HKLM\SYSTEM\CurrentControlSet\Services\btrfs: * `DebugLogLevel` (DWORD): 0 for no messages, 1 for errors and FIXMEs, 2 for warnings also, and 3 for absolutely everything, including traces. * `LogDevice` (string, optional): the serial device you want to output to, such as `\Device\Serial0`. This is probably only useful on virtual machines. * `LogFile` (string, optional): the file you wish to output to, if `LogDevice` isn't set. Bear in mind this is a kernel filename, so you'll have to prefix it with "\\??\\" (e.g., "\\??\\C:\\btrfs.log"). It probably goes without saying, but don't store this on a volume the driver itself is using, or you'll cause an infinite loop. Mount options ------------- The driver will create subkeys in the registry under HKLM\SYSTEM\CurrentControlSet\Services\btrfs for each mounted filesystem, named after its UUID. If you're unsure which UUID refers to which volume, you can check using `btrfs fi show` on Linux. You can add per-volume mount options to this subkey, which will take effect on reboot. If a value is set in the key above this, it will use this by default. * `Ignore` (DWORD): set this to 1 to tell the driver not to attempt loading this filesystem. With the `Readonly` flag, this is probably redundant. * `Readonly` (DWORD): set this to 1 to tell the driver not to allow writing to this volume. This is the equivalent of the `ro` flag on Linux. * `Compress` (DWORD): set this to 1 to tell the driver to write files as compressed by default. This is the equivalent of the `compress` flag on Linux. * `CompressForce` (DWORD): set this to 1 to force compression, i.e. to ignore the `nocompress` inode flag and even attempt compression of incompressible files. This isn't a good idea, but is the equivalent of the `compress-force` flag on Linux. * `CompressType` (DWORD): set this to 1 to prefer zlib compression, 2 to prefer lzo compression, or 3 to prefer zstd compression. The default is 0, which uses zstd or lzo compression if the incompat flags are set, and zlib otherwise. * `FlushInterval` (DWORD): the interval in seconds between metadata flushes. The default is 30, as on Linux - the parameter is called `commit` there. * `ZlibLevel` (DWORD): a number between -1 and 9, which determines how much CPU time is spent trying to compress files. You might want to fiddle with this if you have a fast CPU but a slow disk, or vice versa. The default is 3, which is the hard-coded value on Linux. * `MaxInline` (DWORD): the maximum size that will be allowed for "inline" files, i.e. those stored in the metadata. The default is 2048, which is also the default on modern versions of Linux - the parameter is called `max_inline` there. It will be clipped to the maximum value, which unless you've changed your node size will be a shade under 16 KB. * `SubvolId` (QWORD): the ID of the subvolume that we will attempt to mount as the root. If it doesn't exist, this parameter will be silently ignored. The subvolume ID can be found on the inode property sheet; it's in hex there, as opposed to decimal on the Linux tools. The default is whatever has been set via `btrfs subvolume set-default`; or, failing that, subvolume 5. The equivalent parameter on Linux is called `subvolid`. * `SkipBalance` (DWORD): set to 1 to tell the driver not to attempt resuming a balance which was running when the system last powered down. The default is 0. The equivalent parameter on Linux is `skip_balance`. * `NoPNP` (DWORD): useful for debugging only, this forces any volumes to appear rather than exposing them via the usual Plug and Play method. * `ZstdLevel` (DWORD): Zstd compression level, default 3. * `NoTrim` (DWORD): set this to 1 to disable TRIM support. * `AllowDegraded` (DWORD): set this to 1 to allow mounting a degraded volume, i.e. one with a device missing. You are strongly advised not to enable this unless you need to. * `NoRootDir` (DWORD): if you have changed your default subvolume, either natively or by a registry option, there will be a hidden directory called $Root which points to where the root would normally be. Set this value to 1 to prevent this appearing. * `NoDataCOW` (DWORD): set this to 1 to disable copy-on-write for new files. This is the equivalent of the `nodatacow` flag on Linux. Contact ------- I'd appreciate any feedback you might have, positive or negative: mark@harmstone.com. Copyright --------- This code contains portions of the following software: ### Zlib Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ### LZO WinBtrfs contains portions of an early version of lzo, which is copyright 1996 Markus Oberhumer. Modern versions are licensed under the GPL, but this was licensed under the LGPL, so I believe it is okay to use. ### Zstd Copyright (c) 2016-present, Facebook, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Facebook nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### BLAKE2 [https://github.com/BLAKE2/BLAKE2](https://github.com/BLAKE2/BLAKE2) (public domain) ### SHA256 [https://github.com/amosnier/sha-2](https://github.com/amosnier/sha-2) (public domain) ================================================ FILE: btrfs-dump.pl ================================================ #!/usr/bin/perl # Quick and dirty btrfs tree dumper. Great for diff'ing, which btrfs-debug-tree isn't... # Do something like: # # qemu-nbd -r -n -c /dev/nbd0 ~/vms/win7/win7-32.img ; sleep 1 ; chmod 666 /dev/nbd0p3 # ./btrfs-dump.pl /dev/nbd0p3 > dump2.txt # diff -u dump1.txt dump2.txt > diff2.txt # Like btrfs.h, I'm disclaiming any copyright on this file, but I'd appreciate # hearing about what you do with it: mark@harmstone.com. use Data::Dumper; use strict; use integer; if (scalar(@ARGV) < 1) { my @dp = split(/\//, $0); print "Usage: " . $dp[$#dp] . " [BLOCKDEVICE]\n"; exit; } my %devs = (); for (my $i = 1; $i <= $#ARGV; $i++) { my ($file, $sb); open($file, $ARGV[$i]) || die "Error opening " . $ARGV[$i] . ": $!"; binmode($file); seek($file, 0x10000, 0); read($file, $sb, 0x1000); my @b = unpack("Vx28a16QQA8QQQQQQQQQVVVVVQQQQvCCCa98a256QQx240a2048a672", $sb); if ($b[4] ne "_BHRfS_M") { die $ARGV[$i] . ": not Btrfs"; } my @di = unpack("QQQVVVQQQVCCa16a16", $b[27]); $devs{$di[0]} = $file; } my ($f, $chunktree, $roottree, $logtree, $nodesize, $blocksize); open($f, $ARGV[0]) || die "Error opening " . $ARGV[0] . ": $!"; binmode($f); my %roots = (); my %logroots = (); my @l2p = (); my @l2p_bs = (); my $csum_type; read_superblock($f); print "CHUNK:\n"; dump_tree($chunktree, "", 1); print "\n"; print "ROOT:\n"; dump_tree($roottree, "", 0); print "\n"; if ($logtree != 0) { print "LOG:\n"; dump_tree($logtree, "", 0); print "\n"; } my @rs = sort { $a <=> $b } (keys(%roots)); foreach my $r (@rs) { printf("Tree %x:\n", $r); dump_tree($roots{$r}, ""); print "\n"; } my @lrs = sort { $a <=> $b } (keys(%logroots)); foreach my $lr (@lrs) { printf("Tree %x (log):\n", $lr); dump_tree($logroots{$lr}, ""); print "\n"; } close($f); sub incompat_flags { my ($f) = @_; my @l; if ($f & 0x1) { push @l, "mixed_backref"; $f &= ~0x1; } if ($f & 0x2) { push @l, "default_subvol"; $f &= ~0x2; } if ($f & 0x4) { push @l, "mixed_groups"; $f &= ~0x4; } if ($f & 0x8) { push @l, "compress_lzo"; $f &= ~0x8; } if ($f & 0x10) { push @l, "compress_zstd"; $f &= ~0x10; } if ($f & 0x20) { push @l, "big_metadata"; $f &= ~0x20; } if ($f & 0x40) { push @l, "extended_iref"; $f &= ~0x40; } if ($f & 0x80) { push @l, "raid56"; $f &= ~0x80; } if ($f & 0x100) { push @l, "skinny_metadata"; $f &= ~0x100; } if ($f & 0x200) { push @l, "no_holes"; $f &= ~0x200; } if ($f & 0x400) { push @l, "metadata_uuid"; $f &= ~0x400; } if ($f & 0x800) { push @l, "raid1c34"; $f &= ~0x800; } if ($f & 0x1000) { push @l, "zoned"; $f &= ~0x1000; } if ($f & 0x2000) { push @l, "extent_tree_v2"; $f &= ~0x2000; } if ($f & 0x4000) { push @l, "raid_stripe_tree"; $f &= ~0x4000; } if ($f & 0x10000) { push @l, "simple_quota"; $f &= ~0x10000; } if ($f != 0 || $#l == -1) { push @l, sprintf("%x", $f); } return join(',', @l); } sub compat_ro_flags { my ($f) = @_; my @l; if ($f & 1) { push @l, "free_space_tree"; $f &= ~1; } if ($f & 2) { push @l, "free_space_tree_valid"; $f &= ~2; } if ($f & 4) { push @l, "verity"; $f &= ~4; } if ($f & 8) { push @l, "block_group_tree"; $f &= ~8; } if ($f != 0 || $#l == -1) { push @l, sprintf("%x", $f); } return join(',', @l); } sub format_super_flags { my ($f) = @_; my @l; if ($f & 1) { push @l, "written"; $f &= ~1; } if ($f & 2) { push @l, "reloc"; $f &= ~2; } if ($f & 4) { push @l, "error"; $f &= ~4; } if ($f & 0x100000000) { push @l, "seeding"; $f &= ~0x100000000; } if ($f & 0x200000000) { push @l, "metadump"; $f &= ~0x200000000; } if ($f & 0x400000000) { push @l, "metadump_v2"; $f &= ~0x400000000; } if ($f & 0x800000000) { push @l, "changing_fsid"; $f &= ~0x800000000; } if ($f & 0x1000000000) { push @l, "changing_fsid_v2"; $f &= ~0x1000000000; } if ($f & 0x4000000000) { push @l, "changing_bg_tree"; $f &= ~0x4000000000; } if ($f & 0x8000000000) { push @l, "changing_data_csum"; $f &= ~0x8000000000; } if ($f & 0x10000000000) { push @l, "changin_meta_csum"; $f &= ~0x10000000000; } if ($f != 0 || $#l == -1) { push @l, sprintf("%x", $f); } return join(',', @l); } sub csum_type { my ($t) = @_; if ($t == 0) { return "crc32"; } elsif ($t == 1) { return "xxhash"; } elsif ($t == 2) { return "sha256"; } elsif ($t == 3) { return "blake2"; } else { return sprintf("%x", $t); } } sub read_superblock { my ($f) = @_; my ($sb, @b, @b2, @di, $csum); seek($f, 0x10000, 0); read($f, $sb, 0x1000); ($roottree, $chunktree, $logtree) = unpack("x80QQQ", $sb); @b = unpack("a32a16QQa8QQQQQQQQQVVVVVQQQQvCCCa98A256QQa16x224a2048a672", $sb); @di = unpack("QQQVVVQQQVCCa16a16", $b[27]); $csum_type = $b[23]; if ($csum_type == 1) { $csum = sprintf("%016x", unpack("Q", $b[0])); } elsif ($csum_type == 2 || $csum_type == 3) { $csum = sprintf("%016x%016x%016x%016x", unpack("QQQQ", $b[0])); } else { $csum = sprintf("%08x", unpack("V", $b[0])); } 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])); my $devid = format_uuid($di[12]); $blocksize = $b[14]; $nodesize = $b[15]; $devs{$di[0]} = $f; my $bootstrap = substr($b[32], 0, $b[18]); while (length($bootstrap) > 0) { #print Dumper($bootstrap)."\n"; @b2 = unpack("QCQ", $bootstrap); printf("bootstrap %x,%x,%x\n", @b2[0], @b2[1], @b2[2]); $bootstrap = substr($bootstrap, 0x11); my @c = unpack("QQQQVVVvv", $bootstrap); dump_item(0xe4, substr($bootstrap, 0, 0x30 + ($c[7] * 0x20)), "", 0); $bootstrap = substr($bootstrap, 0x30); my %obj; $obj{'offset'} = $b2[2]; $obj{'size'} = $c[0]; $obj{'type'} = $c[3]; $obj{'num_stripes'} = $c[7]; $obj{'stripe_len'} = $c[2]; $obj{'sub_stripes'} = $c[8]; for (my $i = 0; $i < $c[7]; $i++) { my @cis = unpack("QQa16", $bootstrap); $bootstrap = substr($bootstrap, 0x20); $obj{'stripes'}[$i]{'physoffset'} = $cis[1]; $obj{'stripes'}[$i]{'devid'} = $cis[0]; } push @l2p_bs, \%obj; } my $backups = $b[33]; while (length($backups) > 0) { my $backup = substr($backups, 0, 168); $backups = substr($backups, 168); my @b3 = unpack("QQQQQQQQQQQQQQQx32CCCCCCx10", $backup); 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); } print "\n"; } sub format_uuid { my ($s) = @_; my @b = unpack("VVVV", $s); return sprintf("%08x%08x%08x%08x", $b[3], $b[2], $b[1], $b[0]); } sub format_time { my ($t, $ns) = @_; my @tb = gmtime($t); return sprintf("%04u-%02u-%02uT%02u:%02u:%02u", $tb[5] + 1900, $tb[4] + 1, $tb[3], $tb[2], $tb[1], $tb[0]); } sub inode_flags { my ($flags) = @_; my @l = (); if ($flags & 1) { push @l, "nodatasum"; $flags &= ~1; } if ($flags & 2) { push @l, "nodatacow"; $flags &= ~2; } if ($flags & 4) { push @l, "readonly"; $flags &= ~4; } if ($flags & 8) { push @l, "nocompress"; $flags &= ~8; } if ($flags & 16) { push @l, "prealloc"; $flags &= ~16; } if ($flags & 32) { push @l, "sync"; $flags &= ~32; } if ($flags & 64) { push @l, "immutable"; $flags &= ~64; } if ($flags & 128) { push @l, "append"; $flags &= ~128; } if ($flags & 256) { push @l, "nodump"; $flags &= ~256; } if ($flags & 512) { push @l, "noatime"; $flags &= ~512; } if ($flags & 1024) { push @l, "dirsync"; $flags &= ~1024; } if ($flags & 2048) { push @l, "compress"; $flags &= ~2048; } if ($flags & 0x80000000) { push @l, "root_item_init"; $flags &= ~0x80000000; } if ($flags & 4294967296) { push @l, "ro_verity"; $flags &= ~4294967296; } if ($flags != 0) { push @l, sprintf("%x", $flags); } if ($#l > -1) { return join(',', @l); } else { return 0; } } sub format_balance { my ($s) = @_; my (@b, $flags, @f, $fl, $t); @b = unpack("QVVQQQQQQQVVVV", $s); $flags = $b[9]; $t = sprintf("profiles=%x", $b[0]); if ($flags & (1 << 10)) { $t .= sprintf(" usage=%x", ($b[2] << 32) | $b[1]); } elsif ($flags & (1 << 1)) { $t .= sprintf(" usage=%x..%x", $b[1], $b[2]); } $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]); @f = (); $fl = $flags; if ($fl & (1 << 0)) { push @f, "profiles"; $fl &= ~(1 << 0); } if ($fl & (1 << 1)) { push @f, "usage"; $fl &= ~(1 << 1); } if ($fl & (1 << 2)) { push @f, "devid"; $fl &= ~(1 << 2); } if ($fl & (1 << 3)) { push @f, "drange"; $fl &= ~(1 << 3); } if ($fl & (1 << 4)) { push @f, "vrange"; $fl &= ~(1 << 4); } if ($fl & (1 << 5)) { push @f, "limit"; $fl &= ~(1 << 5); } if ($fl & (1 << 6)) { push @f, "limitrange"; $fl &= ~(1 << 6); } if ($fl & (1 << 7)) { push @f, "stripesrange"; $fl &= ~(1 << 7); } if ($fl & (1 << 8)) { push @f, "convert"; $fl &= ~(1 << 8); } if ($fl & (1 << 9)) { push @f, "soft"; $fl &= ~(1 << 9); } if ($fl & (1 << 10)) { push @f, "usagerange"; $fl &= ~(1 << 10); } if ($fl != 0 || $#f == -1) { push @f, $fl; } $t .= sprintf(" flags=%s", join(',', @f)); if ($flags & (1 << 5)) { $t .= sprintf(" limit=%x", ($b[11] << 32) | $b[10]); } elsif ($flags & (1 << 6)) { $t .= sprintf(" limit=%x..%x", $b[11], $b[10]); } if ($flags & (1 << 7)) { $t .= sprintf(" stripes=%x..%x", $b[12], $b[13]); } return $t; } sub qgroup_status_flags { my ($f) = @_; my (@l); if ($f & 1) { push @l, "on"; $f &= ~1; } if ($f & 2) { push @l, "rescan"; $f &= ~2; } if ($f & 4) { push @l, "inconsistent"; $f &= ~4; } if ($f & 8) { push @l, "simple_mode"; $f &= ~8; } if ($f != 0) { push @l, $f; } return join(',', @l); } sub free_space_bitmap { my ($s, $off) = @_; my $b = ""; while (length($s) != 0) { $b .= reverse(sprintf("%08b", ord($s))); $s = substr($s, 1, length($s) - 1); } my @runs = (); my $run_start = 0; for (my $i = 0; $i < length($b); $i++) { my $c = substr($b, $i, 1); if ($c eq "1" && ($i == 0 || substr($b, $i - 1, 1) eq "0")) { $run_start = $i; } elsif ($c eq "0" && $i != 0 && substr($b, $i - 1, 1) eq "1") { push @runs, sprintf("%x, %x", $off + ($run_start * $blocksize), ($i - $run_start) * $blocksize); } } if (substr($b, length($b) - 1, 1) eq "1") { push @runs, sprintf("%x, %x", $off + ($run_start * $blocksize), (length($b) - $run_start) * $blocksize); } return join('; ', @runs); } sub block_group_item_flags { my ($f) = @_; my (@l); if ($f & 1) { push @l, "data"; $f &= ~1; } if ($f & 2) { push @l, "system"; $f &= ~2; } if ($f & 4) { push @l, "metadata"; $f &= ~4; } if ($f & 8) { push @l, "raid0"; $f &= ~8; } if ($f & 16) { push @l, "raid1"; $f &= ~16; } if ($f & 32) { push @l, "dup"; $f &= ~32; } if ($f & 64) { push @l, "raid10"; $f &= ~64; } if ($f & 128) { push @l, "raid5"; $f &= ~128; } if ($f & 256) { push @l, "raid6"; $f &= ~256; } if ($f & 512) { push @l, "raid1c3"; $f &= ~512; } if ($f & 1024) { push @l, "raid1c4"; $f &= ~1024; } if ($f != 0) { push @l, $f; } return join(',', @l); } sub extent_item_flags { my ($f) = @_; my (@l); if ($f & 1) { push @l, "data"; $f &= ~1; } if ($f & 2) { push @l, "tree_block"; $f &= ~2; } if ($f & 256) { push @l, "full_backref"; $f &= ~256; } if ($f != 0) { push @l, $f; } return join(',', @l); } sub extent_data_type { my ($d) = @_; if ($d == 0) { return "inline"; } elsif ($d == 1) { return "reg"; } elsif ($d == 2) { return "prealloc"; } else { return sprintf("%x", $d); } } sub compression_type { my ($d) = @_; if ($d == 0) { return "none"; } elsif ($d == 1) { return "zlib"; } elsif ($d == 2) { return "lzo"; } elsif ($d == 3) { return "zstd"; } else { return sprintf("%x", $d); } } sub dir_item_type { my ($d) = @_; if ($d == 0) { return "unknown"; } elsif ($d == 1) { return "reg_file"; } elsif ($d == 2) { return "dir"; } elsif ($d == 3) { return "chrdev"; } elsif ($d == 4) { return "blkdev"; } elsif ($d == 5) { return "fifo"; } elsif ($d == 6) { return "sock"; } elsif ($d == 7) { return "symlink"; } elsif ($d == 8) { return "xattr"; } else { return sprintf("%x", $d); } } sub free_space_info_flags { my ($f) = @_; my (@l); if ($f & 1) { push @l, "using_bitmaps"; $f &= ~1; } if ($f != 0 || $#l == -1) { push @l, sprintf("%x", $f); } return join(',', @l); } sub qgroup_limit_flags { my ($f) = @_; my (@l); if ($f & 1) { push @l, "max_rfer"; $f &= ~1; } if ($f & 2) { push @l, "max_excl"; $f &= ~2; } if ($f & 4) { push @l, "rsv_rfer"; $f &= ~4; } if ($f & 8) { push @l, "rsv_excl"; $f &= ~8; } if ($f & 16) { push @l, "rfer_cmpr"; $f &= ~8; } if ($f & 32) { push @l, "excl_cmpr"; $f &= ~8; } if ($f != 0 || $#l == -1) { push @l, $f; } return join(',', @l); } sub dump_item { my ($type, $s, $pref, $id, $off) = @_; my (@b); my $unrecog = 0; print $pref; if ($type == 0x1 || $type == 0x84) { # INODE_ITEM or ROOT_ITEM if (length($s) < 0xa0) { $s .= chr(0) x (0xa0 - length($s)); } @b = unpack("QQQQQVVVVQQQx32QVQVQVQV", $s); $s = substr($s, 0xa0); if ($type == 0x84) { print "root_item"; } else { print "inode_item"; } 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])); if ($type != 0x1) { @b = unpack("QQQQQQQVQCQCC", $s); $s = substr($s, 0x4f); #print Dumper(@b)."\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); @b = unpack("Qa16a16a16QQQQQVQVQVQV", $s); $s = substr($s, 0xc8); # above + 64 blank bytes 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])); } } elsif ($type == 0xc) { # INODE_REF printf("inode_ref"); do { @b = unpack("Qv", $s); $s = substr($s, 0xa); my $name = substr($s, 0, $b[1]); $s = substr($s, $b[1]); printf(" index=%x name_len=%x name=%s", $b[0], $b[1], $name); } while (length($s) > 0); } elsif ($type == 0xd) { # INODE_EXTREF printf("inode_extref"); do { @b = unpack("QQv", $s); $s = substr($s, 0x12); my $name = substr($s, 0, $b[2]); $s = substr($s, $b[2]); printf(" parent_objectid=%x index=%x name_len=%x name=%s", $b[0], $b[1], $b[2], $name); } while (length($s) > 0); } elsif ($type == 0x18 || $type == 0x54 || $type == 0x60) { # XATTR_ITEM, DIR_ITEM or DIR_INDEX print $type == 0x54 ? "dir_item" : ($type == 0x18 ? "xattr_item" : "dir_index"); while (length($s) > 0) { @b = unpack("QCQQvvC", $s); $s = substr($s, 0x1e); my $name = substr($s, 0, $b[5]); $s = substr($s, $b[5]); my $name2 = substr($s, 0, $b[4]); $s = substr($s, $b[4]); 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)); } } elsif ($type == 0x24) { # VERITY_DESC_ITEM printf("verity_desc_item"); if ($off == 0) { @b = unpack("Qx16C", $s); $s = substr($s, 25); printf(" size=%x encryption=%x", $b[0], $b[1]); } else { while (length($s) > 0) { @b = unpack("C", $s); printf(" %02x", $b[0]); $s = substr($s, 1); } } } elsif ($type == 0x25) { # VERITY_MERKLE_ITEM printf("verity_merkle_item"); while (length($s) > 0) { @b = unpack("NNNNNNNN", $s); 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]); $s = substr($s, 32); } } elsif ($type == 0x30) { # ORPHAN_ITEM printf("orphan_item"); } elsif ($type == 0x48) { # DIR_LOG_INDEX @b = unpack("Q", $s); $s = substr($s, 8); printf("dir_log_index end=%x", $b[0]); } elsif ($type == 0x6c) { # EXTENT_DATA @b = unpack("QQCCvC", $s); $s = substr($s, 0x15); 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])); if ($b[5] != 0) { @b = unpack("QQQQ", $s); $s = substr($s, 0x20); printf(" disk_bytenr=%x disk_num_bytes=%x offset=%x num_bytes=%x", @b); } else { $s = substr($s, $b[1]); } } elsif ($type == 0x80) { # EXTENT_CSUM print "extent_csum"; if ($csum_type == 1) { # xxhash while (length($s) > 0) { printf(" %016x", unpack("Q", $s)); $s = substr($s, 8); } } elsif ($csum_type == 2 || $csum_type == 3) { # sha256 or blake2 while (length($s) > 0) { printf(" %016x%016x%016x%016x", unpack("QQQQ", $s)); $s = substr($s, 32); } } else { while (length($s) > 0) { printf(" %08x", unpack("V", $s)); $s = substr($s, 4); } } } elsif ($type == 0x90 || $type == 0x9c) { # ROOT_BACKREF or ROOT_REF @b = unpack("QQv", $s); $s = substr($s, 18); my $name = substr($s, 0, $b[2]); $s = substr($s, $b[2]); printf("%s dirid=%x sequence=%x name_len=%x name=%s", $type == 0x90 ? "root_backref" : "root_ref", $b[0], $b[1], $b[2], $name); } elsif ($type == 0xa8 || $type == 0xa9) { # EXTENT_ITEM_KEY or METADATA_ITEM_KEY # FIXME - TREE_BLOCK is out by one byte (why?) if (length($s) == 4) { @b = unpack("L", $s); $s = substr($s, 4); printf("extent_item_v0 refcount=%x", $b[0]); } else { @b = unpack("QQQ", $s); printf("%s refs=%x generation=%x flags=%s", $type == 0xa9 ? "metadata_item" : "extent_item", $b[0], $b[1], extent_item_flags($b[2])); $s = substr($s, 24); my $refcount = $b[0]; if ($b[2] & 2 && $type != 0xa9) { @b = unpack("QCQC", $s); printf(" key=%x,%x,%x level=%u", $b[0], $b[1], $b[2], $b[3]); $s = substr($s, 18); } while (length($s) > 0) { my $irt = unpack("C", $s); $s = substr($s, 1); if ($irt == 0xac) { @b = unpack("Q", $s); $s = substr($s, 8); printf(" extent_owner_ref root=%x", $b[0]); } elsif ($irt == 0xb0) { @b = unpack("Q", $s); $s = substr($s, 8); printf(" tree_block_ref root=%x", $b[0]); } elsif ($irt == 0xb2) { @b = unpack("QQQv", $s); $s = substr($s, 28); printf(" extent_data_ref root=%x objectid=%x offset=%x count=%x", @b); $refcount -= $b[3] - 1; } elsif ($irt == 0xb6) { @b = unpack("Q", $s); $s = substr($s, 8); printf(" shared_block_ref offset=%x", $b[0]); } elsif ($irt == 0xb8) { @b = unpack("Qv", $s); $s = substr($s, 12); printf(" shared_data_ref offset=%x count=%x", @b); $refcount -= $b[1] - 1; } else { printf(" unknown %x (length %u)", $irt, length($s)); } } } } elsif ($type == 0xb0) { # TREE_BLOCK_REF printf("tree_block_ref"); } elsif ($type == 0xb2) { # EXTENT_DATA_REF @b = unpack("QQQv", $s); $s = substr($s, 28); printf("extent_data_ref root=%x objectid=%x offset=%x count=%x", @b); } elsif ($type == 0xb4) { # EXTENT_REF_V0 @b = unpack("QQQv", $s); $s = substr($s, 28); printf("extent_ref_v0 root=%x gen=%x objid=%x count=%x", @b); } elsif ($type == 0xb6) { # SHARED_BLOCK_REF printf("shared_block_ref"); } elsif ($type == 0xb8) { # SHARED_DATA_REF @b = unpack("v", $s); $s = substr($s, 4); printf("shared_data_ref count=%x", @b); } elsif ($type == 0xc0) { # BLOCK_GROUP_ITEM @b = unpack("QQQ", $s); $s = substr($s, 0x18); printf("block_group_item used=%x chunk_objectid=%x flags=%s", $b[0], $b[1], block_group_item_flags($b[2])); } elsif ($type == 0xc6) { # FREE_SPACE_INFO @b = unpack("VV", $s); $s = substr($s, 0x8); printf("free_space_info extent_count=%x flags=%s", $b[0], free_space_info_flags($b[1])); } elsif ($type == 0xc7) { # FREE_SPACE_EXTENT printf("free_space_extent"); } elsif ($type == 0xc8) { # FREE_SPACE_BITMAP printf("free_space_bitmap %s", free_space_bitmap($s, $id)); $s = ""; } elsif ($type == 0xcc) { # DEV_EXTENT @b = unpack("QQQQa16", $s); $s = substr($s, 0x30); 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])); } elsif ($type == 0xd8) { # DEV_ITEM @b = unpack("QQQVVVQQQVCCa16a16", $s); 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])); $s = substr($s, 0x62); } elsif ($type == 0xe4) { # CHUNK_ITEM @b = unpack("QQQQVVVvv", $s); 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", $b[0], $b[1], $b[2], block_group_item_flags($b[3]), $b[4], $b[5], $b[6], $b[7], $b[8]); $s = substr($s, 0x30); my $numstripes = $b[7]; for (my $i = 0; $i < $numstripes; $i++) { @b = unpack("QQa16", $s); $s = substr($s, 0x20); printf(" stripe(%u) devid=%x offset=%x dev_uuid=%s", $i, $b[0], $b[1], format_uuid($b[2])); } } elsif ($type == 0xf0) { # QGROUP_STATUS @b = unpack("QQQQQ", $s); 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]); $s = substr($s, 0x28); } elsif ($type == 0xf2) { # QGROUP_INFO @b = unpack("QQQQQ", $s); 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]); $s = substr($s, 0x28); } elsif ($type == 0xf4) { # QGROUP_LIMIT @b = unpack("QQQQQ", $s); 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]); $s = substr($s, 0x28); } elsif ($type == 0xf6) { # QGROUP_RELATION printf("qgroup_relation"); } elsif ($type == 0xf8 && $id == 0xfffffffffffffffc) { # balance my ($fl, @f); @b = unpack("Q", $s); $s = substr($s, 8); $fl = $b[0]; @f = (); if ($fl & (1 << 0)) { push @f, "data"; $fl &= ~(1 << 0); } if ($fl & (1 << 1)) { push @f, "system"; $fl &= ~(1 << 1); } if ($fl & (1 << 2)) { push @f, "metadata"; $fl &= ~(1 << 2); } if ($fl != 0 || $#f == -1) { push @f, $fl; } 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))); $s = substr($s, 0x1b8); } elsif ($type == 0xf9) { # DEV_STATS print "dev_stats"; while (length($s) > 0) { printf(" %x", unpack("Q", $s)); $s = substr($s, 8); } } elsif ($type == 0xfb) { # UUID_SUBVOL print "uuid_subvol"; while (length($s) > 0) { printf(" %x", unpack("Q", $s)); $s = substr($s, 8); } } elsif ($type == 0xfc) { # UUID_REC_SUBVOL print "uuid_rec_subvol"; while (length($s) > 0) { printf(" %x", unpack("Q", $s)); $s = substr($s, 8); } } elsif ($type == 0 && $id == 0xfffffffffffffff5) { # free space @b = unpack("QCQQQQ", $s); $s = substr($s, 0x29); printf("free_space location=(%x,%x,%x) generation=%x num_entries=%x num_bitmaps=%x", @b); } else { printf STDERR("ERROR - unknown type %x (size=%x, tell=%x)\n", $type, length($s), tell($f)); printf("unknown (size=%x)", length($s)); $unrecog = 1; } if ($unrecog == 0 && length($s) > 0) { printf(" (left=%x)", length($s)); } print "\n"; } sub read_data { my ($addr, $size, $bs) = @_; my (@arr, $f, $data, $stripeoff, $parity, $stripe, $stripe2, $physoff); if ($bs == 1) { @arr = @l2p_bs; } else { @arr = @l2p; } foreach my $obj (@arr) { if ($obj->{'offset'} <= $addr && ($addr - $obj->{'offset'}) < $obj->{'size'}) { if ($obj->{'type'} & 0x80) { # RAID5 my $data_stripes = $obj->{'num_stripes'} - 1; $stripeoff = ($addr - $obj->{'offset'}) % ($data_stripes * $obj->{'stripe_len'}); $parity = (int(($addr - $obj->{'offset'}) / ($data_stripes * $obj->{'stripe_len'})) + $obj->{'num_stripes'} - 1) % $obj->{'num_stripes'}; $stripe2 = int($stripeoff / $obj->{'stripe_len'}); $stripe = ($parity + $stripe2 + 1) % $obj->{'num_stripes'}; $f = $devs{$obj->{'stripes'}[$stripe]{'devid'}}; $physoff = $obj->{'stripes'}[$stripe]{'physoffset'} + (int(($addr - $obj->{'offset'}) / ($data_stripes * $obj->{'stripe_len'})) * $obj->{'stripe_len'}) + ($stripeoff % $obj->{'stripe_len'}); } elsif ($obj->{'type'} & 0x100) { # RAID6 my $data_stripes = $obj->{'num_stripes'} - 2; $stripeoff = ($addr - $obj->{'offset'}) % ($data_stripes * $obj->{'stripe_len'}); $parity = (int(($addr - $obj->{'offset'}) / ($data_stripes * $obj->{'stripe_len'})) + $obj->{'num_stripes'} - 1) % $obj->{'num_stripes'}; $stripe2 = int($stripeoff / $obj->{'stripe_len'}); $stripe = ($parity + $stripe2 + 1) % $obj->{'num_stripes'}; $f = $devs{$obj->{'stripes'}[$stripe]{'devid'}}; $physoff = $obj->{'stripes'}[$stripe]{'physoffset'} + (int(($addr - $obj->{'offset'}) / ($data_stripes * $obj->{'stripe_len'})) * $obj->{'stripe_len'}) + ($stripeoff % $obj->{'stripe_len'}); } elsif ($obj->{'type'} & 0x40) { # RAID10 my $stripe_num = ($addr - $obj->{'offset'}) / $obj->{'stripe_len'}; my $stripe_offset = ($addr - $obj->{'offset'}) % $obj->{'stripe_len'}; my $stripe = $stripe_num % ($obj->{'num_stripes'} / $obj->{'sub_stripes'}) * $obj->{'sub_stripes'}; $f = $devs{$obj->{'stripes'}[$stripe]{'devid'}}; $physoff = $obj->{'stripes'}[$stripe]{'physoffset'} + (($stripe_num / ($obj->{'num_stripes'} / $obj->{'sub_stripes'})) * $obj->{'stripe_len'}) + $stripe_offset; } elsif ($obj->{'type'} & 0x8) { # RAID0 my $stripe_num = ($addr - $obj->{'offset'}) / $obj->{'stripe_len'}; my $stripe_offset = ($addr - $obj->{'offset'}) % $obj->{'stripe_len'}; my $stripe = $stripe_num % $obj->{'num_stripes'}; $f = $devs{$obj->{'stripes'}[$stripe]{'devid'}}; $physoff = $obj->{'stripes'}[$stripe]{'physoffset'} + (($stripe_num / $obj->{'num_stripes'}) * $obj->{'stripe_len'}) + $stripe_offset; } else { # SINGLE, DUP, RAID1, RAID1C3, RAID1C4 $f = $devs{$obj->{'stripes'}[0]{'devid'}}; $physoff = $obj->{'stripes'}[0]{'physoffset'} + $addr - $obj->{'offset'}; } seek($f, $physoff, 0); read($f, $data, $size); return $data; } } } sub header_flags { my ($flags) = @_; my @l = (); if ($flags & 1) { push @l, "written"; $flags &= ~1; } if ($flags & 2) { push @l, "reloc"; $flags &= ~2; } if ($flags & 0x100000000000000) { push @l, "mixed_backref"; $flags &= ~0x100000000000000; } if ($flags != 0) { push @l, sprintf("%x", $flags); } if ($#l > -1) { return join(',', @l); } else { return 0; } } sub dump_tree { my ($addr, $pref, $bs) = @_; my ($head, @headbits, $level, $treenum, $tree, $csum); $tree = read_data($addr, $nodesize, $bs); @headbits = unpack("a32a16QQa16QQVC", $tree); if ($headbits[2] != $addr) { printf STDERR sprintf("Address mismatch: expected %llx, got %llx\n", $addr, $headbits[2]); exit; } if ($csum_type == 1) { $csum = sprintf("%016x", unpack("Q", $headbits[0])); } elsif ($csum_type == 2 || $csum_type == 3) { $csum = sprintf("%016x%016x%016x%016x", unpack("QQQQ", $headbits[0])); } else { $csum = sprintf("%08x", unpack("V", $headbits[0])); } print $pref; 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]); $level = $headbits[8]; $treenum = $headbits[6]; my $numitems = $headbits[7]; if ($level == 0) { my $headaddr = tell($f); for (my $i = 0; $i < $numitems; $i++) { #read($f, my $itemhead, 0x19); my $itemhead = substr($tree, 0x65 + ($i * 0x19), 0x19); my @ihb = unpack("QCQVV", $itemhead); #print Dumper(@ihb)."\n"; print $pref; printf("%x,%x,%x\n", $ihb[0], $ihb[1], $ihb[2]); my $item = substr($tree, 0x65 + $ihb[3], $ihb[4]); dump_item($ihb[1], $item, $pref, $ihb[0], $ihb[2]); if ($treenum == 3 && $ihb[1] == 0xe4) { my @b = unpack("QQQQVVVvv", $item); my $stripes = substr($item, 48); my %obj; my $numstripes = $b[7]; $obj{'offset'} = $ihb[2]; $obj{'size'} = $b[0]; $obj{'type'} = $b[3]; $obj{'num_stripes'} = $b[7]; $obj{'stripe_len'} = $b[2]; $obj{'sub_stripes'} = $b[8]; for (my $i = 0; $i < $numstripes; $i++) { my @cis = unpack("QQa16", $stripes); $stripes = substr($stripes, 32); $obj{'stripes'}[$i]{'physoffset'} = $cis[1]; $obj{'stripes'}[$i]{'devid'} = $cis[0]; } push @l2p, \%obj; #print Dumper(@l2p); } if ($ihb[1] == 0x84) { if ($treenum == 1) { $roots{$ihb[0]} = unpack("x176Q", $item); } elsif ($treenum == 0xfffffffffffffffa && $ihb[0] == 0xfffffffffffffffa) { $logroots{$ihb[2]} = unpack("x176Q", $item); } } } } else { for (my $i = 0; $i < $numitems; $i++) { my $itemhead = substr($tree, 0x65 + ($i * 0x21), 0x21); my @ihb = unpack("QCQQQ", $itemhead); print $pref; printf("%x,%x,%x blockptr=%x generation=%x\n", $ihb[0], $ihb[1], $ihb[2], $ihb[3], $ihb[4]); dump_tree($ihb[3], " " . $pref, $bs); } } } ================================================ FILE: mingw-amd64.cmake ================================================ # # To Cross-compile, use: # cmake -DCMAKE_TOOLCHAIN_FILE=../mingw.cmake .. # # the name of the target operating system SET(CMAKE_SYSTEM_NAME Windows) # which compilers to use for C and C++ SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) SET(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) SET(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) # here is the target environment located SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) # adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search # programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_PREFIX_PATH /usr/x86_64-w64-mingw32/usr/lib/cmake) set(CMAKE_SYSTEM_PROCESSOR x86_64) ================================================ FILE: mingw-x86.cmake ================================================ # # To Cross-compile, use: # cmake -DCMAKE_TOOLCHAIN_FILE=../mingw.cmake .. # # the name of the target operating system SET(CMAKE_SYSTEM_NAME Windows) # which compilers to use for C and C++ SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc) SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) SET(CMAKE_RC_COMPILER i686-w64-mingw32-windres) SET(CMAKE_C_FLAGS "-m32") SET(CMAKE_CXX_FLAGS "-m32") SET(CMAKE_RC_FLAGS "-F pe-i386") SET(CMAKE_ASM_FLAGS "-m32") # here is the target environment located SET(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32) # adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search # programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_PREFIX_PATH /usr/i686-w64-mingw32/usr/lib/cmake) set(CMAKE_SYSTEM_PROCESSOR x86) ================================================ FILE: msvc-aarch64.cmake ================================================ set(CMAKE_SYSTEM_NAME Windows) SET(CMAKE_C_COMPILER /opt/msvc/bin/arm64/cl) SET(CMAKE_CXX_COMPILER /opt/msvc/bin/arm64/cl) SET(CMAKE_RC_COMPILER /opt/msvc/bin/arm64/rc) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /MANIFEST:NO") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /MANIFEST:NO") ================================================ FILE: msvc-amd64.cmake ================================================ set(CMAKE_SYSTEM_NAME Windows) SET(CMAKE_C_COMPILER /opt/msvc/bin/x64/cl) SET(CMAKE_CXX_COMPILER /opt/msvc/bin/x64/cl) SET(CMAKE_RC_COMPILER /opt/msvc/bin/x64/rc) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /MANIFEST:NO") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /MANIFEST:NO") ================================================ FILE: msvc-armv7.cmake ================================================ set(CMAKE_SYSTEM_NAME Windows) SET(CMAKE_C_COMPILER /opt/msvc/bin/arm/cl) SET(CMAKE_CXX_COMPILER /opt/msvc/bin/arm/cl) SET(CMAKE_RC_COMPILER /opt/msvc/bin/arm/rc) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /MANIFEST:NO") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /MANIFEST:NO") ================================================ FILE: msvc-x86.cmake ================================================ set(CMAKE_SYSTEM_NAME Windows) SET(CMAKE_C_COMPILER /opt/msvc/bin/x86/cl) SET(CMAKE_CXX_COMPILER /opt/msvc/bin/x86/cl) SET(CMAKE_RC_COMPILER /opt/msvc/bin/x86/rc) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /MANIFEST:NO") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /MANIFEST:NO") set(CMAKE_PREFIX_PATH "/home/hellas/wine/vcpkg/installed/x64-windows") ================================================ FILE: send-dump.pl ================================================ #!/usr/bin/perl # Dumper for btrfs send streams. # Released under the same terms, or lack thereof, as btrfs-dump.pl. open($f,$ARGV[0]) or die "Error opening ".$ARGV[0].": ".$!; binmode($f); while (!eof($f)) { do_stream($f); if (!eof($f)) { print "---\n"; } } close($f); sub do_stream { my ($f)=@_; read($f,$a,0x11); ($magic,$ver)=unpack("a13V",$a); if ($magic ne "btrfs-stream\0") { printf STDERR "Not a send file.\n"; close($f); exit; } if ($ver != 1 && $ver != 2) { printf STDERR "Version $ver not supported.\n"; close($f); exit; } $type = 0; while (!eof($f) && $type != 21) { read($f,$a,0xa); ($len,$type,$crc)=unpack("VvV",$a); if ($type == 1) { printf("subvol, %x, %08x\n", $len, $crc); } elsif ($type == 2) { printf("snapshot, %x, %08x\n", $len, $crc); } elsif ($type == 3) { printf("mkfile, %x, %08x\n", $len, $crc); } elsif ($type == 4) { printf("mkdir, %x, %08x\n", $len, $crc); } elsif ($type == 5) { printf("mknod, %x, %08x\n", $len, $crc); } elsif ($type == 6) { printf("mkfifo, %x, %08x\n", $len, $crc); } elsif ($type == 7) { printf("mksock, %x, %08x\n", $len, $crc); } elsif ($type == 8) { printf("symlink, %x, %08x\n", $len, $crc); } elsif ($type == 9) { printf("rename, %x, %08x\n", $len, $crc); } elsif ($type == 10) { printf("link, %x, %08x\n", $len, $crc); } elsif ($type == 11) { printf("unlink, %x, %08x\n", $len, $crc); } elsif ($type == 12) { printf("rmdir, %x, %08x\n", $len, $crc); } elsif ($type == 13) { printf("set_xattr, %x, %08x\n", $len, $crc); } elsif ($type == 14) { printf("remove_xattr, %x, %08x\n", $len, $crc); } elsif ($type == 15) { printf("write, %x, %08x\n", $len, $crc); } elsif ($type == 16) { printf("clone, %x, %08x\n", $len, $crc); } elsif ($type == 17) { printf("truncate, %x, %08x\n", $len, $crc); } elsif ($type == 18) { printf("chmod, %x, %08x\n", $len, $crc); } elsif ($type == 19) { printf("chown, %x, %08x\n", $len, $crc); } elsif ($type == 20) { printf("utimes, %x, %08x\n", $len, $crc); } elsif ($type == 21) { printf("end, %x, %08x\n", $len, $crc); } elsif ($type == 22) { printf("update-extent, %x, %08x\n", $len, $crc); } elsif ($type == 23) { printf("fallocate, %x, %08x\n", $len, $crc); } elsif ($type == 24) { printf("fileattr, %x, %08x\n", $len, $crc); } elsif ($type == 25) { printf("encoded-write, %x, %08x\n", $len, $crc); } else { printf("unknown(%x), %x, %08x\n", $type, $len, $crc); } read($f,$b,$len); print_tlvs($b); } } sub btrfstime { my ($t)=@_; my $ut = unpack("Q",$t); my @lt = localtime($ut); return sprintf("%04u-%02u-%02u %02u:%02u:%02u",$lt[5]+1900,$lt[4]+1,$lt[3],$lt[2],$lt[1],$lt[0]); } sub print_tlvs { my ($b)=@_; while (length($b)>0) { my ($t,$l)=unpack("vv",$b); if ($t == 1) { printf(" uuid: %08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x\n", unpack("NnnnCCCCCC",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 2) { printf(" transid: %x\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 3) { printf(" inode: %x\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 4) { printf(" size: %x\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 5) { printf(" mode: %o\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 6) { printf(" uid: %u\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 7) { printf(" gid: %u\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 8) { printf(" rdev: %x\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 9) { printf(" ctime: %s\n", btrfstime(substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 10) { printf(" mtime: %s\n", btrfstime(substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 11) { printf(" atime: %s\n", btrfstime(substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 12) { printf(" otime: %s\n", btrfstime(substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 13) { printf(" xattr_name: \"%s\"\n", substr($b,4,$l)); $b=substr($b,$l+4); } elsif ($t == 14) { printf(" xattr_data: \"%s\"\n", substr($b,4,$l)); $b=substr($b,$l+4); } elsif ($t == 15) { printf(" path: \"%s\"\n", substr($b,4,$l)); $b=substr($b,$l+4); } elsif ($t == 16) { printf(" path_to: \"%s\"\n", substr($b,4,$l)); $b=substr($b,$l+4); } elsif ($t == 17) { printf(" path_link: \"%s\"\n", substr($b,4,$l)); $b=substr($b,$l+4); } elsif ($t == 18) { printf(" offset: %x\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 19) { if ($ver == 2) { printf(" data: (%x bytes)\n", length($b) - 2); # FIXME $b=""; } else { printf(" data: (%x bytes)\n", $l); $b=substr($b,$l+4); } } elsif ($t == 20) { printf(" clone_uuid: %08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x\n", unpack("NnnnCCCCCC",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 21) { printf(" clone_transid: %x\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 22) { printf(" clone_path: \"%s\"\n", substr($b,4,$l)); $b=substr($b,$l+4); } elsif ($t == 23) { printf(" clone_offset: %x\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 24) { printf(" clone_len: %x\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 25) { printf(" fallocate_mode: %x\n", unpack("V",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 26) { printf(" fileattr: %x\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 27) { printf(" unencoded_file_len: %x\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 28) { printf(" unencoded_len: %x\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 29) { printf(" unencoded_offset: %x\n", unpack("Q",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 30) { printf(" compression: %x\n", unpack("V",substr($b,4,$l))); $b=substr($b,$l+4); } elsif ($t == 31) { printf(" encryption: %x\n", unpack("V",substr($b,4,$l))); $b=substr($b,$l+4); } else { printf(" unknown(%u),%x\n",$t,$l); $b=substr($b,$l+4); } } } ================================================ FILE: src/balance.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include "btrfsioctl.h" #include "crc32c.h" #include typedef struct { uint64_t address; uint64_t new_address; tree_header* data; EXTENT_ITEM* ei; tree* t; bool system; LIST_ENTRY refs; LIST_ENTRY list_entry; } metadata_reloc; typedef struct { uint8_t type; uint64_t hash; union { TREE_BLOCK_REF tbr; SHARED_BLOCK_REF sbr; }; metadata_reloc* parent; bool top; LIST_ENTRY list_entry; } metadata_reloc_ref; typedef struct { uint64_t address; uint64_t size; uint64_t new_address; chunk* newchunk; EXTENT_ITEM* ei; LIST_ENTRY refs; LIST_ENTRY list_entry; } data_reloc; typedef struct { uint8_t type; uint64_t hash; union { EXTENT_DATA_REF edr; SHARED_DATA_REF sdr; }; metadata_reloc* parent; LIST_ENTRY list_entry; } data_reloc_ref; #define BALANCE_UNIT 0x100000 // only read 1 MB at a time static NTSTATUS add_metadata_reloc(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, LIST_ENTRY* items, traverse_ptr* tp, bool skinny, metadata_reloc** mr2, chunk* c, LIST_ENTRY* rollback) { NTSTATUS Status; metadata_reloc* mr; EXTENT_ITEM* ei; uint16_t len; uint64_t inline_rc; uint8_t* ptr; mr = ExAllocatePoolWithTag(PagedPool, sizeof(metadata_reloc), ALLOC_TAG); if (!mr) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } mr->address = tp->item->key.obj_id; mr->data = NULL; mr->ei = (EXTENT_ITEM*)tp->item->data; mr->system = false; InitializeListHead(&mr->refs); Status = delete_tree_item(Vcb, tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); ExFreePool(mr); return Status; } if (!c) c = get_chunk_from_address(Vcb, tp->item->key.obj_id); if (c) { acquire_chunk_lock(c, Vcb); c->used -= Vcb->superblock.node_size; space_list_add(c, tp->item->key.obj_id, Vcb->superblock.node_size, rollback); release_chunk_lock(c, Vcb); } ei = (EXTENT_ITEM*)tp->item->data; inline_rc = 0; len = tp->item->size - sizeof(EXTENT_ITEM); ptr = (uint8_t*)tp->item->data + sizeof(EXTENT_ITEM); if (!skinny) { len -= sizeof(EXTENT_ITEM2); ptr += sizeof(EXTENT_ITEM2); } while (len > 0) { uint8_t secttype = *ptr; uint16_t sectlen = secttype == TYPE_TREE_BLOCK_REF ? sizeof(TREE_BLOCK_REF) : (secttype == TYPE_SHARED_BLOCK_REF ? sizeof(SHARED_BLOCK_REF) : 0); metadata_reloc_ref* ref; len--; if (sectlen > len) { 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); return STATUS_INTERNAL_ERROR; } if (sectlen == 0) { ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, secttype); return STATUS_INTERNAL_ERROR; } ref = ExAllocatePoolWithTag(PagedPool, sizeof(metadata_reloc_ref), ALLOC_TAG); if (!ref) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } if (secttype == TYPE_TREE_BLOCK_REF) { ref->type = TYPE_TREE_BLOCK_REF; RtlCopyMemory(&ref->tbr, ptr + sizeof(uint8_t), sizeof(TREE_BLOCK_REF)); inline_rc++; } else if (secttype == TYPE_SHARED_BLOCK_REF) { ref->type = TYPE_SHARED_BLOCK_REF; RtlCopyMemory(&ref->sbr, ptr + sizeof(uint8_t), sizeof(SHARED_BLOCK_REF)); inline_rc++; } else { ERR("unexpected tree type %x\n", secttype); ExFreePool(ref); return STATUS_INTERNAL_ERROR; } ref->parent = NULL; ref->top = false; InsertTailList(&mr->refs, &ref->list_entry); len -= sectlen; ptr += sizeof(uint8_t) + sectlen; } if (inline_rc < ei->refcount) { // look for non-inline entries traverse_ptr tp2 = *tp, next_tp; while (find_next_item(Vcb, &tp2, &next_tp, false, NULL)) { tp2 = next_tp; if (tp2.item->key.obj_id == tp->item->key.obj_id) { if (tp2.item->key.obj_type == TYPE_TREE_BLOCK_REF) { metadata_reloc_ref* ref = ExAllocatePoolWithTag(PagedPool, sizeof(metadata_reloc_ref), ALLOC_TAG); if (!ref) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ref->type = TYPE_TREE_BLOCK_REF; ref->tbr.offset = tp2.item->key.offset; ref->parent = NULL; ref->top = false; InsertTailList(&mr->refs, &ref->list_entry); Status = delete_tree_item(Vcb, &tp2); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } else if (tp2.item->key.obj_type == TYPE_SHARED_BLOCK_REF) { metadata_reloc_ref* ref = ExAllocatePoolWithTag(PagedPool, sizeof(metadata_reloc_ref), ALLOC_TAG); if (!ref) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ref->type = TYPE_SHARED_BLOCK_REF; ref->sbr.offset = tp2.item->key.offset; ref->parent = NULL; ref->top = false; InsertTailList(&mr->refs, &ref->list_entry); Status = delete_tree_item(Vcb, &tp2); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } } else break; } } InsertTailList(items, &mr->list_entry); if (mr2) *mr2 = mr; return STATUS_SUCCESS; } static NTSTATUS add_metadata_reloc_parent(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, LIST_ENTRY* items, uint64_t address, metadata_reloc** mr2, LIST_ENTRY* rollback) { LIST_ENTRY* le; KEY searchkey; traverse_ptr tp; bool skinny = false; NTSTATUS Status; le = items->Flink; while (le != items) { metadata_reloc* mr = CONTAINING_RECORD(le, metadata_reloc, list_entry); if (mr->address == address) { *mr2 = mr; return STATUS_SUCCESS; } le = le->Flink; } searchkey.obj_id = address; searchkey.obj_type = TYPE_METADATA_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id == address && tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) skinny = true; else if (tp.item->key.obj_id == address && tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->key.offset == Vcb->superblock.node_size && tp.item->size >= sizeof(EXTENT_ITEM)) { EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data; if (!(ei->flags & EXTENT_ITEM_TREE_BLOCK)) { ERR("EXTENT_ITEM for %I64x found, but tree flag not set\n", address); return STATUS_INTERNAL_ERROR; } } else { ERR("could not find valid EXTENT_ITEM for address %I64x\n", address); return STATUS_INTERNAL_ERROR; } Status = add_metadata_reloc(Vcb, items, &tp, skinny, mr2, NULL, rollback); if (!NT_SUCCESS(Status)) { ERR("add_metadata_reloc returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } static void sort_metadata_reloc_refs(metadata_reloc* mr) { LIST_ENTRY newlist, *le; if (mr->refs.Flink == mr->refs.Blink) // 0 or 1 items return; // insertion sort InitializeListHead(&newlist); while (!IsListEmpty(&mr->refs)) { metadata_reloc_ref* ref = CONTAINING_RECORD(RemoveHeadList(&mr->refs), metadata_reloc_ref, list_entry); bool inserted = false; if (ref->type == TYPE_TREE_BLOCK_REF) ref->hash = ref->tbr.offset; else if (ref->type == TYPE_SHARED_BLOCK_REF) ref->hash = ref->parent->new_address; le = newlist.Flink; while (le != &newlist) { metadata_reloc_ref* ref2 = CONTAINING_RECORD(le, metadata_reloc_ref, list_entry); if (ref->type < ref2->type || (ref->type == ref2->type && ref->hash > ref2->hash)) { InsertHeadList(le->Blink, &ref->list_entry); inserted = true; break; } le = le->Flink; } if (!inserted) InsertTailList(&newlist, &ref->list_entry); } newlist.Flink->Blink = &mr->refs; newlist.Blink->Flink = &mr->refs; mr->refs.Flink = newlist.Flink; mr->refs.Blink = newlist.Blink; } static NTSTATUS add_metadata_reloc_extent_item(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, metadata_reloc* mr) { NTSTATUS Status; LIST_ENTRY* le; uint64_t rc = 0; uint16_t inline_len; bool all_inline = true; metadata_reloc_ref* first_noninline = NULL; EXTENT_ITEM* ei; uint8_t* ptr; inline_len = sizeof(EXTENT_ITEM); if (!(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) inline_len += sizeof(EXTENT_ITEM2); sort_metadata_reloc_refs(mr); le = mr->refs.Flink; while (le != &mr->refs) { metadata_reloc_ref* ref = CONTAINING_RECORD(le, metadata_reloc_ref, list_entry); uint16_t extlen = 0; rc++; if (ref->type == TYPE_TREE_BLOCK_REF) extlen += sizeof(TREE_BLOCK_REF); else if (ref->type == TYPE_SHARED_BLOCK_REF) extlen += sizeof(SHARED_BLOCK_REF); if (all_inline) { if ((ULONG)(inline_len + 1 + extlen) > (Vcb->superblock.node_size >> 2)) { all_inline = false; first_noninline = ref; } else inline_len += extlen + 1; } le = le->Flink; } ei = ExAllocatePoolWithTag(PagedPool, inline_len, ALLOC_TAG); if (!ei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ei->refcount = rc; ei->generation = mr->ei->generation; ei->flags = mr->ei->flags; ptr = (uint8_t*)&ei[1]; if (!(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) { EXTENT_ITEM2* ei2 = (EXTENT_ITEM2*)ptr; ei2->firstitem = *(KEY*)&mr->data[1]; ei2->level = mr->data->level; ptr += sizeof(EXTENT_ITEM2); } le = mr->refs.Flink; while (le != &mr->refs) { metadata_reloc_ref* ref = CONTAINING_RECORD(le, metadata_reloc_ref, list_entry); if (ref == first_noninline) break; *ptr = ref->type; ptr++; if (ref->type == TYPE_TREE_BLOCK_REF) { TREE_BLOCK_REF* tbr = (TREE_BLOCK_REF*)ptr; tbr->offset = ref->tbr.offset; ptr += sizeof(TREE_BLOCK_REF); } else if (ref->type == TYPE_SHARED_BLOCK_REF) { SHARED_BLOCK_REF* sbr = (SHARED_BLOCK_REF*)ptr; sbr->offset = ref->parent->new_address; ptr += sizeof(SHARED_BLOCK_REF); } le = le->Flink; } if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA) Status = insert_tree_item(Vcb, Vcb->extent_root, mr->new_address, TYPE_METADATA_ITEM, mr->data->level, ei, inline_len, NULL, NULL); else Status = insert_tree_item(Vcb, Vcb->extent_root, mr->new_address, TYPE_EXTENT_ITEM, Vcb->superblock.node_size, ei, inline_len, NULL, NULL); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(ei); return Status; } if (!all_inline) { le = &first_noninline->list_entry; while (le != &mr->refs) { metadata_reloc_ref* ref = CONTAINING_RECORD(le, metadata_reloc_ref, list_entry); if (ref->type == TYPE_TREE_BLOCK_REF) { Status = insert_tree_item(Vcb, Vcb->extent_root, mr->new_address, TYPE_TREE_BLOCK_REF, ref->tbr.offset, NULL, 0, NULL, NULL); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } } else if (ref->type == TYPE_SHARED_BLOCK_REF) { Status = insert_tree_item(Vcb, Vcb->extent_root, mr->new_address, TYPE_SHARED_BLOCK_REF, ref->parent->new_address, NULL, 0, NULL, NULL); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } } le = le->Flink; } } if (ei->flags & EXTENT_ITEM_SHARED_BACKREFS || mr->data->flags & HEADER_FLAG_SHARED_BACKREF || !(mr->data->flags & HEADER_FLAG_MIXED_BACKREF)) { if (mr->data->level > 0) { uint16_t i; internal_node* in = (internal_node*)&mr->data[1]; for (i = 0; i < mr->data->num_items; i++) { uint64_t sbrrc = find_extent_shared_tree_refcount(Vcb, in[i].address, mr->address, NULL); if (sbrrc > 0) { SHARED_BLOCK_REF sbr; sbr.offset = mr->new_address; Status = increase_extent_refcount(Vcb, in[i].address, Vcb->superblock.node_size, TYPE_SHARED_BLOCK_REF, &sbr, NULL, 0, NULL); if (!NT_SUCCESS(Status)) { ERR("increase_extent_refcount returned %08lx\n", Status); return Status; } sbr.offset = mr->address; Status = decrease_extent_refcount(Vcb, in[i].address, Vcb->superblock.node_size, TYPE_SHARED_BLOCK_REF, &sbr, NULL, 0, sbr.offset, false, NULL); if (!NT_SUCCESS(Status)) { ERR("decrease_extent_refcount returned %08lx\n", Status); return Status; } } } } else { uint16_t i; leaf_node* ln = (leaf_node*)&mr->data[1]; for (i = 0; i < mr->data->num_items; i++) { if (ln[i].key.obj_type == TYPE_EXTENT_DATA && ln[i].size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) { EXTENT_DATA* ed = (EXTENT_DATA*)((uint8_t*)mr->data + sizeof(tree_header) + ln[i].offset); if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; if (ed2->size > 0) { // not sparse uint32_t sdrrc = find_extent_shared_data_refcount(Vcb, ed2->address, mr->address, NULL); if (sdrrc > 0) { SHARED_DATA_REF sdr; chunk* c; sdr.offset = mr->new_address; sdr.count = sdrrc; Status = increase_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_SHARED_DATA_REF, &sdr, NULL, 0, NULL); if (!NT_SUCCESS(Status)) { ERR("increase_extent_refcount returned %08lx\n", Status); return Status; } sdr.offset = mr->address; Status = decrease_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_SHARED_DATA_REF, &sdr, NULL, 0, sdr.offset, false, NULL); if (!NT_SUCCESS(Status)) { ERR("decrease_extent_refcount returned %08lx\n", Status); return Status; } c = get_chunk_from_address(Vcb, ed2->address); if (c) { // check changed_extents ExAcquireResourceExclusiveLite(&c->changed_extents_lock, true); le = c->changed_extents.Flink; while (le != &c->changed_extents) { changed_extent* ce = CONTAINING_RECORD(le, changed_extent, list_entry); if (ce->address == ed2->address) { LIST_ENTRY* le2; le2 = ce->refs.Flink; while (le2 != &ce->refs) { changed_extent_ref* cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry); if (cer->type == TYPE_SHARED_DATA_REF && cer->sdr.offset == mr->address) { cer->sdr.offset = mr->new_address; break; } le2 = le2->Flink; } le2 = ce->old_refs.Flink; while (le2 != &ce->old_refs) { changed_extent_ref* cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry); if (cer->type == TYPE_SHARED_DATA_REF && cer->sdr.offset == mr->address) { cer->sdr.offset = mr->new_address; break; } le2 = le2->Flink; } break; } le = le->Flink; } ExReleaseResourceLite(&c->changed_extents_lock); } } } } } } } } return STATUS_SUCCESS; } static NTSTATUS write_metadata_items(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, LIST_ENTRY* items, LIST_ENTRY* data_items, chunk* c, LIST_ENTRY* rollback) { LIST_ENTRY tree_writes, *le; NTSTATUS Status; traverse_ptr tp; uint8_t level, max_level = 0; chunk* newchunk = NULL; InitializeListHead(&tree_writes); le = items->Flink; while (le != items) { metadata_reloc* mr = CONTAINING_RECORD(le, metadata_reloc, list_entry); LIST_ENTRY* le2; chunk* pc; mr->data = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.node_size, ALLOC_TAG); if (!mr->data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } Status = read_data(Vcb, mr->address, Vcb->superblock.node_size, NULL, true, (uint8_t*)mr->data, c && mr->address >= c->offset && mr->address < c->offset + c->chunk_item->size ? c : NULL, &pc, NULL, 0, false, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned %08lx\n", Status); return Status; } if (pc->chunk_item->type & BLOCK_FLAG_SYSTEM) mr->system = true; if (data_items && mr->data->level == 0) { le2 = data_items->Flink; while (le2 != data_items) { data_reloc* dr = CONTAINING_RECORD(le2, data_reloc, list_entry); leaf_node* ln = (leaf_node*)&mr->data[1]; uint16_t i; for (i = 0; i < mr->data->num_items; i++) { if (ln[i].key.obj_type == TYPE_EXTENT_DATA && ln[i].size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) { EXTENT_DATA* ed = (EXTENT_DATA*)((uint8_t*)mr->data + sizeof(tree_header) + ln[i].offset); if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; if (ed2->address == dr->address) ed2->address = dr->new_address; } } } le2 = le2->Flink; } } if (mr->data->level > max_level) max_level = mr->data->level; le2 = mr->refs.Flink; while (le2 != &mr->refs) { metadata_reloc_ref* ref = CONTAINING_RECORD(le2, metadata_reloc_ref, list_entry); if (ref->type == TYPE_TREE_BLOCK_REF) { KEY* firstitem; root* r = NULL; LIST_ENTRY* le3; tree* t; firstitem = (KEY*)&mr->data[1]; le3 = Vcb->roots.Flink; while (le3 != &Vcb->roots) { root* r2 = CONTAINING_RECORD(le3, root, list_entry); if (r2->id == ref->tbr.offset) { r = r2; break; } le3 = le3->Flink; } if (!r) { ERR("could not find subvol with id %I64x\n", ref->tbr.offset); return STATUS_INTERNAL_ERROR; } Status = find_item_to_level(Vcb, r, &tp, firstitem, false, mr->data->level + 1, NULL); if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) { ERR("find_item_to_level returned %08lx\n", Status); return Status; } t = tp.tree; while (t && t->header.level < mr->data->level + 1) { t = t->parent; } if (!t) ref->top = true; else { metadata_reloc* mr2; Status = add_metadata_reloc_parent(Vcb, items, t->header.address, &mr2, rollback); if (!NT_SUCCESS(Status)) { ERR("add_metadata_reloc_parent returned %08lx\n", Status); return Status; } ref->parent = mr2; } } else if (ref->type == TYPE_SHARED_BLOCK_REF) { metadata_reloc* mr2; Status = add_metadata_reloc_parent(Vcb, items, ref->sbr.offset, &mr2, rollback); if (!NT_SUCCESS(Status)) { ERR("add_metadata_reloc_parent returned %08lx\n", Status); return Status; } ref->parent = mr2; } le2 = le2->Flink; } le = le->Flink; } le = items->Flink; while (le != items) { metadata_reloc* mr = CONTAINING_RECORD(le, metadata_reloc, list_entry); LIST_ENTRY* le2; uint32_t hash; mr->t = NULL; hash = calc_crc32c(0xffffffff, (uint8_t*)&mr->address, sizeof(uint64_t)); le2 = Vcb->trees_ptrs[hash >> 24]; if (le2) { while (le2 != &Vcb->trees_hash) { tree* t = CONTAINING_RECORD(le2, tree, list_entry_hash); if (t->header.address == mr->address) { mr->t = t; break; } else if (t->hash > hash) break; le2 = le2->Flink; } } le = le->Flink; } for (level = 0; level <= max_level; level++) { le = items->Flink; while (le != items) { metadata_reloc* mr = CONTAINING_RECORD(le, metadata_reloc, list_entry); if (mr->data->level == level) { bool done = false; LIST_ENTRY* le2; tree_write* tw; uint64_t flags; tree* t3; if (mr->system) flags = Vcb->system_flags; else if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS) flags = Vcb->data_flags; else flags = Vcb->metadata_flags; if (newchunk) { acquire_chunk_lock(newchunk, Vcb); if (newchunk->chunk_item->type == flags && find_metadata_address_in_chunk(Vcb, newchunk, &mr->new_address)) { newchunk->used += Vcb->superblock.node_size; space_list_subtract(newchunk, mr->new_address, Vcb->superblock.node_size, rollback); done = true; } release_chunk_lock(newchunk, Vcb); } if (!done) { ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true); le2 = Vcb->chunks.Flink; while (le2 != &Vcb->chunks) { chunk* c2 = CONTAINING_RECORD(le2, chunk, list_entry); if (!c2->readonly && !c2->reloc && c2 != newchunk && c2->chunk_item->type == flags) { acquire_chunk_lock(c2, Vcb); if ((c2->chunk_item->size - c2->used) >= Vcb->superblock.node_size) { if (find_metadata_address_in_chunk(Vcb, c2, &mr->new_address)) { c2->used += Vcb->superblock.node_size; space_list_subtract(c2, mr->new_address, Vcb->superblock.node_size, rollback); release_chunk_lock(c2, Vcb); newchunk = c2; done = true; break; } } release_chunk_lock(c2, Vcb); } le2 = le2->Flink; } // allocate new chunk if necessary if (!done) { Status = alloc_chunk(Vcb, flags, &newchunk, false); if (!NT_SUCCESS(Status)) { ERR("alloc_chunk returned %08lx\n", Status); ExReleaseResourceLite(&Vcb->chunk_lock); goto end; } acquire_chunk_lock(newchunk, Vcb); newchunk->balance_num = Vcb->balance.balance_num; if (!find_metadata_address_in_chunk(Vcb, newchunk, &mr->new_address)) { release_chunk_lock(newchunk, Vcb); ExReleaseResourceLite(&Vcb->chunk_lock); ERR("could not find address in new chunk\n"); Status = STATUS_DISK_FULL; goto end; } else { newchunk->used += Vcb->superblock.node_size; space_list_subtract(newchunk, mr->new_address, Vcb->superblock.node_size, rollback); } release_chunk_lock(newchunk, Vcb); } ExReleaseResourceLite(&Vcb->chunk_lock); } // update parents le2 = mr->refs.Flink; while (le2 != &mr->refs) { metadata_reloc_ref* ref = CONTAINING_RECORD(le2, metadata_reloc_ref, list_entry); if (ref->parent) { uint16_t i; internal_node* in = (internal_node*)&ref->parent->data[1]; for (i = 0; i < ref->parent->data->num_items; i++) { if (in[i].address == mr->address) { in[i].address = mr->new_address; break; } } if (ref->parent->t) { LIST_ENTRY* le3; le3 = ref->parent->t->itemlist.Flink; while (le3 != &ref->parent->t->itemlist) { tree_data* td = CONTAINING_RECORD(le3, tree_data, list_entry); if (!td->inserted && td->treeholder.address == mr->address) td->treeholder.address = mr->new_address; le3 = le3->Flink; } } } else if (ref->top && ref->type == TYPE_TREE_BLOCK_REF) { LIST_ENTRY* le3; root* r = NULL; // alter ROOT_ITEM le3 = Vcb->roots.Flink; while (le3 != &Vcb->roots) { root* r2 = CONTAINING_RECORD(le3, root, list_entry); if (r2->id == ref->tbr.offset) { r = r2; break; } le3 = le3->Flink; } if (r) { r->treeholder.address = mr->new_address; if (r == Vcb->root_root) Vcb->superblock.root_tree_addr = mr->new_address; else if (r == Vcb->chunk_root) Vcb->superblock.chunk_tree_addr = mr->new_address; else if (r->root_item.block_number == mr->address) { KEY searchkey; ROOT_ITEM* ri; r->root_item.block_number = mr->new_address; searchkey.obj_id = r->id; searchkey.obj_type = TYPE_ROOT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); goto end; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { ERR("could not find ROOT_ITEM for tree %I64x\n", searchkey.obj_id); Status = STATUS_INTERNAL_ERROR; goto end; } ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG); if (!ri) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(ri, &r->root_item, sizeof(ROOT_ITEM)); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); goto end; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); goto end; } } } } le2 = le2->Flink; } mr->data->address = mr->new_address; t3 = mr->t; while (t3) { uint8_t h; bool inserted; tree* t4 = NULL; // check if tree loaded more than once if (t3->list_entry.Flink != &Vcb->trees_hash) { tree* nt = CONTAINING_RECORD(t3->list_entry_hash.Flink, tree, list_entry_hash); if (nt->header.address == t3->header.address) t4 = nt; } t3->header.address = mr->new_address; h = t3->hash >> 24; if (Vcb->trees_ptrs[h] == &t3->list_entry_hash) { if (t3->list_entry_hash.Flink == &Vcb->trees_hash) Vcb->trees_ptrs[h] = NULL; else { tree* t2 = CONTAINING_RECORD(t3->list_entry_hash.Flink, tree, list_entry_hash); if (t2->hash >> 24 == h) Vcb->trees_ptrs[h] = &t2->list_entry_hash; else Vcb->trees_ptrs[h] = NULL; } } RemoveEntryList(&t3->list_entry_hash); t3->hash = calc_crc32c(0xffffffff, (uint8_t*)&t3->header.address, sizeof(uint64_t)); h = t3->hash >> 24; if (!Vcb->trees_ptrs[h]) { uint8_t h2 = h; le2 = Vcb->trees_hash.Flink; if (h2 > 0) { h2--; do { if (Vcb->trees_ptrs[h2]) { le2 = Vcb->trees_ptrs[h2]; break; } h2--; } while (h2 > 0); } } else le2 = Vcb->trees_ptrs[h]; inserted = false; while (le2 != &Vcb->trees_hash) { tree* t2 = CONTAINING_RECORD(le2, tree, list_entry_hash); if (t2->hash >= t3->hash) { InsertHeadList(le2->Blink, &t3->list_entry_hash); inserted = true; break; } le2 = le2->Flink; } if (!inserted) InsertTailList(&Vcb->trees_hash, &t3->list_entry_hash); if (!Vcb->trees_ptrs[h] || t3->list_entry_hash.Flink == Vcb->trees_ptrs[h]) Vcb->trees_ptrs[h] = &t3->list_entry_hash; if (data_items && level == 0) { le2 = data_items->Flink; while (le2 != data_items) { data_reloc* dr = CONTAINING_RECORD(le2, data_reloc, list_entry); LIST_ENTRY* le3 = t3->itemlist.Flink; while (le3 != &t3->itemlist) { tree_data* td = CONTAINING_RECORD(le3, tree_data, list_entry); if (!td->inserted && td->key.obj_type == TYPE_EXTENT_DATA && td->size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) { EXTENT_DATA* ed = (EXTENT_DATA*)td->data; if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; if (ed2->address == dr->address) ed2->address = dr->new_address; } } le3 = le3->Flink; } le2 = le2->Flink; } } t3 = t4; } calc_tree_checksum(Vcb, mr->data); tw = ExAllocatePoolWithTag(PagedPool, sizeof(tree_write), ALLOC_TAG); if (!tw) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } tw->address = mr->new_address; tw->length = Vcb->superblock.node_size; tw->data = (uint8_t*)mr->data; tw->allocated = false; if (IsListEmpty(&tree_writes)) InsertTailList(&tree_writes, &tw->list_entry); else { bool inserted = false; le2 = tree_writes.Flink; while (le2 != &tree_writes) { tree_write* tw2 = CONTAINING_RECORD(le2, tree_write, list_entry); if (tw2->address > tw->address) { InsertHeadList(le2->Blink, &tw->list_entry); inserted = true; break; } le2 = le2->Flink; } if (!inserted) InsertTailList(&tree_writes, &tw->list_entry); } } le = le->Flink; } } Status = do_tree_writes(Vcb, &tree_writes, true); if (!NT_SUCCESS(Status)) { ERR("do_tree_writes returned %08lx\n", Status); goto end; } le = items->Flink; while (le != items) { metadata_reloc* mr = CONTAINING_RECORD(le, metadata_reloc, list_entry); Status = add_metadata_reloc_extent_item(Vcb, mr); if (!NT_SUCCESS(Status)) { ERR("add_metadata_reloc_extent_item returned %08lx\n", Status); goto end; } le = le->Flink; } Status = STATUS_SUCCESS; end: while (!IsListEmpty(&tree_writes)) { tree_write* tw = CONTAINING_RECORD(RemoveHeadList(&tree_writes), tree_write, list_entry); if (tw->allocated) ExFreePool(tw->data); ExFreePool(tw); } return Status; } static NTSTATUS balance_metadata_chunk(device_extension* Vcb, chunk* c, bool* changed) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; bool b; LIST_ENTRY items, rollback; uint32_t loaded = 0; TRACE("chunk %I64x\n", c->offset); InitializeListHead(&rollback); InitializeListHead(&items); ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); searchkey.obj_id = c->offset; searchkey.obj_type = TYPE_METADATA_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); goto end; } do { traverse_ptr next_tp; if (tp.item->key.obj_id >= c->offset + c->chunk_item->size) break; if (tp.item->key.obj_id >= c->offset && (tp.item->key.obj_type == TYPE_EXTENT_ITEM || tp.item->key.obj_type == TYPE_METADATA_ITEM)) { bool tree = false, skinny = false; if (tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) { tree = true; skinny = true; } else if (tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->key.offset == Vcb->superblock.node_size && tp.item->size >= sizeof(EXTENT_ITEM)) { EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data; if (ei->flags & EXTENT_ITEM_TREE_BLOCK) tree = true; } if (tree) { Status = add_metadata_reloc(Vcb, &items, &tp, skinny, NULL, c, &rollback); if (!NT_SUCCESS(Status)) { ERR("add_metadata_reloc returned %08lx\n", Status); goto end; } loaded++; if (loaded >= 64) // only do 64 at a time break; } } b = find_next_item(Vcb, &tp, &next_tp, false, NULL); if (b) tp = next_tp; } while (b); if (IsListEmpty(&items)) { *changed = false; Status = STATUS_SUCCESS; goto end; } else *changed = true; Status = write_metadata_items(Vcb, &items, NULL, c, &rollback); if (!NT_SUCCESS(Status)) { ERR("write_metadata_items returned %08lx\n", Status); goto end; } Status = STATUS_SUCCESS; Vcb->need_write = true; end: if (NT_SUCCESS(Status)) { Status = do_write(Vcb, NULL); if (!NT_SUCCESS(Status)) ERR("do_write returned %08lx\n", Status); } if (NT_SUCCESS(Status)) clear_rollback(&rollback); else do_rollback(Vcb, &rollback); free_trees(Vcb); ExReleaseResourceLite(&Vcb->tree_lock); while (!IsListEmpty(&items)) { metadata_reloc* mr = CONTAINING_RECORD(RemoveHeadList(&items), metadata_reloc, list_entry); while (!IsListEmpty(&mr->refs)) { metadata_reloc_ref* ref = CONTAINING_RECORD(RemoveHeadList(&mr->refs), metadata_reloc_ref, list_entry); ExFreePool(ref); } if (mr->data) ExFreePool(mr->data); ExFreePool(mr); } return Status; } static NTSTATUS data_reloc_add_tree_edr(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, LIST_ENTRY* metadata_items, data_reloc* dr, EXTENT_DATA_REF* edr, LIST_ENTRY* rollback) { NTSTATUS Status; LIST_ENTRY* le; KEY searchkey; traverse_ptr tp; root* r = NULL; metadata_reloc* mr; uint64_t last_tree = 0; data_reloc_ref* ref; le = Vcb->roots.Flink; while (le != &Vcb->roots) { root* r2 = CONTAINING_RECORD(le, root, list_entry); if (r2->id == edr->root) { r = r2; break; } le = le->Flink; } if (!r) { ERR("could not find subvol %I64x\n", edr->root); return STATUS_INTERNAL_ERROR; } searchkey.obj_id = edr->objid; searchkey.obj_type = TYPE_EXTENT_DATA; searchkey.offset = 0; Status = find_item(Vcb, r, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } 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)) { traverse_ptr tp2; if (find_next_item(Vcb, &tp, &tp2, false, NULL)) tp = tp2; else { ERR("could not find EXTENT_DATA for inode %I64x in root %I64x\n", searchkey.obj_id, r->id); return STATUS_INTERNAL_ERROR; } } ref = NULL; while (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { traverse_ptr tp2; if (tp.item->size >= sizeof(EXTENT_DATA)) { EXTENT_DATA* ed = (EXTENT_DATA*)tp.item->data; if ((ed->type == EXTENT_TYPE_PREALLOC || ed->type == EXTENT_TYPE_REGULAR) && tp.item->size >= offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; if (ed2->address == dr->address && ed2->size == dr->size && tp.item->key.offset - ed2->offset == edr->offset) { if (ref && last_tree == tp.tree->header.address) ref->edr.count++; else { ref = ExAllocatePoolWithTag(PagedPool, sizeof(data_reloc_ref), ALLOC_TAG); if (!ref) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ref->type = TYPE_EXTENT_DATA_REF; RtlCopyMemory(&ref->edr, edr, sizeof(EXTENT_DATA_REF)); ref->edr.count = 1; Status = add_metadata_reloc_parent(Vcb, metadata_items, tp.tree->header.address, &mr, rollback); if (!NT_SUCCESS(Status)) { ERR("add_metadata_reloc_parent returned %08lx\n", Status); ExFreePool(ref); return Status; } last_tree = tp.tree->header.address; ref->parent = mr; InsertTailList(&dr->refs, &ref->list_entry); } } } } if (find_next_item(Vcb, &tp, &tp2, false, NULL)) tp = tp2; else break; } return STATUS_SUCCESS; } static NTSTATUS add_data_reloc(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, LIST_ENTRY* items, LIST_ENTRY* metadata_items, traverse_ptr* tp, chunk* c, LIST_ENTRY* rollback) { NTSTATUS Status; data_reloc* dr; EXTENT_ITEM* ei; uint16_t len; uint64_t inline_rc; uint8_t* ptr; dr = ExAllocatePoolWithTag(PagedPool, sizeof(data_reloc), ALLOC_TAG); if (!dr) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } dr->address = tp->item->key.obj_id; dr->size = tp->item->key.offset; dr->ei = (EXTENT_ITEM*)tp->item->data; InitializeListHead(&dr->refs); Status = delete_tree_item(Vcb, tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } if (!c) c = get_chunk_from_address(Vcb, tp->item->key.obj_id); if (c) { acquire_chunk_lock(c, Vcb); c->used -= tp->item->key.offset; space_list_add(c, tp->item->key.obj_id, tp->item->key.offset, rollback); release_chunk_lock(c, Vcb); } ei = (EXTENT_ITEM*)tp->item->data; inline_rc = 0; len = tp->item->size - sizeof(EXTENT_ITEM); ptr = (uint8_t*)tp->item->data + sizeof(EXTENT_ITEM); while (len > 0) { uint8_t secttype = *ptr; uint16_t sectlen = secttype == TYPE_EXTENT_DATA_REF ? sizeof(EXTENT_DATA_REF) : (secttype == TYPE_SHARED_DATA_REF ? sizeof(SHARED_DATA_REF) : 0); len--; if (sectlen > len) { 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); return STATUS_INTERNAL_ERROR; } if (sectlen == 0) { ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, secttype); return STATUS_INTERNAL_ERROR; } if (secttype == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t)); inline_rc += edr->count; Status = data_reloc_add_tree_edr(Vcb, metadata_items, dr, edr, rollback); if (!NT_SUCCESS(Status)) { ERR("data_reloc_add_tree_edr returned %08lx\n", Status); return Status; } } else if (secttype == TYPE_SHARED_DATA_REF) { metadata_reloc* mr; data_reloc_ref* ref; ref = ExAllocatePoolWithTag(PagedPool, sizeof(data_reloc_ref), ALLOC_TAG); if (!ref) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ref->type = TYPE_SHARED_DATA_REF; RtlCopyMemory(&ref->sdr, ptr + sizeof(uint8_t), sizeof(SHARED_DATA_REF)); inline_rc += ref->sdr.count; Status = add_metadata_reloc_parent(Vcb, metadata_items, ref->sdr.offset, &mr, rollback); if (!NT_SUCCESS(Status)) { ERR("add_metadata_reloc_parent returned %08lx\n", Status); ExFreePool(ref); return Status; } ref->parent = mr; InsertTailList(&dr->refs, &ref->list_entry); } else { ERR("unexpected tree type %x\n", secttype); return STATUS_INTERNAL_ERROR; } len -= sectlen; ptr += sizeof(uint8_t) + sectlen; } if (inline_rc < ei->refcount) { // look for non-inline entries traverse_ptr tp2 = *tp, next_tp; while (find_next_item(Vcb, &tp2, &next_tp, false, NULL)) { tp2 = next_tp; if (tp2.item->key.obj_id == tp->item->key.obj_id) { if (tp2.item->key.obj_type == TYPE_EXTENT_DATA_REF && tp2.item->size >= sizeof(EXTENT_DATA_REF)) { Status = data_reloc_add_tree_edr(Vcb, metadata_items, dr, (EXTENT_DATA_REF*)tp2.item->data, rollback); if (!NT_SUCCESS(Status)) { ERR("data_reloc_add_tree_edr returned %08lx\n", Status); return Status; } Status = delete_tree_item(Vcb, &tp2); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } else if (tp2.item->key.obj_type == TYPE_SHARED_DATA_REF && tp2.item->size >= sizeof(uint32_t)) { metadata_reloc* mr; data_reloc_ref* ref; ref = ExAllocatePoolWithTag(PagedPool, sizeof(data_reloc_ref), ALLOC_TAG); if (!ref) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ref->type = TYPE_SHARED_DATA_REF; ref->sdr.offset = tp2.item->key.offset; ref->sdr.count = *((uint32_t*)tp2.item->data); Status = add_metadata_reloc_parent(Vcb, metadata_items, ref->sdr.offset, &mr, rollback); if (!NT_SUCCESS(Status)) { ERR("add_metadata_reloc_parent returned %08lx\n", Status); ExFreePool(ref); return Status; } ref->parent = mr; InsertTailList(&dr->refs, &ref->list_entry); Status = delete_tree_item(Vcb, &tp2); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } } else break; } } InsertTailList(items, &dr->list_entry); return STATUS_SUCCESS; } static void sort_data_reloc_refs(data_reloc* dr) { LIST_ENTRY newlist, *le; if (IsListEmpty(&dr->refs)) return; // insertion sort InitializeListHead(&newlist); while (!IsListEmpty(&dr->refs)) { data_reloc_ref* ref = CONTAINING_RECORD(RemoveHeadList(&dr->refs), data_reloc_ref, list_entry); bool inserted = false; if (ref->type == TYPE_EXTENT_DATA_REF) ref->hash = get_extent_data_ref_hash2(ref->edr.root, ref->edr.objid, ref->edr.offset); else if (ref->type == TYPE_SHARED_DATA_REF) ref->hash = ref->parent->new_address; le = newlist.Flink; while (le != &newlist) { data_reloc_ref* ref2 = CONTAINING_RECORD(le, data_reloc_ref, list_entry); if (ref->type < ref2->type || (ref->type == ref2->type && ref->hash > ref2->hash)) { InsertHeadList(le->Blink, &ref->list_entry); inserted = true; break; } le = le->Flink; } if (!inserted) InsertTailList(&newlist, &ref->list_entry); } le = newlist.Flink; while (le != &newlist) { data_reloc_ref* ref = CONTAINING_RECORD(le, data_reloc_ref, list_entry); if (le->Flink != &newlist) { data_reloc_ref* ref2 = CONTAINING_RECORD(le->Flink, data_reloc_ref, list_entry); if (ref->type == TYPE_EXTENT_DATA_REF && ref2->type == TYPE_EXTENT_DATA_REF && ref->edr.root == ref2->edr.root && ref->edr.objid == ref2->edr.objid && ref->edr.offset == ref2->edr.offset) { RemoveEntryList(&ref2->list_entry); ref->edr.count += ref2->edr.count; ExFreePool(ref2); continue; } } le = le->Flink; } newlist.Flink->Blink = &dr->refs; newlist.Blink->Flink = &dr->refs; dr->refs.Flink = newlist.Flink; dr->refs.Blink = newlist.Blink; } static NTSTATUS add_data_reloc_extent_item(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, data_reloc* dr) { NTSTATUS Status; LIST_ENTRY* le; uint64_t rc = 0; uint16_t inline_len; bool all_inline = true; data_reloc_ref* first_noninline = NULL; EXTENT_ITEM* ei; uint8_t* ptr; inline_len = sizeof(EXTENT_ITEM); sort_data_reloc_refs(dr); le = dr->refs.Flink; while (le != &dr->refs) { data_reloc_ref* ref = CONTAINING_RECORD(le, data_reloc_ref, list_entry); uint16_t extlen = 0; if (ref->type == TYPE_EXTENT_DATA_REF) { extlen += sizeof(EXTENT_DATA_REF); rc += ref->edr.count; } else if (ref->type == TYPE_SHARED_DATA_REF) { extlen += sizeof(SHARED_DATA_REF); rc++; } if (all_inline) { if ((ULONG)(inline_len + 1 + extlen) > (Vcb->superblock.node_size >> 2)) { all_inline = false; first_noninline = ref; } else inline_len += extlen + 1; } le = le->Flink; } ei = ExAllocatePoolWithTag(PagedPool, inline_len, ALLOC_TAG); if (!ei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ei->refcount = rc; ei->generation = dr->ei->generation; ei->flags = dr->ei->flags; ptr = (uint8_t*)&ei[1]; le = dr->refs.Flink; while (le != &dr->refs) { data_reloc_ref* ref = CONTAINING_RECORD(le, data_reloc_ref, list_entry); if (ref == first_noninline) break; *ptr = ref->type; ptr++; if (ref->type == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)ptr; RtlCopyMemory(edr, &ref->edr, sizeof(EXTENT_DATA_REF)); ptr += sizeof(EXTENT_DATA_REF); } else if (ref->type == TYPE_SHARED_DATA_REF) { SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)ptr; sdr->offset = ref->parent->new_address; sdr->count = ref->sdr.count; ptr += sizeof(SHARED_DATA_REF); } le = le->Flink; } Status = insert_tree_item(Vcb, Vcb->extent_root, dr->new_address, TYPE_EXTENT_ITEM, dr->size, ei, inline_len, NULL, NULL); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } if (!all_inline) { le = &first_noninline->list_entry; while (le != &dr->refs) { data_reloc_ref* ref = CONTAINING_RECORD(le, data_reloc_ref, list_entry); if (ref->type == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* edr; edr = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA_REF), ALLOC_TAG); if (!edr) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(edr, &ref->edr, sizeof(EXTENT_DATA_REF)); Status = insert_tree_item(Vcb, Vcb->extent_root, dr->new_address, TYPE_EXTENT_DATA_REF, ref->hash, edr, sizeof(EXTENT_DATA_REF), NULL, NULL); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } } else if (ref->type == TYPE_SHARED_DATA_REF) { uint32_t* sdr; sdr = ExAllocatePoolWithTag(PagedPool, sizeof(uint32_t), ALLOC_TAG); if (!sdr) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } *sdr = ref->sdr.count; 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } } le = le->Flink; } } return STATUS_SUCCESS; } static NTSTATUS balance_data_chunk(device_extension* Vcb, chunk* c, bool* changed) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; bool b; LIST_ENTRY items, metadata_items, rollback, *le; uint64_t loaded = 0, num_loaded = 0; chunk* newchunk = NULL; uint8_t* data = NULL; TRACE("chunk %I64x\n", c->offset); InitializeListHead(&rollback); InitializeListHead(&items); InitializeListHead(&metadata_items); ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); searchkey.obj_id = c->offset; searchkey.obj_type = TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); goto end; } do { traverse_ptr next_tp; if (tp.item->key.obj_id >= c->offset + c->chunk_item->size) break; if (tp.item->key.obj_id >= c->offset && tp.item->key.obj_type == TYPE_EXTENT_ITEM) { bool tree = false; if (tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) { EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data; if (ei->flags & EXTENT_ITEM_TREE_BLOCK) tree = true; } if (!tree) { Status = add_data_reloc(Vcb, &items, &metadata_items, &tp, c, &rollback); if (!NT_SUCCESS(Status)) { ERR("add_data_reloc returned %08lx\n", Status); goto end; } loaded += tp.item->key.offset; num_loaded++; if (loaded >= 0x1000000 || num_loaded >= 100) // only do so much at a time, so we don't block too obnoxiously break; } } b = find_next_item(Vcb, &tp, &next_tp, false, NULL); if (b) tp = next_tp; } while (b); if (IsListEmpty(&items)) { *changed = false; Status = STATUS_SUCCESS; goto end; } else *changed = true; data = ExAllocatePoolWithTag(PagedPool, BALANCE_UNIT, ALLOC_TAG); if (!data) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } le = items.Flink; while (le != &items) { data_reloc* dr = CONTAINING_RECORD(le, data_reloc, list_entry); bool done = false; LIST_ENTRY* le2; void* csum; RTL_BITMAP bmp; ULONG* bmparr; ULONG bmplen, runlength, index, lastoff; if (newchunk) { acquire_chunk_lock(newchunk, Vcb); if (find_data_address_in_chunk(Vcb, newchunk, dr->size, &dr->new_address)) { newchunk->used += dr->size; space_list_subtract(newchunk, dr->new_address, dr->size, &rollback); done = true; } release_chunk_lock(newchunk, Vcb); } if (!done) { ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true); le2 = Vcb->chunks.Flink; while (le2 != &Vcb->chunks) { chunk* c2 = CONTAINING_RECORD(le2, chunk, list_entry); if (!c2->readonly && !c2->reloc && c2 != newchunk && c2->chunk_item->type == Vcb->data_flags) { acquire_chunk_lock(c2, Vcb); if ((c2->chunk_item->size - c2->used) >= dr->size) { if (find_data_address_in_chunk(Vcb, c2, dr->size, &dr->new_address)) { c2->used += dr->size; space_list_subtract(c2, dr->new_address, dr->size, &rollback); release_chunk_lock(c2, Vcb); newchunk = c2; done = true; break; } } release_chunk_lock(c2, Vcb); } le2 = le2->Flink; } // allocate new chunk if necessary if (!done) { Status = alloc_chunk(Vcb, Vcb->data_flags, &newchunk, false); if (!NT_SUCCESS(Status)) { ERR("alloc_chunk returned %08lx\n", Status); ExReleaseResourceLite(&Vcb->chunk_lock); goto end; } acquire_chunk_lock(newchunk, Vcb); newchunk->balance_num = Vcb->balance.balance_num; if (!find_data_address_in_chunk(Vcb, newchunk, dr->size, &dr->new_address)) { release_chunk_lock(newchunk, Vcb); ExReleaseResourceLite(&Vcb->chunk_lock); ERR("could not find address in new chunk\n"); Status = STATUS_DISK_FULL; goto end; } else { newchunk->used += dr->size; space_list_subtract(newchunk, dr->new_address, dr->size, &rollback); } release_chunk_lock(newchunk, Vcb); } ExReleaseResourceLite(&Vcb->chunk_lock); } dr->newchunk = newchunk; bmplen = (ULONG)(dr->size >> Vcb->sector_shift); bmparr = ExAllocatePoolWithTag(PagedPool, (ULONG)sector_align(bmplen + 1, sizeof(ULONG)), ALLOC_TAG); if (!bmparr) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((dr->size * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG); if (!csum) { ERR("out of memory\n"); ExFreePool(bmparr); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlInitializeBitMap(&bmp, bmparr, bmplen); RtlSetAllBits(&bmp); // 1 = no csum, 0 = csum searchkey.obj_id = EXTENT_CSUM_ID; searchkey.obj_type = TYPE_EXTENT_CSUM; searchkey.offset = dr->address; Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) { ERR("find_item returned %08lx\n", Status); ExFreePool(csum); ExFreePool(bmparr); goto end; } if (Status != STATUS_NOT_FOUND) { do { traverse_ptr next_tp; if (tp.item->key.obj_type == TYPE_EXTENT_CSUM) { if (tp.item->key.offset >= dr->address + dr->size) break; 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) { uint64_t cs = max(dr->address, tp.item->key.offset); uint64_t ce = min(dr->address + dr->size, tp.item->key.offset + (((unsigned int)tp.item->size << Vcb->sector_shift) / Vcb->csum_size)); RtlCopyMemory((uint8_t*)csum + (((cs - dr->address) * Vcb->csum_size) >> Vcb->sector_shift), tp.item->data + (((cs - tp.item->key.offset) * Vcb->csum_size) >> Vcb->sector_shift), (ULONG)(((ce - cs) * Vcb->csum_size) >> Vcb->sector_shift)); RtlClearBits(&bmp, (ULONG)((cs - dr->address) >> Vcb->sector_shift), (ULONG)((ce - cs) >> Vcb->sector_shift)); if (ce == dr->address + dr->size) break; } } if (find_next_item(Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else break; } while (true); } lastoff = 0; runlength = RtlFindFirstRunClear(&bmp, &index); while (runlength != 0) { if (index >= bmplen) break; if (index + runlength >= bmplen) { runlength = bmplen - index; if (runlength == 0) break; } if (index > lastoff) { ULONG off = lastoff; ULONG size = index - lastoff; // handle no csum run do { ULONG rl; if (size << Vcb->sector_shift > BALANCE_UNIT) rl = BALANCE_UNIT >> Vcb->sector_shift; else rl = size; Status = read_data(Vcb, dr->address + (off << Vcb->sector_shift), rl << Vcb->sector_shift, NULL, false, data, c, NULL, NULL, 0, false, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned %08lx\n", Status); ExFreePool(csum); ExFreePool(bmparr); goto end; } Status = write_data_complete(Vcb, dr->new_address + (off << Vcb->sector_shift), data, rl << Vcb->sector_shift, NULL, newchunk, false, 0, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("write_data_complete returned %08lx\n", Status); ExFreePool(csum); ExFreePool(bmparr); goto end; } size -= rl; off += rl; } while (size > 0); } add_checksum_entry(Vcb, dr->new_address + (index << Vcb->sector_shift), runlength, (uint8_t*)csum + (index * Vcb->csum_size), NULL); add_checksum_entry(Vcb, dr->address + (index << Vcb->sector_shift), runlength, NULL, NULL); // handle csum run do { ULONG rl; if (runlength << Vcb->sector_shift > BALANCE_UNIT) rl = BALANCE_UNIT >> Vcb->sector_shift; else rl = runlength; Status = read_data(Vcb, dr->address + (index << Vcb->sector_shift), rl << Vcb->sector_shift, (uint8_t*)csum + (index * Vcb->csum_size), false, data, c, NULL, NULL, 0, false, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned %08lx\n", Status); ExFreePool(csum); ExFreePool(bmparr); goto end; } Status = write_data_complete(Vcb, dr->new_address + (index << Vcb->sector_shift), data, rl << Vcb->sector_shift, NULL, newchunk, false, 0, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("write_data_complete returned %08lx\n", Status); ExFreePool(csum); ExFreePool(bmparr); goto end; } runlength -= rl; index += rl; } while (runlength > 0); lastoff = index; runlength = RtlFindNextForwardRunClear(&bmp, index, &index); } ExFreePool(csum); ExFreePool(bmparr); // handle final nocsum run if (lastoff < dr->size >> Vcb->sector_shift) { ULONG off = lastoff; ULONG size = (ULONG)((dr->size >> Vcb->sector_shift) - lastoff); do { ULONG rl; if (size << Vcb->sector_shift > BALANCE_UNIT) rl = BALANCE_UNIT >> Vcb->sector_shift; else rl = size; Status = read_data(Vcb, dr->address + (off << Vcb->sector_shift), rl << Vcb->sector_shift, NULL, false, data, c, NULL, NULL, 0, false, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned %08lx\n", Status); goto end; } Status = write_data_complete(Vcb, dr->new_address + (off << Vcb->sector_shift), data, rl << Vcb->sector_shift, NULL, newchunk, false, 0, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("write_data_complete returned %08lx\n", Status); goto end; } size -= rl; off += rl; } while (size > 0); } le = le->Flink; } ExFreePool(data); data = NULL; Status = write_metadata_items(Vcb, &metadata_items, &items, NULL, &rollback); if (!NT_SUCCESS(Status)) { ERR("write_metadata_items returned %08lx\n", Status); goto end; } le = items.Flink; while (le != &items) { data_reloc* dr = CONTAINING_RECORD(le, data_reloc, list_entry); Status = add_data_reloc_extent_item(Vcb, dr); if (!NT_SUCCESS(Status)) { ERR("add_data_reloc_extent_item returned %08lx\n", Status); goto end; } le = le->Flink; } le = c->changed_extents.Flink; while (le != &c->changed_extents) { LIST_ENTRY *le2, *le3; changed_extent* ce = CONTAINING_RECORD(le, changed_extent, list_entry); le3 = le->Flink; le2 = items.Flink; while (le2 != &items) { data_reloc* dr = CONTAINING_RECORD(le2, data_reloc, list_entry); if (ce->address == dr->address) { ce->address = dr->new_address; RemoveEntryList(&ce->list_entry); InsertTailList(&dr->newchunk->changed_extents, &ce->list_entry); break; } le2 = le2->Flink; } le = le3; } Status = STATUS_SUCCESS; Vcb->need_write = true; end: if (NT_SUCCESS(Status)) { // update extents in cache inodes before we flush le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c2 = CONTAINING_RECORD(le, chunk, list_entry); if (c2->cache) { LIST_ENTRY* le2; ExAcquireResourceExclusiveLite(c2->cache->Header.Resource, true); le2 = c2->cache->extents.Flink; while (le2 != &c2->cache->extents) { extent* ext = CONTAINING_RECORD(le2, extent, list_entry); if (!ext->ignore) { if (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data; if (ed2->size > 0 && ed2->address >= c->offset && ed2->address < c->offset + c->chunk_item->size) { LIST_ENTRY* le3 = items.Flink; while (le3 != &items) { data_reloc* dr = CONTAINING_RECORD(le3, data_reloc, list_entry); if (ed2->address == dr->address) { ed2->address = dr->new_address; break; } le3 = le3->Flink; } } } } le2 = le2->Flink; } ExReleaseResourceLite(c2->cache->Header.Resource); } le = le->Flink; } Status = do_write(Vcb, NULL); if (!NT_SUCCESS(Status)) ERR("do_write returned %08lx\n", Status); } if (NT_SUCCESS(Status)) { clear_rollback(&rollback); // update open FCBs // FIXME - speed this up(?) le = Vcb->all_fcbs.Flink; while (le != &Vcb->all_fcbs) { struct _fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry_all); LIST_ENTRY* le2; ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); le2 = fcb->extents.Flink; while (le2 != &fcb->extents) { extent* ext = CONTAINING_RECORD(le2, extent, list_entry); if (!ext->ignore) { if (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data; if (ed2->size > 0 && ed2->address >= c->offset && ed2->address < c->offset + c->chunk_item->size) { LIST_ENTRY* le3 = items.Flink; while (le3 != &items) { data_reloc* dr = CONTAINING_RECORD(le3, data_reloc, list_entry); if (ed2->address == dr->address) { ed2->address = dr->new_address; break; } le3 = le3->Flink; } } } } le2 = le2->Flink; } ExReleaseResourceLite(fcb->Header.Resource); le = le->Flink; } } else do_rollback(Vcb, &rollback); free_trees(Vcb); ExReleaseResourceLite(&Vcb->tree_lock); if (data) ExFreePool(data); while (!IsListEmpty(&items)) { data_reloc* dr = CONTAINING_RECORD(RemoveHeadList(&items), data_reloc, list_entry); while (!IsListEmpty(&dr->refs)) { data_reloc_ref* ref = CONTAINING_RECORD(RemoveHeadList(&dr->refs), data_reloc_ref, list_entry); ExFreePool(ref); } ExFreePool(dr); } while (!IsListEmpty(&metadata_items)) { metadata_reloc* mr = CONTAINING_RECORD(RemoveHeadList(&metadata_items), metadata_reloc, list_entry); while (!IsListEmpty(&mr->refs)) { metadata_reloc_ref* ref = CONTAINING_RECORD(RemoveHeadList(&mr->refs), metadata_reloc_ref, list_entry); ExFreePool(ref); } if (mr->data) ExFreePool(mr->data); ExFreePool(mr); } return Status; } static __inline uint64_t get_chunk_dup_type(chunk* c) { if (c->chunk_item->type & BLOCK_FLAG_RAID0) return BLOCK_FLAG_RAID0; else if (c->chunk_item->type & BLOCK_FLAG_RAID1) return BLOCK_FLAG_RAID1; else if (c->chunk_item->type & BLOCK_FLAG_DUPLICATE) return BLOCK_FLAG_DUPLICATE; else if (c->chunk_item->type & BLOCK_FLAG_RAID10) return BLOCK_FLAG_RAID10; else if (c->chunk_item->type & BLOCK_FLAG_RAID5) return BLOCK_FLAG_RAID5; else if (c->chunk_item->type & BLOCK_FLAG_RAID6) return BLOCK_FLAG_RAID6; else if (c->chunk_item->type & BLOCK_FLAG_RAID1C3) return BLOCK_FLAG_RAID1C3; else if (c->chunk_item->type & BLOCK_FLAG_RAID1C4) return BLOCK_FLAG_RAID1C4; else return BLOCK_FLAG_SINGLE; } static bool should_balance_chunk(device_extension* Vcb, uint8_t sort, chunk* c) { btrfs_balance_opts* opts; opts = &Vcb->balance.opts[sort]; if (!(opts->flags & BTRFS_BALANCE_OPTS_ENABLED)) return false; if (opts->flags & BTRFS_BALANCE_OPTS_PROFILES) { uint64_t type = get_chunk_dup_type(c); if (!(type & opts->profiles)) return false; } if (opts->flags & BTRFS_BALANCE_OPTS_DEVID) { uint16_t i; CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; bool b = false; for (i = 0; i < c->chunk_item->num_stripes; i++) { if (cis[i].dev_id == opts->devid) { b = true; break; } } if (!b) return false; } if (opts->flags & BTRFS_BALANCE_OPTS_DRANGE) { uint16_t i, factor; uint64_t physsize; CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; bool b = false; if (c->chunk_item->type & BLOCK_FLAG_RAID0) factor = c->chunk_item->num_stripes; else if (c->chunk_item->type & BLOCK_FLAG_RAID10) factor = c->chunk_item->num_stripes / c->chunk_item->sub_stripes; else if (c->chunk_item->type & BLOCK_FLAG_RAID5) factor = c->chunk_item->num_stripes - 1; else if (c->chunk_item->type & BLOCK_FLAG_RAID6) factor = c->chunk_item->num_stripes - 2; else // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4 factor = 1; physsize = c->chunk_item->size / factor; for (i = 0; i < c->chunk_item->num_stripes; i++) { if (cis[i].offset < opts->drange_end && cis[i].offset + physsize >= opts->drange_start && (!(opts->flags & BTRFS_BALANCE_OPTS_DEVID) || cis[i].dev_id == opts->devid)) { b = true; break; } } if (!b) return false; } if (opts->flags & BTRFS_BALANCE_OPTS_VRANGE) { if (c->offset + c->chunk_item->size <= opts->vrange_start || c->offset > opts->vrange_end) return false; } if (opts->flags & BTRFS_BALANCE_OPTS_STRIPES) { if (c->chunk_item->num_stripes < opts->stripes_start || c->chunk_item->num_stripes < opts->stripes_end) return false; } if (opts->flags & BTRFS_BALANCE_OPTS_USAGE) { uint64_t usage = c->used * 100 / c->chunk_item->size; // usage == 0 should mean completely empty, not just that usage rounds to 0% if (c->used > 0 && usage == 0) usage = 1; if (usage < opts->usage_start || usage > opts->usage_end) return false; } if (opts->flags & BTRFS_BALANCE_OPTS_CONVERT && opts->flags & BTRFS_BALANCE_OPTS_SOFT) { uint64_t type = get_chunk_dup_type(c); if (type == opts->convert) return false; } return true; } static void copy_balance_args(btrfs_balance_opts* opts, BALANCE_ARGS* args) { if (opts->flags & BTRFS_BALANCE_OPTS_PROFILES) { args->profiles = opts->profiles; args->flags |= BALANCE_ARGS_FLAGS_PROFILES; } if (opts->flags & BTRFS_BALANCE_OPTS_USAGE) { if (args->usage_start == 0) { args->flags |= BALANCE_ARGS_FLAGS_USAGE_RANGE; args->usage_start = opts->usage_start; args->usage_end = opts->usage_end; } else { args->flags |= BALANCE_ARGS_FLAGS_USAGE; args->usage = opts->usage_end; } } if (opts->flags & BTRFS_BALANCE_OPTS_DEVID) { args->devid = opts->devid; args->flags |= BALANCE_ARGS_FLAGS_DEVID; } if (opts->flags & BTRFS_BALANCE_OPTS_DRANGE) { args->drange_start = opts->drange_start; args->drange_end = opts->drange_end; args->flags |= BALANCE_ARGS_FLAGS_DRANGE; } if (opts->flags & BTRFS_BALANCE_OPTS_VRANGE) { args->vrange_start = opts->vrange_start; args->vrange_end = opts->vrange_end; args->flags |= BALANCE_ARGS_FLAGS_VRANGE; } if (opts->flags & BTRFS_BALANCE_OPTS_CONVERT) { args->convert = opts->convert; args->flags |= BALANCE_ARGS_FLAGS_CONVERT; if (opts->flags & BTRFS_BALANCE_OPTS_SOFT) args->flags |= BALANCE_ARGS_FLAGS_SOFT; } if (opts->flags & BTRFS_BALANCE_OPTS_LIMIT) { if (args->limit_start == 0) { args->flags |= BALANCE_ARGS_FLAGS_LIMIT_RANGE; args->limit_start = (uint32_t)opts->limit_start; args->limit_end = (uint32_t)opts->limit_end; } else { args->flags |= BALANCE_ARGS_FLAGS_LIMIT; args->limit = opts->limit_end; } } if (opts->flags & BTRFS_BALANCE_OPTS_STRIPES) { args->stripes_start = opts->stripes_start; args->stripes_end = opts->stripes_end; args->flags |= BALANCE_ARGS_FLAGS_STRIPES_RANGE; } } static NTSTATUS add_balance_item(device_extension* Vcb) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; BALANCE_ITEM* bi; searchkey.obj_id = BALANCE_ITEM_ID; searchkey.obj_type = TYPE_TEMP_ITEM; searchkey.offset = 0; ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); goto end; } if (!keycmp(tp.item->key, searchkey)) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); goto end; } } bi = ExAllocatePoolWithTag(PagedPool, sizeof(BALANCE_ITEM), ALLOC_TAG); if (!bi) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlZeroMemory(bi, sizeof(BALANCE_ITEM)); if (Vcb->balance.opts[BALANCE_OPTS_DATA].flags & BTRFS_BALANCE_OPTS_ENABLED) { bi->flags |= BALANCE_FLAGS_DATA; copy_balance_args(&Vcb->balance.opts[BALANCE_OPTS_DATA], &bi->data); } if (Vcb->balance.opts[BALANCE_OPTS_METADATA].flags & BTRFS_BALANCE_OPTS_ENABLED) { bi->flags |= BALANCE_FLAGS_METADATA; copy_balance_args(&Vcb->balance.opts[BALANCE_OPTS_METADATA], &bi->metadata); } if (Vcb->balance.opts[BALANCE_OPTS_SYSTEM].flags & BTRFS_BALANCE_OPTS_ENABLED) { bi->flags |= BALANCE_FLAGS_SYSTEM; copy_balance_args(&Vcb->balance.opts[BALANCE_OPTS_SYSTEM], &bi->system); } Status = insert_tree_item(Vcb, Vcb->root_root, BALANCE_ITEM_ID, TYPE_TEMP_ITEM, 0, bi, sizeof(BALANCE_ITEM), NULL, NULL); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(bi); goto end; } Status = STATUS_SUCCESS; end: if (NT_SUCCESS(Status)) { Status = do_write(Vcb, NULL); if (!NT_SUCCESS(Status)) ERR("do_write returned %08lx\n", Status); } free_trees(Vcb); ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS remove_balance_item(device_extension* Vcb) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; searchkey.obj_id = BALANCE_ITEM_ID; searchkey.obj_type = TYPE_TEMP_ITEM; searchkey.offset = 0; ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); goto end; } if (!keycmp(tp.item->key, searchkey)) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); goto end; } Status = do_write(Vcb, NULL); if (!NT_SUCCESS(Status)) { ERR("do_write returned %08lx\n", Status); goto end; } free_trees(Vcb); } Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static void load_balance_args(btrfs_balance_opts* opts, BALANCE_ARGS* args) { opts->flags = BTRFS_BALANCE_OPTS_ENABLED; if (args->flags & BALANCE_ARGS_FLAGS_PROFILES) { opts->flags |= BTRFS_BALANCE_OPTS_PROFILES; opts->profiles = args->profiles; } if (args->flags & BALANCE_ARGS_FLAGS_USAGE) { opts->flags |= BTRFS_BALANCE_OPTS_USAGE; opts->usage_start = 0; opts->usage_end = (uint8_t)args->usage; } else if (args->flags & BALANCE_ARGS_FLAGS_USAGE_RANGE) { opts->flags |= BTRFS_BALANCE_OPTS_USAGE; opts->usage_start = (uint8_t)args->usage_start; opts->usage_end = (uint8_t)args->usage_end; } if (args->flags & BALANCE_ARGS_FLAGS_DEVID) { opts->flags |= BTRFS_BALANCE_OPTS_DEVID; opts->devid = args->devid; } if (args->flags & BALANCE_ARGS_FLAGS_DRANGE) { opts->flags |= BTRFS_BALANCE_OPTS_DRANGE; opts->drange_start = args->drange_start; opts->drange_end = args->drange_end; } if (args->flags & BALANCE_ARGS_FLAGS_VRANGE) { opts->flags |= BTRFS_BALANCE_OPTS_VRANGE; opts->vrange_start = args->vrange_start; opts->vrange_end = args->vrange_end; } if (args->flags & BALANCE_ARGS_FLAGS_LIMIT) { opts->flags |= BTRFS_BALANCE_OPTS_LIMIT; opts->limit_start = 0; opts->limit_end = args->limit; } else if (args->flags & BALANCE_ARGS_FLAGS_LIMIT_RANGE) { opts->flags |= BTRFS_BALANCE_OPTS_LIMIT; opts->limit_start = args->limit_start; opts->limit_end = args->limit_end; } if (args->flags & BALANCE_ARGS_FLAGS_STRIPES_RANGE) { opts->flags |= BTRFS_BALANCE_OPTS_STRIPES; opts->stripes_start = (uint16_t)args->stripes_start; opts->stripes_end = (uint16_t)args->stripes_end; } if (args->flags & BALANCE_ARGS_FLAGS_CONVERT) { opts->flags |= BTRFS_BALANCE_OPTS_CONVERT; opts->convert = args->convert; if (args->flags & BALANCE_ARGS_FLAGS_SOFT) opts->flags |= BTRFS_BALANCE_OPTS_SOFT; } } static NTSTATUS remove_superblocks(device* dev) { NTSTATUS Status; superblock* sb; int i = 0; sb = ExAllocatePoolWithTag(PagedPool, sizeof(superblock), ALLOC_TAG); if (!sb) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(sb, sizeof(superblock)); while (superblock_addrs[i] > 0 && dev->devitem.num_bytes >= superblock_addrs[i] + sizeof(superblock)) { Status = write_data_phys(dev->devobj, dev->fileobj, superblock_addrs[i], sb, sizeof(superblock)); if (!NT_SUCCESS(Status)) { ExFreePool(sb); return Status; } i++; } ExFreePool(sb); return STATUS_SUCCESS; } static NTSTATUS finish_removing_device(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, device* dev) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; LIST_ENTRY* le; volume_device_extension* vde; if (Vcb->need_write) { Status = do_write(Vcb, NULL); if (!NT_SUCCESS(Status)) ERR("do_write returned %08lx\n", Status); } else Status = STATUS_SUCCESS; free_trees(Vcb); if (!NT_SUCCESS(Status)) return Status; // remove entry in chunk tree searchkey.obj_id = 1; searchkey.obj_type = TYPE_DEV_ITEM; searchkey.offset = dev->devitem.dev_id; Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (!keycmp(searchkey, tp.item->key)) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } // remove stats entry in device tree searchkey.obj_id = 0; searchkey.obj_type = TYPE_DEV_STATS; searchkey.offset = dev->devitem.dev_id; Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (!keycmp(searchkey, tp.item->key)) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } // update superblock Vcb->superblock.num_devices--; Vcb->superblock.total_bytes -= dev->devitem.num_bytes; Vcb->devices_loaded--; RemoveEntryList(&dev->list_entry); // flush Status = do_write(Vcb, NULL); if (!NT_SUCCESS(Status)) ERR("do_write returned %08lx\n", Status); free_trees(Vcb); if (!NT_SUCCESS(Status)) return Status; if (!dev->readonly && dev->devobj) { Status = remove_superblocks(dev); if (!NT_SUCCESS(Status)) WARN("remove_superblocks returned %08lx\n", Status); } // remove entry in volume list vde = Vcb->vde; if (dev->devobj) { pdo_device_extension* pdode = vde->pdode; ExAcquireResourceExclusiveLite(&pdode->child_lock, true); le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); if (RtlCompareMemory(&dev->devitem.device_uuid, &vc->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { PFILE_OBJECT FileObject; PDEVICE_OBJECT mountmgr; UNICODE_STRING mmdevpath; pdode->children_loaded--; if (vc->had_drive_letter) { // re-add entry to mountmgr RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME); Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr); if (!NT_SUCCESS(Status)) ERR("IoGetDeviceObjectPointer returned %08lx\n", Status); else { MOUNTDEV_NAME mdn; Status = dev_ioctl(dev->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status); else { MOUNTDEV_NAME* mdn2; ULONG mdnsize = (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength; mdn2 = ExAllocatePoolWithTag(PagedPool, mdnsize, ALLOC_TAG); if (!mdn2) ERR("out of memory\n"); else { Status = dev_ioctl(dev->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, mdn2, mdnsize, true, NULL); if (!NT_SUCCESS(Status)) ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status); else { UNICODE_STRING name; name.Buffer = mdn2->Name; name.Length = name.MaximumLength = mdn2->NameLength; Status = mountmgr_add_drive_letter(mountmgr, &name); if (!NT_SUCCESS(Status)) WARN("mountmgr_add_drive_letter returned %08lx\n", Status); } ExFreePool(mdn2); } } ObDereferenceObject(FileObject); } } ExFreePool(vc->pnp_name.Buffer); RemoveEntryList(&vc->list_entry); ExFreePool(vc); ObDereferenceObject(vc->fileobj); break; } le = le->Flink; } if (pdode->children_loaded > 0 && vde->device->Characteristics & FILE_REMOVABLE_MEDIA) { vde->device->Characteristics &= ~FILE_REMOVABLE_MEDIA; le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); if (vc->devobj->Characteristics & FILE_REMOVABLE_MEDIA) { vde->device->Characteristics |= FILE_REMOVABLE_MEDIA; break; } le = le->Flink; } } pdode->num_children = Vcb->superblock.num_devices; ExReleaseResourceLite(&pdode->child_lock); // free dev if (dev->trim && !dev->readonly && !Vcb->options.no_trim) trim_whole_device(dev); } while (!IsListEmpty(&dev->space)) { LIST_ENTRY* le2 = RemoveHeadList(&dev->space); space* s = CONTAINING_RECORD(le2, space, list_entry); ExFreePool(s); } ExFreePool(dev); if (Vcb->trim) { Vcb->trim = false; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev2 = CONTAINING_RECORD(le, device, list_entry); if (dev2->trim) { Vcb->trim = true; break; } le = le->Flink; } } FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_CHANGE_SIZE); return STATUS_SUCCESS; } static void trim_unalloc_space(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, device* dev) { DEVICE_MANAGE_DATA_SET_ATTRIBUTES* dmdsa; DEVICE_DATA_SET_RANGE* ranges; ULONG datalen, i; KEY searchkey; traverse_ptr tp; NTSTATUS Status; bool b; uint64_t lastoff = 0x100000; // don't TRIM the first megabyte, in case someone has been daft enough to install GRUB there LIST_ENTRY* le; dev->num_trim_entries = 0; searchkey.obj_id = dev->devitem.dev_id; searchkey.obj_type = TYPE_DEV_EXTENT; searchkey.offset = 0; Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return; } do { traverse_ptr next_tp; if (tp.item->key.obj_id == dev->devitem.dev_id && tp.item->key.obj_type == TYPE_DEV_EXTENT) { if (tp.item->size >= sizeof(DEV_EXTENT)) { DEV_EXTENT* de = (DEV_EXTENT*)tp.item->data; if (tp.item->key.offset > lastoff) add_trim_entry_avoid_sb(Vcb, dev, lastoff, tp.item->key.offset - lastoff); lastoff = tp.item->key.offset + de->length; } else { 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)); return; } } b = find_next_item(Vcb, &tp, &next_tp, false, NULL); if (b) { tp = next_tp; 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)) break; } } while (b); if (lastoff < dev->devitem.num_bytes) add_trim_entry_avoid_sb(Vcb, dev, lastoff, dev->devitem.num_bytes - lastoff); if (dev->num_trim_entries == 0) return; datalen = (ULONG)sector_align(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), sizeof(uint64_t)) + (dev->num_trim_entries * sizeof(DEVICE_DATA_SET_RANGE)); dmdsa = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG); if (!dmdsa) { ERR("out of memory\n"); goto end; } dmdsa->Size = sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES); dmdsa->Action = DeviceDsmAction_Trim; dmdsa->Flags = DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED; dmdsa->ParameterBlockOffset = 0; dmdsa->ParameterBlockLength = 0; dmdsa->DataSetRangesOffset = (ULONG)sector_align(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), sizeof(uint64_t)); dmdsa->DataSetRangesLength = dev->num_trim_entries * sizeof(DEVICE_DATA_SET_RANGE); ranges = (DEVICE_DATA_SET_RANGE*)((uint8_t*)dmdsa + dmdsa->DataSetRangesOffset); i = 0; le = dev->trim_list.Flink; while (le != &dev->trim_list) { space* s = CONTAINING_RECORD(le, space, list_entry); ranges[i].StartingOffset = s->address; ranges[i].LengthInBytes = s->size; i++; le = le->Flink; } Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES, dmdsa, datalen, NULL, 0, true, NULL); if (!NT_SUCCESS(Status)) WARN("IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES returned %08lx\n", Status); ExFreePool(dmdsa); end: while (!IsListEmpty(&dev->trim_list)) { space* s = CONTAINING_RECORD(RemoveHeadList(&dev->trim_list), space, list_entry); ExFreePool(s); } dev->num_trim_entries = 0; } static NTSTATUS try_consolidation(device_extension* Vcb, uint64_t flags, chunk** newchunk) { NTSTATUS Status; bool changed; LIST_ENTRY* le; chunk* rc; // FIXME - allow with metadata chunks? while (true) { rc = NULL; ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceSharedLite(&Vcb->chunk_lock, true); // choose the least-used chunk we haven't looked at yet le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry); // FIXME - skip full-size chunks over e.g. 90% full? if (c->chunk_item->type & BLOCK_FLAG_DATA && !c->readonly && c->balance_num != Vcb->balance.balance_num && (!rc || c->used < rc->used)) rc = c; le = le->Flink; } ExReleaseResourceLite(&Vcb->chunk_lock); if (!rc) { ExReleaseResourceLite(&Vcb->tree_lock); break; } if (rc->list_entry_balance.Flink) { RemoveEntryList(&rc->list_entry_balance); Vcb->balance.chunks_left--; } rc->list_entry_balance.Flink = (LIST_ENTRY*)1; // so it doesn't get dropped rc->reloc = true; ExReleaseResourceLite(&Vcb->tree_lock); do { changed = false; Status = balance_data_chunk(Vcb, rc, &changed); if (!NT_SUCCESS(Status)) { ERR("balance_data_chunk returned %08lx\n", Status); Vcb->balance.status = Status; rc->list_entry_balance.Flink = NULL; rc->reloc = false; return Status; } KeWaitForSingleObject(&Vcb->balance.event, Executive, KernelMode, false, NULL); if (Vcb->readonly) Vcb->balance.stopping = true; if (Vcb->balance.stopping) return STATUS_SUCCESS; } while (changed); rc->list_entry_balance.Flink = NULL; rc->changed = true; rc->space_changed = true; rc->balance_num = Vcb->balance.balance_num; Status = do_write(Vcb, NULL); if (!NT_SUCCESS(Status)) { ERR("do_write returned %08lx\n", Status); return Status; } free_trees(Vcb); } ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true); Status = alloc_chunk(Vcb, flags, &rc, true); ExReleaseResourceLite(&Vcb->chunk_lock); if (NT_SUCCESS(Status)) { *newchunk = rc; return Status; } else { ERR("alloc_chunk returned %08lx\n", Status); return Status; } } static NTSTATUS regenerate_space_list(device_extension* Vcb, device* dev) { LIST_ENTRY* le; while (!IsListEmpty(&dev->space)) { space* s = CONTAINING_RECORD(RemoveHeadList(&dev->space), space, list_entry); ExFreePool(s); } // The Linux driver doesn't like to allocate chunks within the first megabyte of a device. space_list_add2(&dev->space, NULL, 0x100000, dev->devitem.num_bytes - 0x100000, NULL, NULL); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { uint16_t n; chunk* c = CONTAINING_RECORD(le, chunk, list_entry); CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; for (n = 0; n < c->chunk_item->num_stripes; n++) { uint64_t stripe_size = 0; if (cis[n].dev_id == dev->devitem.dev_id) { if (stripe_size == 0) { uint16_t factor; if (c->chunk_item->type & BLOCK_FLAG_RAID0) factor = c->chunk_item->num_stripes; else if (c->chunk_item->type & BLOCK_FLAG_RAID10) factor = c->chunk_item->num_stripes / c->chunk_item->sub_stripes; else if (c->chunk_item->type & BLOCK_FLAG_RAID5) factor = c->chunk_item->num_stripes - 1; else if (c->chunk_item->type & BLOCK_FLAG_RAID6) factor = c->chunk_item->num_stripes - 2; else // SINGLE, DUP, RAID1, RAID1C3, RAID1C4 factor = 1; stripe_size = c->chunk_item->size / factor; } space_list_subtract2(&dev->space, NULL, cis[n].offset, stripe_size, NULL, NULL); } } le = le->Flink; } return STATUS_SUCCESS; } _Function_class_(KSTART_ROUTINE) void __stdcall balance_thread(void* context) { device_extension* Vcb = (device_extension*)context; LIST_ENTRY chunks; LIST_ENTRY* le; uint64_t num_chunks[3], okay_metadata_chunks = 0, okay_data_chunks = 0, okay_system_chunks = 0; uint64_t old_data_flags = 0, old_metadata_flags = 0, old_system_flags = 0; NTSTATUS Status; Vcb->balance.balance_num++; Vcb->balance.stopping = false; KeInitializeEvent(&Vcb->balance.finished, NotificationEvent, false); if (Vcb->balance.opts[BALANCE_OPTS_DATA].flags & BTRFS_BALANCE_OPTS_ENABLED && Vcb->balance.opts[BALANCE_OPTS_DATA].flags & BTRFS_BALANCE_OPTS_CONVERT) { old_data_flags = Vcb->data_flags; Vcb->data_flags = BLOCK_FLAG_DATA | (Vcb->balance.opts[BALANCE_OPTS_DATA].convert == BLOCK_FLAG_SINGLE ? 0 : Vcb->balance.opts[BALANCE_OPTS_DATA].convert); FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_CHANGE_SIZE); } if (Vcb->balance.opts[BALANCE_OPTS_METADATA].flags & BTRFS_BALANCE_OPTS_ENABLED && Vcb->balance.opts[BALANCE_OPTS_METADATA].flags & BTRFS_BALANCE_OPTS_CONVERT) { old_metadata_flags = Vcb->metadata_flags; Vcb->metadata_flags = BLOCK_FLAG_METADATA | (Vcb->balance.opts[BALANCE_OPTS_METADATA].convert == BLOCK_FLAG_SINGLE ? 0 : Vcb->balance.opts[BALANCE_OPTS_METADATA].convert); } if (Vcb->balance.opts[BALANCE_OPTS_SYSTEM].flags & BTRFS_BALANCE_OPTS_ENABLED && Vcb->balance.opts[BALANCE_OPTS_SYSTEM].flags & BTRFS_BALANCE_OPTS_CONVERT) { old_system_flags = Vcb->system_flags; Vcb->system_flags = BLOCK_FLAG_SYSTEM | (Vcb->balance.opts[BALANCE_OPTS_SYSTEM].convert == BLOCK_FLAG_SINGLE ? 0 : Vcb->balance.opts[BALANCE_OPTS_SYSTEM].convert); } if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS) { if (Vcb->balance.opts[BALANCE_OPTS_DATA].flags & BTRFS_BALANCE_OPTS_ENABLED) RtlCopyMemory(&Vcb->balance.opts[BALANCE_OPTS_METADATA], &Vcb->balance.opts[BALANCE_OPTS_DATA], sizeof(btrfs_balance_opts)); else if (Vcb->balance.opts[BALANCE_OPTS_METADATA].flags & BTRFS_BALANCE_OPTS_ENABLED) RtlCopyMemory(&Vcb->balance.opts[BALANCE_OPTS_DATA], &Vcb->balance.opts[BALANCE_OPTS_METADATA], sizeof(btrfs_balance_opts)); } num_chunks[0] = num_chunks[1] = num_chunks[2] = 0; Vcb->balance.total_chunks = Vcb->balance.chunks_left = 0; InitializeListHead(&chunks); // FIXME - what are we supposed to do with limit_start? if (!Vcb->readonly) { if (!Vcb->balance.removing && !Vcb->balance.shrinking) { Status = add_balance_item(Vcb); if (!NT_SUCCESS(Status)) { ERR("add_balance_item returned %08lx\n", Status); Vcb->balance.status = Status; goto end; } } else { if (Vcb->need_write) { Status = do_write(Vcb, NULL); free_trees(Vcb); if (!NT_SUCCESS(Status)) { ERR("do_write returned %08lx\n", Status); Vcb->balance.status = Status; goto end; } } } } KeWaitForSingleObject(&Vcb->balance.event, Executive, KernelMode, false, NULL); if (Vcb->balance.stopping) goto end; ExAcquireResourceSharedLite(&Vcb->chunk_lock, true); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry); uint8_t sort; acquire_chunk_lock(c, Vcb); if (c->chunk_item->type & BLOCK_FLAG_DATA) sort = BALANCE_OPTS_DATA; else if (c->chunk_item->type & BLOCK_FLAG_METADATA) sort = BALANCE_OPTS_METADATA; else if (c->chunk_item->type & BLOCK_FLAG_SYSTEM) sort = BALANCE_OPTS_SYSTEM; else { ERR("unexpected chunk type %I64x\n", c->chunk_item->type); release_chunk_lock(c, Vcb); break; } if ((!(Vcb->balance.opts[sort].flags & BTRFS_BALANCE_OPTS_LIMIT) || num_chunks[sort] < Vcb->balance.opts[sort].limit_end) && should_balance_chunk(Vcb, sort, c)) { InsertTailList(&chunks, &c->list_entry_balance); num_chunks[sort]++; Vcb->balance.total_chunks++; Vcb->balance.chunks_left++; } else if (sort == BALANCE_OPTS_METADATA) okay_metadata_chunks++; else if (sort == BALANCE_OPTS_DATA) okay_data_chunks++; else if (sort == BALANCE_OPTS_SYSTEM) okay_system_chunks++; if (!c->cache_loaded) { Status = load_cache_chunk(Vcb, c, NULL); if (!NT_SUCCESS(Status)) { ERR("load_cache_chunk returned %08lx\n", Status); Vcb->balance.status = Status; release_chunk_lock(c, Vcb); ExReleaseResourceLite(&Vcb->chunk_lock); goto end; } } release_chunk_lock(c, Vcb); le = le->Flink; } ExReleaseResourceLite(&Vcb->chunk_lock); // If we're doing a full balance, try and allocate a new chunk now, before we mess things up if (okay_metadata_chunks == 0 || okay_data_chunks == 0 || okay_system_chunks == 0) { bool consolidated = false; chunk* c; if (okay_metadata_chunks == 0) { ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true); Status = alloc_chunk(Vcb, Vcb->metadata_flags, &c, true); if (NT_SUCCESS(Status)) c->balance_num = Vcb->balance.balance_num; else if (Status != STATUS_DISK_FULL || consolidated) { ERR("alloc_chunk returned %08lx\n", Status); ExReleaseResourceLite(&Vcb->chunk_lock); Vcb->balance.status = Status; goto end; } ExReleaseResourceLite(&Vcb->chunk_lock); if (Status == STATUS_DISK_FULL) { Status = try_consolidation(Vcb, Vcb->metadata_flags, &c); if (!NT_SUCCESS(Status)) { ERR("try_consolidation returned %08lx\n", Status); Vcb->balance.status = Status; goto end; } else c->balance_num = Vcb->balance.balance_num; consolidated = true; if (Vcb->balance.stopping) goto end; } } if (okay_data_chunks == 0) { ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true); Status = alloc_chunk(Vcb, Vcb->data_flags, &c, true); if (NT_SUCCESS(Status)) c->balance_num = Vcb->balance.balance_num; else if (Status != STATUS_DISK_FULL || consolidated) { ERR("alloc_chunk returned %08lx\n", Status); ExReleaseResourceLite(&Vcb->chunk_lock); Vcb->balance.status = Status; goto end; } ExReleaseResourceLite(&Vcb->chunk_lock); if (Status == STATUS_DISK_FULL) { Status = try_consolidation(Vcb, Vcb->data_flags, &c); if (!NT_SUCCESS(Status)) { ERR("try_consolidation returned %08lx\n", Status); Vcb->balance.status = Status; goto end; } else c->balance_num = Vcb->balance.balance_num; consolidated = true; if (Vcb->balance.stopping) goto end; } } if (okay_system_chunks == 0) { ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true); Status = alloc_chunk(Vcb, Vcb->system_flags, &c, true); if (NT_SUCCESS(Status)) c->balance_num = Vcb->balance.balance_num; else if (Status != STATUS_DISK_FULL || consolidated) { ERR("alloc_chunk returned %08lx\n", Status); ExReleaseResourceLite(&Vcb->chunk_lock); Vcb->balance.status = Status; goto end; } ExReleaseResourceLite(&Vcb->chunk_lock); if (Status == STATUS_DISK_FULL) { Status = try_consolidation(Vcb, Vcb->system_flags, &c); if (!NT_SUCCESS(Status)) { ERR("try_consolidation returned %08lx\n", Status); Vcb->balance.status = Status; goto end; } else c->balance_num = Vcb->balance.balance_num; consolidated = true; if (Vcb->balance.stopping) goto end; } } } ExAcquireResourceSharedLite(&Vcb->chunk_lock, true); le = chunks.Flink; while (le != &chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry_balance); c->reloc = true; le = le->Flink; } ExReleaseResourceLite(&Vcb->chunk_lock); // do data chunks before metadata le = chunks.Flink; while (le != &chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry_balance); LIST_ENTRY* le2 = le->Flink; if (c->chunk_item->type & BLOCK_FLAG_DATA) { bool changed; do { changed = false; Status = balance_data_chunk(Vcb, c, &changed); if (!NT_SUCCESS(Status)) { ERR("balance_data_chunk returned %08lx\n", Status); Vcb->balance.status = Status; goto end; } KeWaitForSingleObject(&Vcb->balance.event, Executive, KernelMode, false, NULL); if (Vcb->readonly) Vcb->balance.stopping = true; if (Vcb->balance.stopping) break; } while (changed); c->changed = true; c->space_changed = true; } if (Vcb->balance.stopping) goto end; if (c->chunk_item->type & BLOCK_FLAG_DATA && (!(Vcb->balance.opts[BALANCE_OPTS_METADATA].flags & BTRFS_BALANCE_OPTS_ENABLED) || !(c->chunk_item->type & BLOCK_FLAG_METADATA))) { RemoveEntryList(&c->list_entry_balance); c->list_entry_balance.Flink = NULL; Vcb->balance.chunks_left--; } le = le2; } // do metadata chunks while (!IsListEmpty(&chunks)) { chunk* c; bool changed; le = RemoveHeadList(&chunks); c = CONTAINING_RECORD(le, chunk, list_entry_balance); if (c->chunk_item->type & BLOCK_FLAG_METADATA || c->chunk_item->type & BLOCK_FLAG_SYSTEM) { do { Status = balance_metadata_chunk(Vcb, c, &changed); if (!NT_SUCCESS(Status)) { ERR("balance_metadata_chunk returned %08lx\n", Status); Vcb->balance.status = Status; goto end; } KeWaitForSingleObject(&Vcb->balance.event, Executive, KernelMode, false, NULL); if (Vcb->readonly) Vcb->balance.stopping = true; if (Vcb->balance.stopping) break; } while (changed); c->changed = true; c->space_changed = true; } if (Vcb->balance.stopping) break; c->list_entry_balance.Flink = NULL; Vcb->balance.chunks_left--; } end: if (!Vcb->readonly) { if (Vcb->balance.stopping || !NT_SUCCESS(Vcb->balance.status)) { le = chunks.Flink; while (le != &chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry_balance); c->reloc = false; le = le->Flink; c->list_entry_balance.Flink = NULL; } if (old_data_flags != 0) Vcb->data_flags = old_data_flags; if (old_metadata_flags != 0) Vcb->metadata_flags = old_metadata_flags; if (old_system_flags != 0) Vcb->system_flags = old_system_flags; } if (Vcb->balance.removing) { device* dev = NULL; ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev2 = CONTAINING_RECORD(le, device, list_entry); if (dev2->devitem.dev_id == Vcb->balance.opts[0].devid) { dev = dev2; break; } le = le->Flink; } if (dev) { if (Vcb->balance.chunks_left == 0) { Status = finish_removing_device(Vcb, dev); if (!NT_SUCCESS(Status)) { ERR("finish_removing_device returned %08lx\n", Status); dev->reloc = false; } } else dev->reloc = false; } ExReleaseResourceLite(&Vcb->tree_lock); } else if (Vcb->balance.shrinking) { device* dev = NULL; ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev2 = CONTAINING_RECORD(le, device, list_entry); if (dev2->devitem.dev_id == Vcb->balance.opts[0].devid) { dev = dev2; break; } le = le->Flink; } if (!dev) { ERR("could not find device %I64x\n", Vcb->balance.opts[0].devid); Vcb->balance.status = STATUS_INTERNAL_ERROR; } if (Vcb->balance.stopping || !NT_SUCCESS(Vcb->balance.status)) { if (dev) { Status = regenerate_space_list(Vcb, dev); if (!NT_SUCCESS(Status)) WARN("regenerate_space_list returned %08lx\n", Status); } } else { uint64_t old_size; old_size = dev->devitem.num_bytes; dev->devitem.num_bytes = Vcb->balance.opts[0].drange_start; Status = update_dev_item(Vcb, dev, NULL); if (!NT_SUCCESS(Status)) { ERR("update_dev_item returned %08lx\n", Status); dev->devitem.num_bytes = old_size; Vcb->balance.status = Status; Status = regenerate_space_list(Vcb, dev); if (!NT_SUCCESS(Status)) WARN("regenerate_space_list returned %08lx\n", Status); } else { Vcb->superblock.total_bytes -= old_size - dev->devitem.num_bytes; Status = do_write(Vcb, NULL); if (!NT_SUCCESS(Status)) ERR("do_write returned %08lx\n", Status); free_trees(Vcb); } } ExReleaseResourceLite(&Vcb->tree_lock); if (!Vcb->balance.stopping && NT_SUCCESS(Vcb->balance.status)) FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_CHANGE_SIZE); } else { Status = remove_balance_item(Vcb); if (!NT_SUCCESS(Status)) { ERR("remove_balance_item returned %08lx\n", Status); goto end; } } if (Vcb->trim && !Vcb->options.no_trim) { ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev2 = CONTAINING_RECORD(le, device, list_entry); if (dev2->devobj && !dev2->readonly && dev2->trim) trim_unalloc_space(Vcb, dev2); le = le->Flink; } ExReleaseResourceLite(&Vcb->tree_lock); } } ZwClose(Vcb->balance.thread); Vcb->balance.thread = NULL; KeSetEvent(&Vcb->balance.finished, 0, false); } NTSTATUS start_balance(device_extension* Vcb, void* data, ULONG length, KPROCESSOR_MODE processor_mode) { NTSTATUS Status; btrfs_start_balance* bsb = (btrfs_start_balance*)data; OBJECT_ATTRIBUTES oa; uint8_t i; if (length < sizeof(btrfs_start_balance) || !data) return STATUS_INVALID_PARAMETER; if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; if (Vcb->locked) { WARN("cannot start balance while locked\n"); return STATUS_DEVICE_NOT_READY; } if (Vcb->scrub.thread) { WARN("cannot start balance while scrub running\n"); return STATUS_DEVICE_NOT_READY; } if (Vcb->balance.thread) { WARN("balance already running\n"); return STATUS_DEVICE_NOT_READY; } if (Vcb->readonly) return STATUS_MEDIA_WRITE_PROTECTED; if (!(bsb->opts[BALANCE_OPTS_DATA].flags & BTRFS_BALANCE_OPTS_ENABLED) && !(bsb->opts[BALANCE_OPTS_METADATA].flags & BTRFS_BALANCE_OPTS_ENABLED) && !(bsb->opts[BALANCE_OPTS_SYSTEM].flags & BTRFS_BALANCE_OPTS_ENABLED)) return STATUS_SUCCESS; for (i = 0; i < 3; i++) { if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_ENABLED) { if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_PROFILES) { bsb->opts[i].profiles &= BLOCK_FLAG_RAID0 | BLOCK_FLAG_RAID1 | BLOCK_FLAG_DUPLICATE | BLOCK_FLAG_RAID10 | BLOCK_FLAG_RAID5 | BLOCK_FLAG_RAID6 | BLOCK_FLAG_SINGLE | BLOCK_FLAG_RAID1C3 | BLOCK_FLAG_RAID1C4; if (bsb->opts[i].profiles == 0) return STATUS_INVALID_PARAMETER; } if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_DEVID) { if (bsb->opts[i].devid == 0) return STATUS_INVALID_PARAMETER; } if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_DRANGE) { if (bsb->opts[i].drange_start > bsb->opts[i].drange_end) return STATUS_INVALID_PARAMETER; } if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_VRANGE) { if (bsb->opts[i].vrange_start > bsb->opts[i].vrange_end) return STATUS_INVALID_PARAMETER; } if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_LIMIT) { bsb->opts[i].limit_start = max(1, bsb->opts[i].limit_start); bsb->opts[i].limit_end = max(1, bsb->opts[i].limit_end); if (bsb->opts[i].limit_start > bsb->opts[i].limit_end) return STATUS_INVALID_PARAMETER; } if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_STRIPES) { bsb->opts[i].stripes_start = max(1, bsb->opts[i].stripes_start); bsb->opts[i].stripes_end = max(1, bsb->opts[i].stripes_end); if (bsb->opts[i].stripes_start > bsb->opts[i].stripes_end) return STATUS_INVALID_PARAMETER; } if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_USAGE) { bsb->opts[i].usage_start = min(100, bsb->opts[i].stripes_start); bsb->opts[i].usage_end = min(100, bsb->opts[i].stripes_end); if (bsb->opts[i].stripes_start > bsb->opts[i].stripes_end) return STATUS_INVALID_PARAMETER; } if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_CONVERT) { if (bsb->opts[i].convert != BLOCK_FLAG_RAID0 && bsb->opts[i].convert != BLOCK_FLAG_RAID1 && bsb->opts[i].convert != BLOCK_FLAG_DUPLICATE && bsb->opts[i].convert != BLOCK_FLAG_RAID10 && bsb->opts[i].convert != BLOCK_FLAG_RAID5 && bsb->opts[i].convert != BLOCK_FLAG_RAID6 && bsb->opts[i].convert != BLOCK_FLAG_SINGLE && bsb->opts[i].convert != BLOCK_FLAG_RAID1C3 && bsb->opts[i].convert != BLOCK_FLAG_RAID1C4) return STATUS_INVALID_PARAMETER; } } } RtlCopyMemory(&Vcb->balance.opts[BALANCE_OPTS_DATA], &bsb->opts[BALANCE_OPTS_DATA], sizeof(btrfs_balance_opts)); RtlCopyMemory(&Vcb->balance.opts[BALANCE_OPTS_METADATA], &bsb->opts[BALANCE_OPTS_METADATA], sizeof(btrfs_balance_opts)); RtlCopyMemory(&Vcb->balance.opts[BALANCE_OPTS_SYSTEM], &bsb->opts[BALANCE_OPTS_SYSTEM], sizeof(btrfs_balance_opts)); Vcb->balance.paused = false; Vcb->balance.removing = false; Vcb->balance.shrinking = false; Vcb->balance.status = STATUS_SUCCESS; KeInitializeEvent(&Vcb->balance.event, NotificationEvent, !Vcb->balance.paused); InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); Status = PsCreateSystemThread(&Vcb->balance.thread, 0, &oa, NULL, NULL, balance_thread, Vcb); if (!NT_SUCCESS(Status)) { ERR("PsCreateSystemThread returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } NTSTATUS look_for_balance_item(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; BALANCE_ITEM* bi; OBJECT_ATTRIBUTES oa; int i; searchkey.obj_id = BALANCE_ITEM_ID; searchkey.obj_type = TYPE_TEMP_ITEM; searchkey.offset = 0; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (keycmp(tp.item->key, searchkey)) { TRACE("no balance item found\n"); return STATUS_NOT_FOUND; } if (tp.item->size < sizeof(BALANCE_ITEM)) { 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(BALANCE_ITEM)); return STATUS_INTERNAL_ERROR; } bi = (BALANCE_ITEM*)tp.item->data; if (bi->flags & BALANCE_FLAGS_DATA) load_balance_args(&Vcb->balance.opts[BALANCE_OPTS_DATA], &bi->data); if (bi->flags & BALANCE_FLAGS_METADATA) load_balance_args(&Vcb->balance.opts[BALANCE_OPTS_METADATA], &bi->metadata); if (bi->flags & BALANCE_FLAGS_SYSTEM) load_balance_args(&Vcb->balance.opts[BALANCE_OPTS_SYSTEM], &bi->system); // do the heuristics that Linux driver does for (i = 0; i < 3; i++) { if (Vcb->balance.opts[i].flags & BTRFS_BALANCE_OPTS_ENABLED) { // if converting, don't redo chunks already done if (Vcb->balance.opts[i].flags & BTRFS_BALANCE_OPTS_CONVERT) Vcb->balance.opts[i].flags |= BTRFS_BALANCE_OPTS_SOFT; // don't balance chunks more than 90% filled - presumably these // have already been done if (!(Vcb->balance.opts[i].flags & BTRFS_BALANCE_OPTS_USAGE) && !(Vcb->balance.opts[i].flags & BTRFS_BALANCE_OPTS_CONVERT) ) { Vcb->balance.opts[i].flags |= BTRFS_BALANCE_OPTS_USAGE; Vcb->balance.opts[i].usage_start = 0; Vcb->balance.opts[i].usage_end = 90; } } } if (Vcb->readonly || Vcb->options.skip_balance) Vcb->balance.paused = true; else Vcb->balance.paused = false; Vcb->balance.removing = false; Vcb->balance.shrinking = false; Vcb->balance.status = STATUS_SUCCESS; KeInitializeEvent(&Vcb->balance.event, NotificationEvent, !Vcb->balance.paused); InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); Status = PsCreateSystemThread(&Vcb->balance.thread, 0, &oa, NULL, NULL, balance_thread, Vcb); if (!NT_SUCCESS(Status)) { ERR("PsCreateSystemThread returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } NTSTATUS query_balance(device_extension* Vcb, void* data, ULONG length) { btrfs_query_balance* bqb = (btrfs_query_balance*)data; if (length < sizeof(btrfs_query_balance) || !data) return STATUS_INVALID_PARAMETER; if (!Vcb->balance.thread) { bqb->status = BTRFS_BALANCE_STOPPED; if (!NT_SUCCESS(Vcb->balance.status)) { bqb->status |= BTRFS_BALANCE_ERROR; bqb->error = Vcb->balance.status; } return STATUS_SUCCESS; } bqb->status = Vcb->balance.paused ? BTRFS_BALANCE_PAUSED : BTRFS_BALANCE_RUNNING; if (Vcb->balance.removing) bqb->status |= BTRFS_BALANCE_REMOVAL; if (Vcb->balance.shrinking) bqb->status |= BTRFS_BALANCE_SHRINKING; if (!NT_SUCCESS(Vcb->balance.status)) bqb->status |= BTRFS_BALANCE_ERROR; bqb->chunks_left = Vcb->balance.chunks_left; bqb->total_chunks = Vcb->balance.total_chunks; bqb->error = Vcb->balance.status; RtlCopyMemory(&bqb->data_opts, &Vcb->balance.opts[BALANCE_OPTS_DATA], sizeof(btrfs_balance_opts)); RtlCopyMemory(&bqb->metadata_opts, &Vcb->balance.opts[BALANCE_OPTS_METADATA], sizeof(btrfs_balance_opts)); RtlCopyMemory(&bqb->system_opts, &Vcb->balance.opts[BALANCE_OPTS_SYSTEM], sizeof(btrfs_balance_opts)); return STATUS_SUCCESS; } NTSTATUS pause_balance(device_extension* Vcb, KPROCESSOR_MODE processor_mode) { if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; if (!Vcb->balance.thread) return STATUS_DEVICE_NOT_READY; if (Vcb->balance.paused) return STATUS_DEVICE_NOT_READY; Vcb->balance.paused = true; KeClearEvent(&Vcb->balance.event); return STATUS_SUCCESS; } NTSTATUS resume_balance(device_extension* Vcb, KPROCESSOR_MODE processor_mode) { if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; if (!Vcb->balance.thread) return STATUS_DEVICE_NOT_READY; if (!Vcb->balance.paused) return STATUS_DEVICE_NOT_READY; if (Vcb->readonly) return STATUS_MEDIA_WRITE_PROTECTED; Vcb->balance.paused = false; KeSetEvent(&Vcb->balance.event, 0, false); return STATUS_SUCCESS; } NTSTATUS stop_balance(device_extension* Vcb, KPROCESSOR_MODE processor_mode) { if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; if (!Vcb->balance.thread) return STATUS_DEVICE_NOT_READY; Vcb->balance.paused = false; Vcb->balance.stopping = true; Vcb->balance.status = STATUS_SUCCESS; KeSetEvent(&Vcb->balance.event, 0, false); return STATUS_SUCCESS; } NTSTATUS remove_device(device_extension* Vcb, void* data, ULONG length, KPROCESSOR_MODE processor_mode) { uint64_t devid; LIST_ENTRY* le; device* dev = NULL; NTSTATUS Status; int i; uint64_t num_rw_devices; OBJECT_ATTRIBUTES oa; TRACE("(%p, %p, %lx)\n", Vcb, data, length); if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; if (length < sizeof(uint64_t)) return STATUS_INVALID_PARAMETER; devid = *(uint64_t*)data; ExAcquireResourceSharedLite(&Vcb->tree_lock, true); if (Vcb->readonly) { ExReleaseResourceLite(&Vcb->tree_lock); return STATUS_MEDIA_WRITE_PROTECTED; } num_rw_devices = 0; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev2 = CONTAINING_RECORD(le, device, list_entry); if (dev2->devitem.dev_id == devid) dev = dev2; if (!dev2->readonly) num_rw_devices++; le = le->Flink; } if (!dev) { ExReleaseResourceLite(&Vcb->tree_lock); WARN("device %I64x not found\n", devid); return STATUS_NOT_FOUND; } if (!dev->readonly) { if (num_rw_devices == 1) { ExReleaseResourceLite(&Vcb->tree_lock); WARN("not removing last non-readonly device\n"); return STATUS_INVALID_PARAMETER; } if (num_rw_devices == 4 && ((Vcb->data_flags & BLOCK_FLAG_RAID10 || Vcb->metadata_flags & BLOCK_FLAG_RAID10 || Vcb->system_flags & BLOCK_FLAG_RAID10) || (Vcb->data_flags & BLOCK_FLAG_RAID6 || Vcb->metadata_flags & BLOCK_FLAG_RAID6 || Vcb->system_flags & BLOCK_FLAG_RAID6) || (Vcb->data_flags & BLOCK_FLAG_RAID1C4 || Vcb->metadata_flags & BLOCK_FLAG_RAID1C4 || Vcb->system_flags & BLOCK_FLAG_RAID1C4) ) ) { ExReleaseResourceLite(&Vcb->tree_lock); ERR("would not be enough devices to satisfy RAID requirement (RAID6/10/1C4)\n"); return STATUS_CANNOT_DELETE; } if (num_rw_devices == 3 && ((Vcb->data_flags & BLOCK_FLAG_RAID5 || Vcb->metadata_flags & BLOCK_FLAG_RAID5 || Vcb->system_flags & BLOCK_FLAG_RAID5) || (Vcb->data_flags & BLOCK_FLAG_RAID1C3 || Vcb->metadata_flags & BLOCK_FLAG_RAID1C3 || Vcb->system_flags & BLOCK_FLAG_RAID1C3)) ) { ExReleaseResourceLite(&Vcb->tree_lock); ERR("would not be enough devices to satisfy RAID requirement (RAID5/1C3)\n"); return STATUS_CANNOT_DELETE; } if (num_rw_devices == 2 && ((Vcb->data_flags & BLOCK_FLAG_RAID0 || Vcb->metadata_flags & BLOCK_FLAG_RAID0 || Vcb->system_flags & BLOCK_FLAG_RAID0) || (Vcb->data_flags & BLOCK_FLAG_RAID1 || Vcb->metadata_flags & BLOCK_FLAG_RAID1 || Vcb->system_flags & BLOCK_FLAG_RAID1)) ) { ExReleaseResourceLite(&Vcb->tree_lock); ERR("would not be enough devices to satisfy RAID requirement (RAID0/1)\n"); return STATUS_CANNOT_DELETE; } } ExReleaseResourceLite(&Vcb->tree_lock); if (Vcb->balance.thread) { WARN("balance already running\n"); return STATUS_DEVICE_NOT_READY; } dev->reloc = true; RtlZeroMemory(Vcb->balance.opts, sizeof(btrfs_balance_opts) * 3); for (i = 0; i < 3; i++) { Vcb->balance.opts[i].flags = BTRFS_BALANCE_OPTS_ENABLED | BTRFS_BALANCE_OPTS_DEVID; Vcb->balance.opts[i].devid = devid; } Vcb->balance.paused = false; Vcb->balance.removing = true; Vcb->balance.shrinking = false; Vcb->balance.status = STATUS_SUCCESS; KeInitializeEvent(&Vcb->balance.event, NotificationEvent, !Vcb->balance.paused); InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); Status = PsCreateSystemThread(&Vcb->balance.thread, 0, &oa, NULL, NULL, balance_thread, Vcb); if (!NT_SUCCESS(Status)) { ERR("PsCreateSystemThread returned %08lx\n", Status); dev->reloc = false; return Status; } return STATUS_SUCCESS; } ================================================ FILE: src/blake2-impl.h ================================================ /* BLAKE2 reference source code package - reference C implementations Copyright 2012, Samuel Neves . You may use this under the terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at your option. The terms of these licenses can be found at: - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 - OpenSSL license : https://www.openssl.org/source/license.html - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 More information about the BLAKE2 hash function can be found at https://blake2.net. */ #pragma once #include #include #if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) #if defined(_MSC_VER) #define BLAKE2_INLINE __inline #elif defined(__GNUC__) #define BLAKE2_INLINE __inline__ #else #define BLAKE2_INLINE #endif #else #define BLAKE2_INLINE inline #endif #define NATIVE_LITTLE_ENDIAN static BLAKE2_INLINE uint32_t load32( const void *src ) { #if defined(NATIVE_LITTLE_ENDIAN) uint32_t w; memcpy(&w, src, sizeof w); return w; #else const uint8_t *p = ( const uint8_t * )src; return (( uint32_t )( p[0] ) << 0) | (( uint32_t )( p[1] ) << 8) | (( uint32_t )( p[2] ) << 16) | (( uint32_t )( p[3] ) << 24) ; #endif } static BLAKE2_INLINE uint64_t load64( const void *src ) { #if defined(NATIVE_LITTLE_ENDIAN) uint64_t w; memcpy(&w, src, sizeof w); return w; #else const uint8_t *p = ( const uint8_t * )src; return (( uint64_t )( p[0] ) << 0) | (( uint64_t )( p[1] ) << 8) | (( uint64_t )( p[2] ) << 16) | (( uint64_t )( p[3] ) << 24) | (( uint64_t )( p[4] ) << 32) | (( uint64_t )( p[5] ) << 40) | (( uint64_t )( p[6] ) << 48) | (( uint64_t )( p[7] ) << 56) ; #endif } static BLAKE2_INLINE uint16_t load16( const void *src ) { #if defined(NATIVE_LITTLE_ENDIAN) uint16_t w; memcpy(&w, src, sizeof w); return w; #else const uint8_t *p = ( const uint8_t * )src; return ( uint16_t )((( uint32_t )( p[0] ) << 0) | (( uint32_t )( p[1] ) << 8)); #endif } static BLAKE2_INLINE void store16( void *dst, uint16_t w ) { #if defined(NATIVE_LITTLE_ENDIAN) memcpy(dst, &w, sizeof w); #else uint8_t *p = ( uint8_t * )dst; *p++ = ( uint8_t )w; w >>= 8; *p++ = ( uint8_t )w; #endif } static BLAKE2_INLINE void store32( void *dst, uint32_t w ) { #if defined(NATIVE_LITTLE_ENDIAN) memcpy(dst, &w, sizeof w); #else uint8_t *p = ( uint8_t * )dst; p[0] = (uint8_t)(w >> 0); p[1] = (uint8_t)(w >> 8); p[2] = (uint8_t)(w >> 16); p[3] = (uint8_t)(w >> 24); #endif } static BLAKE2_INLINE void store64( void *dst, uint64_t w ) { #if defined(NATIVE_LITTLE_ENDIAN) memcpy(dst, &w, sizeof w); #else uint8_t *p = ( uint8_t * )dst; p[0] = (uint8_t)(w >> 0); p[1] = (uint8_t)(w >> 8); p[2] = (uint8_t)(w >> 16); p[3] = (uint8_t)(w >> 24); p[4] = (uint8_t)(w >> 32); p[5] = (uint8_t)(w >> 40); p[6] = (uint8_t)(w >> 48); p[7] = (uint8_t)(w >> 56); #endif } static BLAKE2_INLINE uint64_t load48( const void *src ) { const uint8_t *p = ( const uint8_t * )src; return (( uint64_t )( p[0] ) << 0) | (( uint64_t )( p[1] ) << 8) | (( uint64_t )( p[2] ) << 16) | (( uint64_t )( p[3] ) << 24) | (( uint64_t )( p[4] ) << 32) | (( uint64_t )( p[5] ) << 40) ; } static BLAKE2_INLINE void store48( void *dst, uint64_t w ) { uint8_t *p = ( uint8_t * )dst; p[0] = (uint8_t)(w >> 0); p[1] = (uint8_t)(w >> 8); p[2] = (uint8_t)(w >> 16); p[3] = (uint8_t)(w >> 24); p[4] = (uint8_t)(w >> 32); p[5] = (uint8_t)(w >> 40); } static BLAKE2_INLINE uint32_t rotr32( const uint32_t w, const unsigned c ) { return ( w >> c ) | ( w << ( 32 - c ) ); } static BLAKE2_INLINE uint64_t rotr64( const uint64_t w, const unsigned c ) { return ( w >> c ) | ( w << ( 64 - c ) ); } #if defined(_MSC_VER) #define BLAKE2_PACKED(x) __pragma(pack(push, 1)) x __pragma(pack(pop)) #else #define BLAKE2_PACKED(x) x __attribute__((packed)) #endif enum blake2b_constant { BLAKE2B_BLOCKBYTES = 128, BLAKE2B_OUTBYTES = 64, BLAKE2B_KEYBYTES = 64, BLAKE2B_SALTBYTES = 16, BLAKE2B_PERSONALBYTES = 16 }; typedef struct blake2b_state__ { uint64_t h[8]; uint64_t t[2]; uint64_t f[2]; uint8_t buf[BLAKE2B_BLOCKBYTES]; size_t buflen; size_t outlen; uint8_t last_node; } blake2b_state; BLAKE2_PACKED(struct blake2b_param__ { uint8_t digest_length; /* 1 */ uint8_t key_length; /* 2 */ uint8_t fanout; /* 3 */ uint8_t depth; /* 4 */ uint32_t leaf_length; /* 8 */ uint32_t node_offset; /* 12 */ uint32_t xof_length; /* 16 */ uint8_t node_depth; /* 17 */ uint8_t inner_length; /* 18 */ uint8_t reserved[14]; /* 32 */ uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */ uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */ }); typedef struct blake2b_param__ blake2b_param; ================================================ FILE: src/blake2b-ref.c ================================================ /* BLAKE2 reference source code package - reference C implementations Copyright 2012, Samuel Neves . You may use this under the terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at your option. The terms of these licenses can be found at: - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 - OpenSSL license : https://www.openssl.org/source/license.html - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 More information about the BLAKE2 hash function can be found at https://blake2.net. */ #include #include #include #include "blake2-impl.h" static const uint64_t blake2b_IV[8] = { 0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL }; static const uint8_t blake2b_sigma[12][16] = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } }; static int blake2b_update(blake2b_state* S, const void* in, size_t inlen); static void blake2b_set_lastnode( blake2b_state *S ) { S->f[1] = (uint64_t)-1; } /* Some helper functions, not necessarily useful */ static int blake2b_is_lastblock( const blake2b_state *S ) { return S->f[0] != 0; } static void blake2b_set_lastblock( blake2b_state *S ) { if( S->last_node ) blake2b_set_lastnode( S ); S->f[0] = (uint64_t)-1; } static void blake2b_increment_counter( blake2b_state *S, const uint64_t inc ) { S->t[0] += inc; S->t[1] += ( S->t[0] < inc ); } static void blake2b_init0( blake2b_state *S ) { size_t i; memset( S, 0, sizeof( blake2b_state ) ); for( i = 0; i < 8; ++i ) S->h[i] = blake2b_IV[i]; } /* init xors IV with input parameter block */ static void blake2b_init_param( blake2b_state *S, const blake2b_param *P ) { const uint8_t *p = ( const uint8_t * )( P ); size_t i; blake2b_init0( S ); /* IV XOR ParamBlock */ for( i = 0; i < 8; ++i ) S->h[i] ^= load64( p + sizeof( S->h[i] ) * i ); S->outlen = P->digest_length; } static void blake2b_init( blake2b_state *S, size_t outlen ) { blake2b_param P[1]; P->digest_length = (uint8_t)outlen; P->key_length = 0; P->fanout = 1; P->depth = 1; store32( &P->leaf_length, 0 ); store32( &P->node_offset, 0 ); store32( &P->xof_length, 0 ); P->node_depth = 0; P->inner_length = 0; memset( P->reserved, 0, sizeof( P->reserved ) ); memset( P->salt, 0, sizeof( P->salt ) ); memset( P->personal, 0, sizeof( P->personal ) ); blake2b_init_param( S, P ); } #define G(r,i,a,b,c,d) \ do { \ a = a + b + m[blake2b_sigma[r][2*i+0]]; \ d = rotr64(d ^ a, 32); \ c = c + d; \ b = rotr64(b ^ c, 24); \ a = a + b + m[blake2b_sigma[r][2*i+1]]; \ d = rotr64(d ^ a, 16); \ c = c + d; \ b = rotr64(b ^ c, 63); \ } while(0) #define ROUND(r) \ do { \ G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ G(r,2,v[ 2],v[ 6],v[10],v[14]); \ G(r,3,v[ 3],v[ 7],v[11],v[15]); \ G(r,4,v[ 0],v[ 5],v[10],v[15]); \ G(r,5,v[ 1],v[ 6],v[11],v[12]); \ G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \ } while(0) static void blake2b_compress( blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES] ) { uint64_t m[16]; uint64_t v[16]; size_t i; for( i = 0; i < 16; ++i ) { m[i] = load64( block + i * sizeof( m[i] ) ); } for( i = 0; i < 8; ++i ) { v[i] = S->h[i]; } v[ 8] = blake2b_IV[0]; v[ 9] = blake2b_IV[1]; v[10] = blake2b_IV[2]; v[11] = blake2b_IV[3]; v[12] = blake2b_IV[4] ^ S->t[0]; v[13] = blake2b_IV[5] ^ S->t[1]; v[14] = blake2b_IV[6] ^ S->f[0]; v[15] = blake2b_IV[7] ^ S->f[1]; ROUND( 0 ); ROUND( 1 ); ROUND( 2 ); ROUND( 3 ); ROUND( 4 ); ROUND( 5 ); ROUND( 6 ); ROUND( 7 ); ROUND( 8 ); ROUND( 9 ); ROUND( 10 ); ROUND( 11 ); for( i = 0; i < 8; ++i ) { S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; } } #undef G #undef ROUND static int blake2b_update( blake2b_state *S, const void *pin, size_t inlen ) { const unsigned char * in = (const unsigned char *)pin; if( inlen > 0 ) { size_t left = S->buflen; size_t fill = BLAKE2B_BLOCKBYTES - left; if( inlen > fill ) { S->buflen = 0; memcpy( S->buf + left, in, fill ); /* Fill buffer */ blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES ); blake2b_compress( S, S->buf ); /* Compress */ in += fill; inlen -= fill; while(inlen > BLAKE2B_BLOCKBYTES) { blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); blake2b_compress( S, in ); in += BLAKE2B_BLOCKBYTES; inlen -= BLAKE2B_BLOCKBYTES; } } memcpy( S->buf + S->buflen, in, inlen ); S->buflen += inlen; } return 0; } static int blake2b_final( blake2b_state *S, void *out, size_t outlen ) { uint8_t buffer[BLAKE2B_OUTBYTES] = {0}; size_t i; if( out == NULL || outlen < S->outlen ) return -1; if( blake2b_is_lastblock( S ) ) return -1; blake2b_increment_counter( S, S->buflen ); blake2b_set_lastblock( S ); memset( S->buf + S->buflen, 0, BLAKE2B_BLOCKBYTES - S->buflen ); /* Padding */ blake2b_compress( S, S->buf ); for( i = 0; i < 8; ++i ) /* Output full hash to temp buffer */ store64( buffer + sizeof( S->h[i] ) * i, S->h[i] ); memcpy( out, buffer, S->outlen ); return 0; } /* inlen, at least, should be uint64_t. Others can be size_t. */ void blake2b( void *out, size_t outlen, const void *in, size_t inlen ) { blake2b_state S[1]; blake2b_init( S, outlen ); blake2b_update( S, ( const uint8_t * )in, inlen ); blake2b_final( S, out, outlen ); } ================================================ FILE: src/boot.c ================================================ /* Copyright (c) Mark Harmstone 2019 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #ifdef _MSC_VER #include #endif extern ERESOURCE pdo_list_lock; extern LIST_ENTRY pdo_list; extern ERESOURCE boot_lock; extern PDRIVER_OBJECT drvobj; BTRFS_UUID boot_uuid; // initialized to 0 uint64_t boot_subvol = 0; // Not in any headers? Windbg knows about it though. #define DOE_START_PENDING 0x10 // Just as much as we need - the version in mingw is truncated still further typedef struct { CSHORT Type; USHORT Size; PDEVICE_OBJECT DeviceObject; ULONG PowerFlags; void* Dope; ULONG ExtensionFlags; } DEVOBJ_EXTENSION2; static bool get_system_root() { NTSTATUS Status; HANDLE h; UNICODE_STRING us, target; OBJECT_ATTRIBUTES objatt; ULONG retlen = 0; bool second_time = false; static const WCHAR system_root[] = L"\\SystemRoot"; static const WCHAR boot_device[] = L"\\Device\\BootDevice"; static const WCHAR arc_btrfs_prefix[] = L"\\ArcName\\btrfs("; us.Buffer = (WCHAR*)system_root; us.Length = us.MaximumLength = sizeof(system_root) - sizeof(WCHAR); InitializeObjectAttributes(&objatt, &us, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); while (true) { Status = ZwOpenSymbolicLinkObject(&h, GENERIC_READ, &objatt); if (!NT_SUCCESS(Status)) { ERR("ZwOpenSymbolicLinkObject returned %08lx\n", Status); return false; } target.Length = target.MaximumLength = 0; Status = ZwQuerySymbolicLinkObject(h, &target, &retlen); if (Status != STATUS_BUFFER_TOO_SMALL) { ERR("ZwQuerySymbolicLinkObject returned %08lx\n", Status); NtClose(h); return false; } if (retlen == 0) { NtClose(h); return false; } target.Buffer = ExAllocatePoolWithTag(NonPagedPool, retlen, ALLOC_TAG); if (!target.Buffer) { ERR("out of memory\n"); NtClose(h); return false; } target.Length = target.MaximumLength = (USHORT)retlen; Status = ZwQuerySymbolicLinkObject(h, &target, NULL); if (!NT_SUCCESS(Status)) { ERR("ZwQuerySymbolicLinkObject returned %08lx\n", Status); NtClose(h); ExFreePool(target.Buffer); return false; } NtClose(h); if (second_time) { TRACE("boot device is %.*S\n", (int)(target.Length / sizeof(WCHAR)), target.Buffer); } else { TRACE("system root is %.*S\n", (int)(target.Length / sizeof(WCHAR)), target.Buffer); } if (!second_time && target.Length >= sizeof(boot_device) - sizeof(WCHAR) && RtlCompareMemory(target.Buffer, boot_device, sizeof(boot_device) - sizeof(WCHAR)) == sizeof(boot_device) - sizeof(WCHAR)) { ExFreePool(target.Buffer); us.Buffer = (WCHAR*)boot_device; us.Length = us.MaximumLength = sizeof(boot_device) - sizeof(WCHAR); second_time = true; } else break; } if (target.Length >= sizeof(arc_btrfs_prefix) - sizeof(WCHAR) && RtlCompareMemory(target.Buffer, arc_btrfs_prefix, sizeof(arc_btrfs_prefix) - sizeof(WCHAR)) == sizeof(arc_btrfs_prefix) - sizeof(WCHAR)) { WCHAR* s = &target.Buffer[(sizeof(arc_btrfs_prefix) / sizeof(WCHAR)) - 1]; for (unsigned int i = 0; i < 16; i++) { if (*s >= '0' && *s <= '9') boot_uuid.uuid[i] = (*s - '0') << 4; else if (*s >= 'a' && *s <= 'f') boot_uuid.uuid[i] = (*s - 'a' + 0xa) << 4; else if (*s >= 'A' && *s <= 'F') boot_uuid.uuid[i] = (*s - 'A' + 0xa) << 4; else { ExFreePool(target.Buffer); return false; } s++; if (*s >= '0' && *s <= '9') boot_uuid.uuid[i] |= *s - '0'; else if (*s >= 'a' && *s <= 'f') boot_uuid.uuid[i] |= *s - 'a' + 0xa; else if (*s >= 'A' && *s <= 'F') boot_uuid.uuid[i] |= *s - 'A' + 0xa; else { ExFreePool(target.Buffer); return false; } s++; if (i == 3 || i == 5 || i == 7 || i == 9) { if (*s != '-') { ExFreePool(target.Buffer); return false; } s++; } } if (*s != ')') { ExFreePool(target.Buffer); return false; } ExFreePool(target.Buffer); return true; } ExFreePool(target.Buffer); return false; } static void mountmgr_notification(BTRFS_UUID* uuid) { UNICODE_STRING mmdevpath; NTSTATUS Status; PFILE_OBJECT FileObject; PDEVICE_OBJECT mountmgr; ULONG mmtnlen; MOUNTMGR_TARGET_NAME* mmtn; WCHAR* w; RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME); Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr); if (!NT_SUCCESS(Status)) { ERR("IoGetDeviceObjectPointer returned %08lx\n", Status); return; } mmtnlen = offsetof(MOUNTMGR_TARGET_NAME, DeviceName[0]) + sizeof(BTRFS_VOLUME_PREFIX) + (36 * sizeof(WCHAR)); mmtn = ExAllocatePoolWithTag(NonPagedPool, mmtnlen, ALLOC_TAG); if (!mmtn) { ERR("out of memory\n"); return; } mmtn->DeviceNameLength = sizeof(BTRFS_VOLUME_PREFIX) + (36 * sizeof(WCHAR)); RtlCopyMemory(mmtn->DeviceName, BTRFS_VOLUME_PREFIX, sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)); w = &mmtn->DeviceName[(sizeof(BTRFS_VOLUME_PREFIX) / sizeof(WCHAR)) - 1]; for (unsigned int i = 0; i < 16; i++) { *w = hex_digit(uuid->uuid[i] >> 4); w++; *w = hex_digit(uuid->uuid[i] & 0xf); w++; if (i == 3 || i == 5 || i == 7 || i == 9) { *w = L'-'; w++; } } *w = L'}'; Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION, mmtn, mmtnlen, NULL, 0, false, NULL); if (!NT_SUCCESS(Status)) { ERR("IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION returned %08lx\n", Status); ExFreePool(mmtn); return; } ExFreePool(mmtn); } static void check_boot_options() { NTSTATUS Status; WCHAR* s; static const WCHAR pathw[] = L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control"; static const WCHAR namew[] = L"SystemStartOptions"; static const WCHAR subvol[] = L"SUBVOL="; try { HANDLE control; OBJECT_ATTRIBUTES oa; UNICODE_STRING path; ULONG kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) - sizeof(WCHAR) + (255 * sizeof(WCHAR)); KEY_VALUE_FULL_INFORMATION* kvfi; UNICODE_STRING name; WCHAR* options; path.Buffer = (WCHAR*)pathw; path.Length = path.MaximumLength = sizeof(pathw) - sizeof(WCHAR); InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwOpenKey(&control, KEY_QUERY_VALUE, &oa); if (!NT_SUCCESS(Status)) { ERR("ZwOpenKey returned %08lx\n", Status); return; } // FIXME - don't fail if value too long (can we query for the length?) kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); NtClose(control); return; } name.Buffer = (WCHAR*)namew; name.Length = name.MaximumLength = sizeof(namew) - sizeof(WCHAR); Status = ZwQueryValueKey(control, &name, KeyValueFullInformation, kvfi, kvfilen, &kvfilen); if (!NT_SUCCESS(Status)) { ERR("ZwQueryValueKey returned %08lx\n", Status); NtClose(control); return; } NtClose(control); options = (WCHAR*)((uint8_t*)kvfi + kvfi->DataOffset); options[kvfi->DataLength / sizeof(WCHAR)] = 0; // FIXME - make sure buffer long enough to allow this s = wcsstr(options, subvol); if (!s) return; s += (sizeof(subvol) / sizeof(WCHAR)) - 1; boot_subvol = 0; while (true) { if (*s >= '0' && *s <= '9') { boot_subvol <<= 4; boot_subvol |= *s - '0'; } else if (*s >= 'a' && *s <= 'f') { boot_subvol <<= 4; boot_subvol |= *s - 'a' + 0xa; } else if (*s >= 'A' && *s <= 'F') { boot_subvol <<= 4; boot_subvol |= *s - 'A' + 0xa; } else break; s++; } } except (EXCEPTION_EXECUTE_HANDLER) { return; } if (boot_subvol != 0) { TRACE("passed subvol %I64x in boot options\n", boot_subvol); } } void boot_add_device(DEVICE_OBJECT* pdo) { pdo_device_extension* pdode = pdo->DeviceExtension; AddDevice(drvobj, pdo); // To stop Windows sneakily setting DOE_START_PENDING pdode->dont_report = true; if (pdo->DeviceObjectExtension) { ((DEVOBJ_EXTENSION2*)pdo->DeviceObjectExtension)->ExtensionFlags &= ~DOE_START_PENDING; if (pdode && pdode->vde && pdode->vde->device) ((DEVOBJ_EXTENSION2*)pdode->vde->device->DeviceObjectExtension)->ExtensionFlags &= ~DOE_START_PENDING; } mountmgr_notification(&pdode->uuid); } void check_system_root() { LIST_ENTRY* le; PDEVICE_OBJECT pdo_to_add = NULL; TRACE("()\n"); // wait for any PNP notifications in progress to finish ExAcquireResourceExclusiveLite(&boot_lock, TRUE); ExReleaseResourceLite(&boot_lock); if (!get_system_root()) return; ExAcquireResourceSharedLite(&pdo_list_lock, true); le = pdo_list.Flink; while (le != &pdo_list) { pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry); if (RtlCompareMemory(&pdode->uuid, &boot_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { if (!pdode->vde) pdo_to_add = pdode->pdo; else if (pdode->vde->device && !(pdode->vde->device->Flags & DO_SYSTEM_BOOT_PARTITION)) { // AddDevice has beaten us to it NTSTATUS Status; pdode->vde->device->Flags |= DO_SYSTEM_BOOT_PARTITION; pdode->pdo->Flags |= DO_SYSTEM_BOOT_PARTITION; Status = IoSetDeviceInterfaceState(&pdode->vde->bus_name, false); if (!NT_SUCCESS(Status)) ERR("IoSetDeviceInterfaceState returned %08lx\n", Status); Status = IoSetDeviceInterfaceState(&pdode->vde->bus_name, true); if (!NT_SUCCESS(Status)) ERR("IoSetDeviceInterfaceState returned %08lx\n", Status); } break; } le = le->Flink; } ExReleaseResourceLite(&pdo_list_lock); check_boot_options(); // If our FS depends on volumes that aren't there when we do our IoRegisterPlugPlayNotification calls // in DriverEntry, bus_query_device_relations won't get called until it's too late. We need to do our // own call to AddDevice here as a result. We need to clear the DOE_START_PENDING bits, or NtOpenFile // will return STATUS_NO_SUCH_DEVICE. if (pdo_to_add) boot_add_device(pdo_to_add); } ================================================ FILE: src/btrfs-vol.inf ================================================ ;;; ;;; WinBtrfs ;;; ;;; ;;; Copyright (c) 2016-24 Mark Harmstone ;;; [Version] Signature = "$Windows NT$" Class = Volume ClassGuid = {71a27cdd-812a-11d0-bec7-08002be2092f} Provider = %Me% DriverVer = 03/15/2024,1.9.0.0 CatalogFile = btrfs.cat PnpLockdown = 1 [DestinationDirs] Btrfs.DriverFiles = 12 ;%windir%\system32\drivers [Manufacturer] %Me%=Standard,NTamd64,NTx86,NTarm,NTarm64 [Standard.NTamd64] %VolumeName% = Btrfs_Install, BtrfsVolume %ControllerName% = Btrfs_Install, ROOT\btrfs [Standard.NTx86] %VolumeName% = Btrfs_Install, BtrfsVolume %ControllerName% = Btrfs_Install, ROOT\btrfs [Standard.NTarm] %VolumeName% = Btrfs_Install, BtrfsVolume %ControllerName% = Btrfs_Install, ROOT\btrfs [Standard.NTarm64] %VolumeName% = Btrfs_Install, BtrfsVolume %ControllerName% = Btrfs_Install, ROOT\btrfs [Btrfs_Install] OptionDesc = %ServiceDescription% CopyFiles = Btrfs.DriverFiles [Btrfs_Install.Services] AddService = %ServiceName%,2,Btrfs.Service ; ; Services Section ; [Btrfs.Service] DisplayName = %ServiceName% Description = %ServiceDescription% ServiceBinary = %12%\%DriverName%.sys ;%windir%\system32\drivers\ ServiceType = 1 StartType = 1 ;SERVICE_SYSTEM_START ErrorControl = 1 LoadOrderGroup = "File System" ; ; Copy Files ; [Btrfs.DriverFiles] %DriverName%.sys [SourceDisksFiles] btrfs.sys = 1,, [SourceDisksNames.x86] 1 = %DiskId1%,,,\x86 [SourceDisksNames.amd64] 1 = %DiskId1%,,,\amd64 [SourceDisksNames.arm] 1 = %DiskId1%,,,\arm [SourceDisksNames.arm64] 1 = %DiskId1%,,,\aarch64 ;; ;; String Section ;; [Strings] Me = "Mark Harmstone" ServiceDescription = "Btrfs driver" ServiceName = "btrfs" DriverName = "btrfs" DiskId1 = "Btrfs Device Installation Disk" VolumeName = "Btrfs volume" ControllerName = "Btrfs controller" REG_EXPAND_SZ = 0x00020000 ================================================ FILE: src/btrfs.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #ifdef _DEBUG #define DEBUG #endif #include "btrfs_drv.h" #include "zstd/lib/common/xxhash.h" #include "crc32c.h" #ifndef _MSC_VER #include #else #include #endif #include #include "btrfs.h" #include #ifndef _MSC_VER #include #include #undef INITGUID #endif #include #include #ifdef _MSC_VER #include #include #undef INITGUID #endif #ifdef _ARM64_ #define ARM64_ID_AA64ISAR0_EL1 0x4030 #endif #include #define INCOMPAT_SUPPORTED (BTRFS_INCOMPAT_FLAGS_MIXED_BACKREF | BTRFS_INCOMPAT_FLAGS_DEFAULT_SUBVOL | BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS | \ BTRFS_INCOMPAT_FLAGS_COMPRESS_LZO | BTRFS_INCOMPAT_FLAGS_BIG_METADATA | BTRFS_INCOMPAT_FLAGS_RAID56 | \ BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF | BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA | BTRFS_INCOMPAT_FLAGS_NO_HOLES | \ BTRFS_INCOMPAT_FLAGS_COMPRESS_ZSTD | BTRFS_INCOMPAT_FLAGS_METADATA_UUID | BTRFS_INCOMPAT_FLAGS_RAID1C34) #define COMPAT_RO_SUPPORTED (BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE | BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID | \ BTRFS_COMPAT_RO_FLAGS_VERITY | BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE) static const WCHAR device_name[] = {'\\','B','t','r','f','s',0}; static const WCHAR dosdevice_name[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\','B','t','r','f','s',0}; DEFINE_GUID(BtrfsBusInterface, 0x4d414874, 0x6865, 0x6761, 0x6d, 0x65, 0x83, 0x69, 0x17, 0x9a, 0x7d, 0x1d); PDRIVER_OBJECT drvobj; PDEVICE_OBJECT master_devobj, busobj; LIST_ENTRY uid_map_list, gid_map_list; LIST_ENTRY VcbList; ERESOURCE global_loading_lock; uint32_t debug_log_level = 0; uint32_t mount_compress = 0; uint32_t mount_compress_force = 0; uint32_t mount_compress_type = 0; uint32_t mount_zlib_level = 3; uint32_t mount_zstd_level = 3; uint32_t mount_flush_interval = 30; uint32_t mount_max_inline = 2048; uint32_t mount_skip_balance = 0; uint32_t mount_no_barrier = 0; uint32_t mount_no_trim = 0; uint32_t mount_clear_cache = 0; uint32_t mount_allow_degraded = 0; uint32_t mount_readonly = 0; uint32_t mount_no_root_dir = 0; uint32_t mount_nodatacow = 0; uint32_t no_pnp = 0; bool log_started = false; UNICODE_STRING log_device, log_file, registry_path; tPsUpdateDiskCounters fPsUpdateDiskCounters; tCcCopyReadEx fCcCopyReadEx; tCcCopyWriteEx fCcCopyWriteEx; tCcSetAdditionalCacheAttributesEx fCcSetAdditionalCacheAttributesEx; tFsRtlUpdateDiskCounters fFsRtlUpdateDiskCounters; tIoUnregisterPlugPlayNotificationEx fIoUnregisterPlugPlayNotificationEx; tFsRtlGetEcpListFromIrp fFsRtlGetEcpListFromIrp; tFsRtlGetNextExtraCreateParameter fFsRtlGetNextExtraCreateParameter; tFsRtlValidateReparsePointBuffer fFsRtlValidateReparsePointBuffer; tFsRtlCheckLockForOplockRequest fFsRtlCheckLockForOplockRequest; tFsRtlAreThereCurrentOrInProgressFileLocks fFsRtlAreThereCurrentOrInProgressFileLocks; bool diskacc = false; void *notification_entry = NULL, *notification_entry2 = NULL, *notification_entry3 = NULL; ERESOURCE pdo_list_lock, mapping_lock; LIST_ENTRY pdo_list; bool finished_probing = false; HANDLE degraded_wait_handle = NULL, mountmgr_thread_handle = NULL; bool degraded_wait = true; KEVENT mountmgr_thread_event; bool shutting_down = false; ERESOURCE boot_lock; bool is_windows_8; extern uint64_t boot_subvol; #ifdef _DEBUG PFILE_OBJECT comfo = NULL; PDEVICE_OBJECT comdo = NULL; HANDLE log_handle = NULL; ERESOURCE log_lock; HANDLE serial_thread_handle = NULL; static void init_serial(bool first_time); #endif static NTSTATUS close_file(_In_ PFILE_OBJECT FileObject, _In_ PIRP Irp); static void __stdcall do_xor_basic(uint8_t* buf1, uint8_t* buf2, uint32_t len); xor_func do_xor = do_xor_basic; typedef struct { KEVENT Event; IO_STATUS_BLOCK iosb; } read_context; // no longer in Windows headers?? extern BOOLEAN WdmlibRtlIsNtDdiVersionAvailable(ULONG Version); #ifdef _DEBUG _Function_class_(IO_COMPLETION_ROUTINE) static NTSTATUS __stdcall dbg_completion(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp, _In_ PVOID conptr) { read_context* context = conptr; UNUSED(DeviceObject); context->iosb = Irp->IoStatus; KeSetEvent(&context->Event, 0, false); return STATUS_MORE_PROCESSING_REQUIRED; } #define DEBUG_MESSAGE_LEN 1024 #ifdef DEBUG_LONG_MESSAGES void _debug_message(_In_ const char* func, _In_ const char* file, _In_ unsigned int line, _In_ char* s, ...) { #else void _debug_message(_In_ const char* func, _In_ char* s, ...) { #endif LARGE_INTEGER offset; PIO_STACK_LOCATION IrpSp; NTSTATUS Status; PIRP Irp; va_list ap; char *buf2, *buf; read_context context; uint32_t length; buf2 = ExAllocatePoolWithTag(NonPagedPool, DEBUG_MESSAGE_LEN, ALLOC_TAG); if (!buf2) { DbgPrint("Couldn't allocate buffer in debug_message\n"); return; } #ifdef DEBUG_LONG_MESSAGES sprintf(buf2, "%p:%s:%s:%u:", (void*)PsGetCurrentThread(), func, file, line); #else sprintf(buf2, "%p:%s:", (void*)PsGetCurrentThread(), func); #endif buf = &buf2[strlen(buf2)]; va_start(ap, s); RtlStringCbVPrintfA(buf, DEBUG_MESSAGE_LEN - strlen(buf2), s, ap); ExAcquireResourceSharedLite(&log_lock, true); if (!log_started || (log_device.Length == 0 && log_file.Length == 0)) { DbgPrint(buf2); } else if (log_device.Length > 0) { if (!comdo) { DbgPrint(buf2); goto exit2; } length = (uint32_t)strlen(buf2); offset.u.LowPart = 0; offset.u.HighPart = 0; RtlZeroMemory(&context, sizeof(read_context)); KeInitializeEvent(&context.Event, NotificationEvent, false); Irp = IoAllocateIrp(comdo->StackSize, false); if (!Irp) { DbgPrint("IoAllocateIrp failed\n"); goto exit2; } IrpSp = IoGetNextIrpStackLocation(Irp); IrpSp->MajorFunction = IRP_MJ_WRITE; IrpSp->FileObject = comfo; if (comdo->Flags & DO_BUFFERED_IO) { Irp->AssociatedIrp.SystemBuffer = buf2; Irp->Flags = IRP_BUFFERED_IO; } else if (comdo->Flags & DO_DIRECT_IO) { Irp->MdlAddress = IoAllocateMdl(buf2, length, false, false, NULL); if (!Irp->MdlAddress) { DbgPrint("IoAllocateMdl failed\n"); goto exit; } MmBuildMdlForNonPagedPool(Irp->MdlAddress); } else { Irp->UserBuffer = buf2; } IrpSp->Parameters.Write.Length = length; IrpSp->Parameters.Write.ByteOffset = offset; Irp->UserIosb = &context.iosb; Irp->UserEvent = &context.Event; IoSetCompletionRoutine(Irp, dbg_completion, &context, true, true, true); Status = IoCallDriver(comdo, Irp); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL); Status = context.iosb.Status; } if (comdo->Flags & DO_DIRECT_IO) IoFreeMdl(Irp->MdlAddress); if (!NT_SUCCESS(Status)) { DbgPrint("failed to write to COM1 - error %08lx\n", Status); goto exit; } exit: IoFreeIrp(Irp); } else if (log_handle != NULL) { IO_STATUS_BLOCK iosb; length = (uint32_t)strlen(buf2); Status = ZwWriteFile(log_handle, NULL, NULL, NULL, &iosb, buf2, length, NULL, NULL); if (!NT_SUCCESS(Status)) { DbgPrint("failed to write to file - error %08lx\n", Status); } } exit2: ExReleaseResourceLite(&log_lock); va_end(ap); if (buf2) ExFreePool(buf2); } #endif bool is_top_level(_In_ PIRP Irp) { if (!IoGetTopLevelIrp()) { IoSetTopLevelIrp(Irp); return true; } return false; } static void __stdcall do_xor_basic(uint8_t* buf1, uint8_t* buf2, uint32_t len) { uint32_t j; #if defined(_ARM_) || defined(_ARM64_) uint64x2_t x1, x2; if (((uintptr_t)buf1 & 0xf) == 0 && ((uintptr_t)buf2 & 0xf) == 0) { while (len >= 16) { x1 = vld1q_u64((const uint64_t*)buf1); x2 = vld1q_u64((const uint64_t*)buf2); x1 = veorq_u64(x1, x2); vst1q_u64((uint64_t*)buf1, x1); buf1 += 16; buf2 += 16; len -= 16; } } #endif #if defined(_AMD64_) || defined(_ARM64_) while (len > 8) { *(uint64_t*)buf1 ^= *(uint64_t*)buf2; buf1 += 8; buf2 += 8; len -= 8; } #endif while (len > 4) { *(uint32_t*)buf1 ^= *(uint32_t*)buf2; buf1 += 4; buf2 += 4; len -= 4; } for (j = 0; j < len; j++) { *buf1 ^= *buf2; buf1++; buf2++; } } _Function_class_(DRIVER_UNLOAD) static void __stdcall DriverUnload(_In_ PDRIVER_OBJECT DriverObject) { UNICODE_STRING dosdevice_nameW; TRACE("(%p)\n", DriverObject); dosdevice_nameW.Buffer = (WCHAR*)dosdevice_name; dosdevice_nameW.Length = dosdevice_nameW.MaximumLength = sizeof(dosdevice_name) - sizeof(WCHAR); IoDeleteSymbolicLink(&dosdevice_nameW); IoDeleteDevice(DriverObject->DeviceObject); while (!IsListEmpty(&uid_map_list)) { LIST_ENTRY* le = RemoveHeadList(&uid_map_list); uid_map* um = CONTAINING_RECORD(le, uid_map, listentry); ExFreePool(um->sid); ExFreePool(um); } while (!IsListEmpty(&gid_map_list)) { gid_map* gm = CONTAINING_RECORD(RemoveHeadList(&gid_map_list), gid_map, listentry); ExFreePool(gm->sid); ExFreePool(gm); } // FIXME - free volumes and their devpaths #ifdef _DEBUG if (comfo) ObDereferenceObject(comfo); if (log_handle) ZwClose(log_handle); #endif ExDeleteResourceLite(&global_loading_lock); ExDeleteResourceLite(&pdo_list_lock); if (log_device.Buffer) ExFreePool(log_device.Buffer); if (log_file.Buffer) ExFreePool(log_file.Buffer); if (registry_path.Buffer) ExFreePool(registry_path.Buffer); #ifdef _DEBUG ExDeleteResourceLite(&log_lock); #endif ExDeleteResourceLite(&mapping_lock); } static bool get_last_inode(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _In_opt_ PIRP Irp) { KEY searchkey; traverse_ptr tp, prev_tp; NTSTATUS Status; // get last entry searchkey.obj_id = 0xffffffffffffffff; searchkey.obj_type = 0xff; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, r, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return false; } 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) { r->lastinode = tp.item->key.obj_id; TRACE("last inode for tree %I64x is %I64x\n", r->id, r->lastinode); return true; } while (find_prev_item(Vcb, &tp, &prev_tp, Irp)) { tp = prev_tp; TRACE("moving on to %I64x,%x,%I64x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); 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) { r->lastinode = tp.item->key.obj_id; TRACE("last inode for tree %I64x is %I64x\n", r->id, r->lastinode); return true; } } r->lastinode = SUBVOL_ROOT_INODE; WARN("no INODE_ITEMs in tree %I64x\n", r->id); return true; } _Success_(return) static bool extract_xattr(_In_reads_bytes_(size) void* item, _In_ USHORT size, _In_z_ char* name, _Out_ uint8_t** data, _Out_ uint16_t* datalen) { DIR_ITEM* xa = (DIR_ITEM*)item; USHORT xasize; while (true) { if (size < sizeof(DIR_ITEM) || size < (sizeof(DIR_ITEM) - 1 + xa->m + xa->n)) { WARN("DIR_ITEM is truncated\n"); return false; } if (xa->n == strlen(name) && RtlCompareMemory(name, xa->name, xa->n) == xa->n) { TRACE("found xattr %s\n", name); *datalen = xa->m; if (xa->m > 0) { *data = ExAllocatePoolWithTag(PagedPool, xa->m, ALLOC_TAG); if (!*data) { ERR("out of memory\n"); return false; } RtlCopyMemory(*data, &xa->name[xa->n], xa->m); } else *data = NULL; return true; } xasize = sizeof(DIR_ITEM) - 1 + xa->m + xa->n; if (size > xasize) { size -= xasize; xa = (DIR_ITEM*)&xa->name[xa->m + xa->n]; } else break; } TRACE("xattr %s not found\n", name); return false; } _Success_(return) bool 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, _Out_ uint8_t** data, _Out_ uint16_t* datalen, _In_opt_ PIRP Irp) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; TRACE("(%p, %I64x, %I64x, %s, %08x, %p, %p)\n", Vcb, subvol->id, inode, name, crc32, data, datalen); searchkey.obj_id = inode; searchkey.obj_type = TYPE_XATTR_ITEM; searchkey.offset = crc32; Status = find_item(Vcb, subvol, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return false; } if (keycmp(tp.item->key, searchkey)) { TRACE("could not find item (%I64x,%x,%I64x)\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); return false; } if (tp.item->size < sizeof(DIR_ITEM)) { 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)); return false; } return extract_xattr(tp.item->data, tp.item->size, name, data, datalen); } _Dispatch_type_(IRP_MJ_CLOSE) _Function_class_(DRIVER_DISPATCH) static NTSTATUS __stdcall drv_close(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { NTSTATUS Status; PIO_STACK_LOCATION IrpSp; device_extension* Vcb = DeviceObject->DeviceExtension; bool top_level; FsRtlEnterFileSystem(); TRACE("close\n"); top_level = is_top_level(Irp); if (DeviceObject == master_devobj) { TRACE("Closing file system\n"); Status = STATUS_SUCCESS; goto end; } else if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = vol_close(DeviceObject, Irp); goto end; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } IrpSp = IoGetCurrentIrpStackLocation(Irp); // FIXME - call FsRtlNotifyUninitializeSync(&Vcb->NotifySync) if unmounting Status = close_file(IrpSp->FileObject, Irp); end: Irp->IoStatus.Status = Status; Irp->IoStatus.Information = 0; IoCompleteRequest( Irp, IO_DISK_INCREMENT ); if (top_level) IoSetTopLevelIrp(NULL); TRACE("returning %08lx\n", Status); FsRtlExitFileSystem(); return Status; } _Dispatch_type_(IRP_MJ_FLUSH_BUFFERS) _Function_class_(DRIVER_DISPATCH) static NTSTATUS __stdcall drv_flush_buffers(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { NTSTATUS Status; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp ); PFILE_OBJECT FileObject = IrpSp->FileObject; fcb* fcb = FileObject->FsContext; device_extension* Vcb = DeviceObject->DeviceExtension; bool top_level; FsRtlEnterFileSystem(); TRACE("flush buffers\n"); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = STATUS_SUCCESS; goto end; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_SUCCESS; goto end; } if (!fcb) { ERR("fcb was NULL\n"); Status = STATUS_SUCCESS; goto end; } if (fcb == Vcb->volume_fcb) { Status = STATUS_SUCCESS; goto end; } FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL); Irp->IoStatus.Information = 0; fcb->Header.IsFastIoPossible = fast_io_possible(fcb); Status = STATUS_SUCCESS; Irp->IoStatus.Status = Status; if (fcb->type != BTRFS_TYPE_DIRECTORY) { CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &Irp->IoStatus); if (fcb->Header.PagingIoResource) { ExAcquireResourceExclusiveLite(fcb->Header.PagingIoResource, true); ExReleaseResourceLite(fcb->Header.PagingIoResource); } Status = Irp->IoStatus.Status; } end: IoCompleteRequest(Irp, IO_NO_INCREMENT); TRACE("returning %08lx\n", Status); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } static void calculate_total_space(_In_ device_extension* Vcb, _Out_ uint64_t* totalsize, _Out_ uint64_t* freespace) { uint64_t nfactor, dfactor, sectors_used; if (Vcb->data_flags & BLOCK_FLAG_DUPLICATE || Vcb->data_flags & BLOCK_FLAG_RAID1 || Vcb->data_flags & BLOCK_FLAG_RAID10) { nfactor = 1; dfactor = 2; } else if (Vcb->data_flags & BLOCK_FLAG_RAID5) { nfactor = Vcb->superblock.num_devices - 1; dfactor = Vcb->superblock.num_devices; } else if (Vcb->data_flags & BLOCK_FLAG_RAID6) { nfactor = Vcb->superblock.num_devices - 2; dfactor = Vcb->superblock.num_devices; } else if (Vcb->data_flags & BLOCK_FLAG_RAID1C3) { nfactor = 1; dfactor = 3; } else if (Vcb->data_flags & BLOCK_FLAG_RAID1C4) { nfactor = 1; dfactor = 4; } else { nfactor = 1; dfactor = 1; } sectors_used = (Vcb->superblock.bytes_used >> Vcb->sector_shift) * nfactor / dfactor; *totalsize = (Vcb->superblock.total_bytes >> Vcb->sector_shift) * nfactor / dfactor; *freespace = sectors_used > *totalsize ? 0 : (*totalsize - sectors_used); } // simplified version of FsRtlAreNamesEqual, which can be a bottleneck! static bool compare_strings(const UNICODE_STRING* us1, const UNICODE_STRING* us2) { if (us1->Length != us2->Length) return false; WCHAR* s1 = us1->Buffer; WCHAR* s2 = us2->Buffer; for (unsigned int i = 0; i < us1->Length / sizeof(WCHAR); i++) { WCHAR c1 = *s1; WCHAR c2 = *s2; if (c1 != c2) { if (c1 >= 'a' && c1 <= 'z') c1 = c1 - 'a' + 'A'; if (c2 >= 'a' && c2 <= 'z') c2 = c2 - 'a' + 'A'; if (c1 != c2) return false; } s1++; s2++; } return true; } #define INIT_UNICODE_STRING(var, val) UNICODE_STRING us##var; us##var.Buffer = (WCHAR*)val; us##var.Length = us##var.MaximumLength = sizeof(val) - sizeof(WCHAR); // This function exists because we have to lie about our FS type in certain situations. // MPR!MprGetConnection queries the FS type, and compares it to a whitelist. If it doesn't match, // it will return ERROR_NO_NET_OR_BAD_PATH, which prevents UAC from working. // The command mklink refuses to create hard links on anything other than NTFS, so we have to // blacklist cmd.exe too. static bool lie_about_fs_type() { NTSTATUS Status; PROCESS_BASIC_INFORMATION pbi; PPEB peb; LIST_ENTRY* le; ULONG retlen; #ifdef _AMD64_ ULONG_PTR wow64info; #endif INIT_UNICODE_STRING(mpr, L"MPR.DLL"); INIT_UNICODE_STRING(cmd, L"CMD.EXE"); INIT_UNICODE_STRING(fsutil, L"FSUTIL.EXE"); INIT_UNICODE_STRING(storsvc, L"STORSVC.DLL"); /* Not doing a Volkswagen, honest! Some IFS tests won't run if not recognized FS. */ INIT_UNICODE_STRING(ifstest, L"IFSTEST.EXE"); if (!PsGetCurrentProcess()) return false; #ifdef _AMD64_ Status = ZwQueryInformationProcess(NtCurrentProcess(), ProcessWow64Information, &wow64info, sizeof(wow64info), NULL); if (NT_SUCCESS(Status) && wow64info != 0) return true; #endif Status = ZwQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &pbi, sizeof(pbi), &retlen); if (!NT_SUCCESS(Status)) { ERR("ZwQueryInformationProcess returned %08lx\n", Status); return false; } if (!pbi.PebBaseAddress) return false; peb = pbi.PebBaseAddress; if (!peb->Ldr) return false; le = peb->Ldr->InMemoryOrderModuleList.Flink; while (le != &peb->Ldr->InMemoryOrderModuleList) { LDR_DATA_TABLE_ENTRY* entry = CONTAINING_RECORD(le, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); bool blacklist = false; if (entry->FullDllName.Length >= usmpr.Length) { UNICODE_STRING name; name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - usmpr.Length) / sizeof(WCHAR)]; name.Length = name.MaximumLength = usmpr.Length; blacklist = compare_strings(&name, &usmpr); } if (!blacklist && entry->FullDllName.Length >= uscmd.Length) { UNICODE_STRING name; name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - uscmd.Length) / sizeof(WCHAR)]; name.Length = name.MaximumLength = uscmd.Length; blacklist = compare_strings(&name, &uscmd); } if (!blacklist && entry->FullDllName.Length >= usfsutil.Length) { UNICODE_STRING name; name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - usfsutil.Length) / sizeof(WCHAR)]; name.Length = name.MaximumLength = usfsutil.Length; blacklist = compare_strings(&name, &usfsutil); } if (!blacklist && entry->FullDllName.Length >= usstorsvc.Length) { UNICODE_STRING name; name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - usstorsvc.Length) / sizeof(WCHAR)]; name.Length = name.MaximumLength = usstorsvc.Length; blacklist = compare_strings(&name, &usstorsvc); } if (!blacklist && entry->FullDllName.Length >= usifstest.Length) { UNICODE_STRING name; name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - usifstest.Length) / sizeof(WCHAR)]; name.Length = name.MaximumLength = usifstest.Length; blacklist = compare_strings(&name, &usifstest); } if (blacklist) { void** frames; ULONG i, num_frames; frames = ExAllocatePoolWithTag(PagedPool, 256 * sizeof(void*), ALLOC_TAG); if (!frames) { ERR("out of memory\n"); return false; } num_frames = RtlWalkFrameChain(frames, 256, 1); for (i = 0; i < num_frames; i++) { // entry->Reserved3[1] appears to be the image size if (frames[i] >= entry->DllBase && (ULONG_PTR)frames[i] <= (ULONG_PTR)entry->DllBase + (ULONG_PTR)entry->Reserved3[1]) { ExFreePool(frames); return true; } } ExFreePool(frames); } le = le->Flink; } return false; } // version of RtlUTF8ToUnicodeN for Vista and below NTSTATUS utf8_to_utf16(WCHAR* dest, ULONG dest_max, ULONG* dest_len, char* src, ULONG src_len) { NTSTATUS Status = STATUS_SUCCESS; uint8_t* in = (uint8_t*)src; uint16_t* out = (uint16_t*)dest; ULONG needed = 0, left = dest_max / sizeof(uint16_t); for (ULONG i = 0; i < src_len; i++) { uint32_t cp; if (!(in[i] & 0x80)) cp = in[i]; else if ((in[i] & 0xe0) == 0xc0) { if (i == src_len - 1 || (in[i+1] & 0xc0) != 0x80) { cp = 0xfffd; Status = STATUS_SOME_NOT_MAPPED; } else { cp = ((in[i] & 0x1f) << 6) | (in[i+1] & 0x3f); i++; } } else if ((in[i] & 0xf0) == 0xe0) { if (i >= src_len - 2 || (in[i+1] & 0xc0) != 0x80 || (in[i+2] & 0xc0) != 0x80) { cp = 0xfffd; Status = STATUS_SOME_NOT_MAPPED; } else { cp = ((in[i] & 0xf) << 12) | ((in[i+1] & 0x3f) << 6) | (in[i+2] & 0x3f); i += 2; } } else if ((in[i] & 0xf8) == 0xf0) { if (i >= src_len - 3 || (in[i+1] & 0xc0) != 0x80 || (in[i+2] & 0xc0) != 0x80 || (in[i+3] & 0xc0) != 0x80) { cp = 0xfffd; Status = STATUS_SOME_NOT_MAPPED; } else { cp = ((in[i] & 0x7) << 18) | ((in[i+1] & 0x3f) << 12) | ((in[i+2] & 0x3f) << 6) | (in[i+3] & 0x3f); i += 3; } } else { cp = 0xfffd; Status = STATUS_SOME_NOT_MAPPED; } if (cp > 0x10ffff) { cp = 0xfffd; Status = STATUS_SOME_NOT_MAPPED; } if (dest) { if (cp <= 0xffff) { if (left < 1) return STATUS_BUFFER_OVERFLOW; *out = (uint16_t)cp; out++; left--; } else { if (left < 2) return STATUS_BUFFER_OVERFLOW; cp -= 0x10000; *out = 0xd800 | ((cp & 0xffc00) >> 10); out++; *out = 0xdc00 | (cp & 0x3ff); out++; left -= 2; } } if (cp <= 0xffff) needed += sizeof(uint16_t); else needed += 2 * sizeof(uint16_t); } if (dest_len) *dest_len = needed; return Status; } // version of RtlUnicodeToUTF8N for Vista and below NTSTATUS utf16_to_utf8(char* dest, ULONG dest_max, ULONG* dest_len, WCHAR* src, ULONG src_len) { NTSTATUS Status = STATUS_SUCCESS; uint16_t* in = (uint16_t*)src; uint8_t* out = (uint8_t*)dest; ULONG in_len = src_len / sizeof(uint16_t); ULONG needed = 0, left = dest_max; for (ULONG i = 0; i < in_len; i++) { uint32_t cp = *in; in++; if ((cp & 0xfc00) == 0xd800) { if (i == in_len - 1 || (*in & 0xfc00) != 0xdc00) { cp = 0xfffd; Status = STATUS_SOME_NOT_MAPPED; } else { cp = (cp & 0x3ff) << 10; cp |= *in & 0x3ff; cp += 0x10000; in++; i++; } } else if ((cp & 0xfc00) == 0xdc00) { cp = 0xfffd; Status = STATUS_SOME_NOT_MAPPED; } if (cp > 0x10ffff) { cp = 0xfffd; Status = STATUS_SOME_NOT_MAPPED; } if (dest) { if (cp < 0x80) { if (left < 1) return STATUS_BUFFER_OVERFLOW; *out = (uint8_t)cp; out++; left--; } else if (cp < 0x800) { if (left < 2) return STATUS_BUFFER_OVERFLOW; *out = 0xc0 | ((cp & 0x7c0) >> 6); out++; *out = 0x80 | (cp & 0x3f); out++; left -= 2; } else if (cp < 0x10000) { if (left < 3) return STATUS_BUFFER_OVERFLOW; *out = 0xe0 | ((cp & 0xf000) >> 12); out++; *out = 0x80 | ((cp & 0xfc0) >> 6); out++; *out = 0x80 | (cp & 0x3f); out++; left -= 3; } else { if (left < 4) return STATUS_BUFFER_OVERFLOW; *out = 0xf0 | ((cp & 0x1c0000) >> 18); out++; *out = 0x80 | ((cp & 0x3f000) >> 12); out++; *out = 0x80 | ((cp & 0xfc0) >> 6); out++; *out = 0x80 | (cp & 0x3f); out++; left -= 4; } } if (cp < 0x80) needed++; else if (cp < 0x800) needed += 2; else if (cp < 0x10000) needed += 3; else needed += 4; } if (dest_len) *dest_len = needed; return Status; } _Dispatch_type_(IRP_MJ_QUERY_VOLUME_INFORMATION) _Function_class_(DRIVER_DISPATCH) static NTSTATUS __stdcall drv_query_volume_information(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { PIO_STACK_LOCATION IrpSp; NTSTATUS Status; ULONG BytesCopied = 0; device_extension* Vcb = DeviceObject->DeviceExtension; bool top_level; FsRtlEnterFileSystem(); TRACE("query volume information\n"); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = STATUS_INVALID_DEVICE_REQUEST; goto end; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } IrpSp = IoGetCurrentIrpStackLocation(Irp); Status = STATUS_NOT_IMPLEMENTED; switch (IrpSp->Parameters.QueryVolume.FsInformationClass) { case FileFsAttributeInformation: { FILE_FS_ATTRIBUTE_INFORMATION* data = Irp->AssociatedIrp.SystemBuffer; bool overflow = false; static const WCHAR ntfs[] = L"NTFS"; static const WCHAR btrfs[] = L"Btrfs"; const WCHAR* fs_name; ULONG fs_name_len, orig_fs_name_len; if (Irp->RequestorMode == UserMode && lie_about_fs_type()) { fs_name = ntfs; orig_fs_name_len = fs_name_len = sizeof(ntfs) - sizeof(WCHAR); } else { fs_name = btrfs; orig_fs_name_len = fs_name_len = sizeof(btrfs) - sizeof(WCHAR); } TRACE("FileFsAttributeInformation\n"); if (IrpSp->Parameters.QueryVolume.Length < sizeof(FILE_FS_ATTRIBUTE_INFORMATION) - sizeof(WCHAR) + fs_name_len) { if (IrpSp->Parameters.QueryVolume.Length > sizeof(FILE_FS_ATTRIBUTE_INFORMATION) - sizeof(WCHAR)) fs_name_len = IrpSp->Parameters.QueryVolume.Length - sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + sizeof(WCHAR); else fs_name_len = 0; overflow = true; } data->FileSystemAttributes = FILE_CASE_PRESERVED_NAMES | FILE_CASE_SENSITIVE_SEARCH | FILE_UNICODE_ON_DISK | FILE_NAMED_STREAMS | FILE_SUPPORTS_HARD_LINKS | FILE_PERSISTENT_ACLS | FILE_SUPPORTS_REPARSE_POINTS | FILE_SUPPORTS_SPARSE_FILES | FILE_SUPPORTS_OBJECT_IDS | FILE_SUPPORTS_OPEN_BY_FILE_ID | FILE_SUPPORTS_EXTENDED_ATTRIBUTES | FILE_SUPPORTS_BLOCK_REFCOUNTING | FILE_SUPPORTS_POSIX_UNLINK_RENAME; if (Vcb->readonly) data->FileSystemAttributes |= FILE_READ_ONLY_VOLUME; // should also be FILE_FILE_COMPRESSION when supported data->MaximumComponentNameLength = 255; // FIXME - check data->FileSystemNameLength = orig_fs_name_len; RtlCopyMemory(data->FileSystemName, fs_name, fs_name_len); BytesCopied = sizeof(FILE_FS_ATTRIBUTE_INFORMATION) - sizeof(WCHAR) + fs_name_len; Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS; break; } case FileFsDeviceInformation: { FILE_FS_DEVICE_INFORMATION* ffdi = Irp->AssociatedIrp.SystemBuffer; TRACE("FileFsDeviceInformation\n"); ffdi->DeviceType = FILE_DEVICE_DISK; ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ffdi->Characteristics = Vcb->Vpb->RealDevice->Characteristics; ExReleaseResourceLite(&Vcb->tree_lock); if (Vcb->readonly) ffdi->Characteristics |= FILE_READ_ONLY_DEVICE; else ffdi->Characteristics &= ~FILE_READ_ONLY_DEVICE; BytesCopied = sizeof(FILE_FS_DEVICE_INFORMATION); Status = STATUS_SUCCESS; break; } case FileFsFullSizeInformation: { FILE_FS_FULL_SIZE_INFORMATION* ffsi = Irp->AssociatedIrp.SystemBuffer; TRACE("FileFsFullSizeInformation\n"); calculate_total_space(Vcb, (uint64_t*)&ffsi->TotalAllocationUnits.QuadPart, (uint64_t*)&ffsi->ActualAvailableAllocationUnits.QuadPart); ffsi->CallerAvailableAllocationUnits.QuadPart = ffsi->ActualAvailableAllocationUnits.QuadPart; ffsi->SectorsPerAllocationUnit = Vcb->superblock.sector_size / 512; ffsi->BytesPerSector = 512; BytesCopied = sizeof(FILE_FS_FULL_SIZE_INFORMATION); Status = STATUS_SUCCESS; break; } case FileFsObjectIdInformation: { FILE_FS_OBJECTID_INFORMATION* ffoi = Irp->AssociatedIrp.SystemBuffer; TRACE("FileFsObjectIdInformation\n"); RtlCopyMemory(ffoi->ObjectId, &Vcb->superblock.uuid.uuid[0], sizeof(UCHAR) * 16); RtlZeroMemory(ffoi->ExtendedInfo, sizeof(ffoi->ExtendedInfo)); BytesCopied = sizeof(FILE_FS_OBJECTID_INFORMATION); Status = STATUS_SUCCESS; break; } case FileFsSizeInformation: { FILE_FS_SIZE_INFORMATION* ffsi = Irp->AssociatedIrp.SystemBuffer; TRACE("FileFsSizeInformation\n"); calculate_total_space(Vcb, (uint64_t*)&ffsi->TotalAllocationUnits.QuadPart, (uint64_t*)&ffsi->AvailableAllocationUnits.QuadPart); ffsi->SectorsPerAllocationUnit = Vcb->superblock.sector_size / 512; ffsi->BytesPerSector = 512; BytesCopied = sizeof(FILE_FS_SIZE_INFORMATION); Status = STATUS_SUCCESS; break; } case FileFsVolumeInformation: { FILE_FS_VOLUME_INFORMATION* data = Irp->AssociatedIrp.SystemBuffer; FILE_FS_VOLUME_INFORMATION ffvi; bool overflow = false; ULONG label_len, orig_label_len; TRACE("FileFsVolumeInformation\n"); TRACE("max length = %lu\n", IrpSp->Parameters.QueryVolume.Length); ExAcquireResourceSharedLite(&Vcb->tree_lock, true); Status = utf8_to_utf16(NULL, 0, &label_len, Vcb->superblock.label, (ULONG)strlen(Vcb->superblock.label)); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 returned %08lx\n", Status); ExReleaseResourceLite(&Vcb->tree_lock); break; } orig_label_len = label_len; if (IrpSp->Parameters.QueryVolume.Length < offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel) + label_len) { if (IrpSp->Parameters.QueryVolume.Length > offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel)) label_len = IrpSp->Parameters.QueryVolume.Length - offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel); else label_len = 0; overflow = true; } TRACE("label_len = %lu\n", label_len); RtlZeroMemory(&ffvi, offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel)); 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]; ffvi.VolumeLabelLength = orig_label_len; RtlCopyMemory(data, &ffvi, min(offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel), IrpSp->Parameters.QueryVolume.Length)); if (label_len > 0) { ULONG bytecount; Status = utf8_to_utf16(&data->VolumeLabel[0], label_len, &bytecount, Vcb->superblock.label, (ULONG)strlen(Vcb->superblock.label)); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) { ERR("utf8_to_utf16 returned %08lx\n", Status); ExReleaseResourceLite(&Vcb->tree_lock); break; } TRACE("label = %.*S\n", (int)(label_len / sizeof(WCHAR)), data->VolumeLabel); } ExReleaseResourceLite(&Vcb->tree_lock); BytesCopied = offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel) + label_len; Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS; break; } #ifdef _MSC_VER // not in mingw yet case FileFsSectorSizeInformation: { FILE_FS_SECTOR_SIZE_INFORMATION* data = Irp->AssociatedIrp.SystemBuffer; data->LogicalBytesPerSector = Vcb->superblock.sector_size; data->PhysicalBytesPerSectorForAtomicity = Vcb->superblock.sector_size; data->PhysicalBytesPerSectorForPerformance = Vcb->superblock.sector_size; data->FileSystemEffectivePhysicalBytesPerSectorForAtomicity = Vcb->superblock.sector_size; data->ByteOffsetForSectorAlignment = 0; data->ByteOffsetForPartitionAlignment = 0; data->Flags = SSINFO_FLAGS_ALIGNED_DEVICE | SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE; if (Vcb->trim && !Vcb->options.no_trim) data->Flags |= SSINFO_FLAGS_TRIM_ENABLED; BytesCopied = sizeof(FILE_FS_SECTOR_SIZE_INFORMATION); Status = STATUS_SUCCESS; break; } #endif default: Status = STATUS_INVALID_PARAMETER; WARN("unknown FsInformationClass %u\n", IrpSp->Parameters.QueryVolume.FsInformationClass); break; } if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) Irp->IoStatus.Information = 0; else Irp->IoStatus.Information = BytesCopied; end: Irp->IoStatus.Status = Status; IoCompleteRequest( Irp, IO_DISK_INCREMENT ); if (top_level) IoSetTopLevelIrp(NULL); TRACE("query volume information returning %08lx\n", Status); FsRtlExitFileSystem(); return Status; } _Function_class_(IO_COMPLETION_ROUTINE) static NTSTATUS __stdcall read_completion(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp, _In_ PVOID conptr) { read_context* context = conptr; UNUSED(DeviceObject); context->iosb = Irp->IoStatus; KeSetEvent(&context->Event, 0, false); return STATUS_MORE_PROCESSING_REQUIRED; } NTSTATUS create_root(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ uint64_t id, _Out_ root** rootptr, _In_ bool no_tree, _In_ uint64_t offset, _In_opt_ PIRP Irp) { NTSTATUS Status; root* r; ROOT_ITEM* ri; traverse_ptr tp; r = ExAllocatePoolWithTag(PagedPool, sizeof(root), ALLOC_TAG); if (!r) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } r->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(root_nonpaged), ALLOC_TAG); if (!r->nonpaged) { ERR("out of memory\n"); ExFreePool(r); return STATUS_INSUFFICIENT_RESOURCES; } ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG); if (!ri) { ERR("out of memory\n"); ExFreePool(r->nonpaged); ExFreePool(r); return STATUS_INSUFFICIENT_RESOURCES; } r->id = id; r->treeholder.address = 0; r->treeholder.generation = Vcb->superblock.generation; r->treeholder.tree = NULL; r->lastinode = 0; r->dirty = false; r->received = false; r->reserved = NULL; r->parent = 0; r->send_ops = 0; RtlZeroMemory(&r->root_item, sizeof(ROOT_ITEM)); r->root_item.num_references = 1; r->fcbs_version = 0; r->checked_for_orphans = true; r->dropped = false; InitializeListHead(&r->fcbs); RtlZeroMemory(r->fcbs_ptrs, sizeof(LIST_ENTRY*) * 256); RtlCopyMemory(ri, &r->root_item, sizeof(ROOT_ITEM)); // We ask here for a traverse_ptr to the item we're inserting, so we can // copy some of the tree's variables Status = insert_tree_item(Vcb, Vcb->root_root, id, TYPE_ROOT_ITEM, offset, ri, sizeof(ROOT_ITEM), &tp, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(ri); ExFreePool(r->nonpaged); ExFreePool(r); return Status; } ExInitializeResourceLite(&r->nonpaged->load_tree_lock); InsertTailList(&Vcb->roots, &r->list_entry); if (!no_tree) { tree* t = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG); if (!t) { ERR("out of memory\n"); delete_tree_item(Vcb, &tp); ExFreePool(r->nonpaged); ExFreePool(r); ExFreePool(ri); return STATUS_INSUFFICIENT_RESOURCES; } t->nonpaged = NULL; t->is_unique = true; t->uniqueness_determined = true; t->buf = NULL; r->treeholder.tree = t; RtlZeroMemory(&t->header, sizeof(tree_header)); t->header.fs_uuid = tp.tree->header.fs_uuid; t->header.address = 0; t->header.flags = HEADER_FLAG_MIXED_BACKREF | 1; // 1 == "written"? Why does the Linux driver record this? t->header.chunk_tree_uuid = tp.tree->header.chunk_tree_uuid; t->header.generation = Vcb->superblock.generation; t->header.tree_id = id; t->header.num_items = 0; t->header.level = 0; t->has_address = false; t->size = 0; t->Vcb = Vcb; t->parent = NULL; t->paritem = NULL; t->root = r; InitializeListHead(&t->itemlist); t->new_address = 0; t->has_new_address = false; t->updated_extents = false; InsertTailList(&Vcb->trees, &t->list_entry); t->list_entry_hash.Flink = NULL; t->write = true; Vcb->need_write = true; } *rootptr = r; return STATUS_SUCCESS; } static NTSTATUS set_label(_In_ device_extension* Vcb, _In_ FILE_FS_LABEL_INFORMATION* ffli) { ULONG utf8len; NTSTATUS Status; ULONG vollen, i; TRACE("label = %.*S\n", (int)(ffli->VolumeLabelLength / sizeof(WCHAR)), ffli->VolumeLabel); vollen = ffli->VolumeLabelLength; for (i = 0; i < ffli->VolumeLabelLength / sizeof(WCHAR); i++) { if (ffli->VolumeLabel[i] == 0) { vollen = i * sizeof(WCHAR); break; } else if (ffli->VolumeLabel[i] == '/' || ffli->VolumeLabel[i] == '\\') { Status = STATUS_INVALID_VOLUME_LABEL; goto end; } } if (vollen == 0) { utf8len = 0; } else { Status = utf16_to_utf8(NULL, 0, &utf8len, ffli->VolumeLabel, vollen); if (!NT_SUCCESS(Status)) goto end; if (utf8len > MAX_LABEL_SIZE) { Status = STATUS_INVALID_VOLUME_LABEL; goto end; } } ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); if (utf8len > 0) { Status = utf16_to_utf8((PCHAR)&Vcb->superblock.label, MAX_LABEL_SIZE, &utf8len, ffli->VolumeLabel, vollen); if (!NT_SUCCESS(Status)) goto release; } else Status = STATUS_SUCCESS; if (utf8len < MAX_LABEL_SIZE) RtlZeroMemory(Vcb->superblock.label + utf8len, MAX_LABEL_SIZE - utf8len); Vcb->need_write = true; release: ExReleaseResourceLite(&Vcb->tree_lock); end: TRACE("returning %08lx\n", Status); return Status; } _Dispatch_type_(IRP_MJ_SET_VOLUME_INFORMATION) _Function_class_(DRIVER_DISPATCH) static NTSTATUS __stdcall drv_set_volume_information(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); device_extension* Vcb = DeviceObject->DeviceExtension; NTSTATUS Status; bool top_level; FsRtlEnterFileSystem(); TRACE("set volume information\n"); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = STATUS_INVALID_DEVICE_REQUEST; goto end; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } Status = STATUS_NOT_IMPLEMENTED; if (Vcb->readonly) { Status = STATUS_MEDIA_WRITE_PROTECTED; goto end; } if (Vcb->removing || Vcb->locked) { Status = STATUS_ACCESS_DENIED; goto end; } switch (IrpSp->Parameters.SetVolume.FsInformationClass) { case FileFsControlInformation: FIXME("STUB: FileFsControlInformation\n"); break; case FileFsLabelInformation: TRACE("FileFsLabelInformation\n"); Status = set_label(Vcb, Irp->AssociatedIrp.SystemBuffer); break; case FileFsObjectIdInformation: FIXME("STUB: FileFsObjectIdInformation\n"); break; default: WARN("Unrecognized FsInformationClass 0x%x\n", IrpSp->Parameters.SetVolume.FsInformationClass); break; } end: Irp->IoStatus.Status = Status; Irp->IoStatus.Information = 0; TRACE("returning %08lx\n", Status); IoCompleteRequest( Irp, IO_NO_INCREMENT ); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } void send_notification_fileref(_In_ file_ref* fileref, _In_ ULONG filter_match, _In_ ULONG action, _In_opt_ PUNICODE_STRING stream) { UNICODE_STRING fn; NTSTATUS Status; ULONG reqlen; USHORT name_offset; fcb* fcb = fileref->fcb; fn.Length = fn.MaximumLength = 0; Status = fileref_get_filename(fileref, &fn, NULL, &reqlen); if (Status != STATUS_BUFFER_OVERFLOW) { ERR("fileref_get_filename returned %08lx\n", Status); return; } if (reqlen > 0xffff) { WARN("reqlen was too long for FsRtlNotifyFilterReportChange\n"); return; } fn.Buffer = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG); if (!fn.Buffer) { ERR("out of memory\n"); return; } fn.MaximumLength = (USHORT)reqlen; fn.Length = 0; Status = fileref_get_filename(fileref, &fn, &name_offset, &reqlen); if (!NT_SUCCESS(Status)) { ERR("fileref_get_filename returned %08lx\n", Status); ExFreePool(fn.Buffer); return; } FsRtlNotifyFilterReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&fn, name_offset, (PSTRING)stream, NULL, filter_match, action, NULL, NULL); ExFreePool(fn.Buffer); } static void send_notification_fcb(_In_ file_ref* fileref, _In_ ULONG filter_match, _In_ ULONG action, _In_opt_ PUNICODE_STRING stream) { fcb* fcb = fileref->fcb; LIST_ENTRY* le; NTSTATUS Status; // no point looking for hardlinks if st_nlink == 1 if (fileref->fcb->inode_item.st_nlink == 1) { ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true); send_notification_fileref(fileref, filter_match, action, stream); ExReleaseResourceLite(&fcb->Vcb->fileref_lock); return; } ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true); le = fcb->hardlinks.Flink; while (le != &fcb->hardlinks) { hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry); file_ref* parfr; Status = open_fileref_by_inode(fcb->Vcb, fcb->subvol, hl->parent, &parfr, NULL); if (!NT_SUCCESS(Status)) ERR("open_fileref_by_inode returned %08lx\n", Status); else if (!parfr->deleted) { UNICODE_STRING fn; ULONG pathlen; fn.Length = fn.MaximumLength = 0; Status = fileref_get_filename(parfr, &fn, NULL, &pathlen); if (Status != STATUS_BUFFER_OVERFLOW) { ERR("fileref_get_filename returned %08lx\n", Status); free_fileref(parfr); break; } if (parfr != fcb->Vcb->root_fileref) pathlen += sizeof(WCHAR); if (pathlen + hl->name.Length > 0xffff) { WARN("pathlen + hl->name.Length was too long for FsRtlNotifyFilterReportChange\n"); free_fileref(parfr); break; } fn.MaximumLength = (USHORT)(pathlen + hl->name.Length); fn.Buffer = ExAllocatePoolWithTag(PagedPool, fn.MaximumLength, ALLOC_TAG); if (!fn.Buffer) { ERR("out of memory\n"); free_fileref(parfr); break; } Status = fileref_get_filename(parfr, &fn, NULL, NULL); if (!NT_SUCCESS(Status)) { ERR("fileref_get_filename returned %08lx\n", Status); free_fileref(parfr); ExFreePool(fn.Buffer); break; } if (parfr != fcb->Vcb->root_fileref) { fn.Buffer[(pathlen / sizeof(WCHAR)) - 1] = '\\'; fn.Length += sizeof(WCHAR); } RtlCopyMemory(&fn.Buffer[pathlen / sizeof(WCHAR)], hl->name.Buffer, hl->name.Length); fn.Length += hl->name.Length; FsRtlNotifyFilterReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&fn, (USHORT)pathlen, (PSTRING)stream, NULL, filter_match, action, NULL, NULL); ExFreePool(fn.Buffer); free_fileref(parfr); } le = le->Flink; } ExReleaseResourceLite(&fcb->Vcb->fileref_lock); } typedef struct { file_ref* fileref; ULONG filter_match; ULONG action; PUNICODE_STRING stream; PIO_WORKITEM work_item; } notification_fcb; _Function_class_(IO_WORKITEM_ROUTINE) static void __stdcall notification_work_item(PDEVICE_OBJECT DeviceObject, PVOID con) { notification_fcb* nf = con; UNUSED(DeviceObject); ExAcquireResourceSharedLite(&nf->fileref->fcb->Vcb->tree_lock, TRUE); // protect us from fileref being reaped send_notification_fcb(nf->fileref, nf->filter_match, nf->action, nf->stream); free_fileref(nf->fileref); ExReleaseResourceLite(&nf->fileref->fcb->Vcb->tree_lock); IoFreeWorkItem(nf->work_item); ExFreePool(nf); } void queue_notification_fcb(_In_ file_ref* fileref, _In_ ULONG filter_match, _In_ ULONG action, _In_opt_ PUNICODE_STRING stream) { notification_fcb* nf; PIO_WORKITEM work_item; nf = ExAllocatePoolWithTag(PagedPool, sizeof(notification_fcb), ALLOC_TAG); if (!nf) { ERR("out of memory\n"); return; } work_item = IoAllocateWorkItem(master_devobj); if (!work_item) { ERR("out of memory\n"); ExFreePool(nf); return; } InterlockedIncrement(&fileref->refcount); nf->fileref = fileref; nf->filter_match = filter_match; nf->action = action; nf->stream = stream; nf->work_item = work_item; IoQueueWorkItem(work_item, notification_work_item, DelayedWorkQueue, nf); } void mark_fcb_dirty(_In_ fcb* fcb) { if (!fcb->dirty) { #ifdef DEBUG_FCB_REFCOUNTS LONG rc; #endif fcb->dirty = true; #ifdef DEBUG_FCB_REFCOUNTS rc = InterlockedIncrement(&fcb->refcount); WARN("fcb %p: refcount now %i\n", fcb, rc); #else InterlockedIncrement(&fcb->refcount); #endif ExAcquireResourceExclusiveLite(&fcb->Vcb->dirty_fcbs_lock, true); InsertTailList(&fcb->Vcb->dirty_fcbs, &fcb->list_entry_dirty); ExReleaseResourceLite(&fcb->Vcb->dirty_fcbs_lock); } fcb->Vcb->need_write = true; } void mark_fileref_dirty(_In_ file_ref* fileref) { if (!fileref->dirty) { fileref->dirty = true; increase_fileref_refcount(fileref); ExAcquireResourceExclusiveLite(&fileref->fcb->Vcb->dirty_filerefs_lock, true); InsertTailList(&fileref->fcb->Vcb->dirty_filerefs, &fileref->list_entry_dirty); ExReleaseResourceLite(&fileref->fcb->Vcb->dirty_filerefs_lock); } fileref->fcb->Vcb->need_write = true; } #ifdef DEBUG_FCB_REFCOUNTS void _free_fcb(_Inout_ fcb* fcb, _In_ const char* func) { LONG rc = InterlockedDecrement(&fcb->refcount); #else void free_fcb(_Inout_ fcb* fcb) { InterlockedDecrement(&fcb->refcount); #endif #ifdef DEBUG_FCB_REFCOUNTS ERR("fcb %p (%s): refcount now %i (subvol %I64x, inode %I64x)\n", fcb, func, rc, fcb->subvol ? fcb->subvol->id : 0, fcb->inode); #endif } void reap_fcb(fcb* fcb) { uint8_t c = fcb->hash >> 24; if (fcb->subvol && fcb->subvol->fcbs_ptrs[c] == &fcb->list_entry) { if (fcb->list_entry.Flink != &fcb->subvol->fcbs && (CONTAINING_RECORD(fcb->list_entry.Flink, struct _fcb, list_entry)->hash >> 24) == c) fcb->subvol->fcbs_ptrs[c] = fcb->list_entry.Flink; else fcb->subvol->fcbs_ptrs[c] = NULL; } if (fcb->list_entry.Flink) { RemoveEntryList(&fcb->list_entry); if (fcb->subvol && fcb->subvol->dropped && IsListEmpty(&fcb->subvol->fcbs)) { ExDeleteResourceLite(&fcb->subvol->nonpaged->load_tree_lock); ExFreePool(fcb->subvol->nonpaged); ExFreePool(fcb->subvol); } } if (fcb->list_entry_all.Flink) RemoveEntryList(&fcb->list_entry_all); ExDeleteResourceLite(&fcb->nonpaged->resource); ExDeleteResourceLite(&fcb->nonpaged->paging_resource); ExDeleteResourceLite(&fcb->nonpaged->dir_children_lock); ExFreeToNPagedLookasideList(&fcb->Vcb->fcb_np_lookaside, fcb->nonpaged); if (fcb->sd) ExFreePool(fcb->sd); if (fcb->adsxattr.Buffer) ExFreePool(fcb->adsxattr.Buffer); if (fcb->reparse_xattr.Buffer) ExFreePool(fcb->reparse_xattr.Buffer); if (fcb->ea_xattr.Buffer) ExFreePool(fcb->ea_xattr.Buffer); if (fcb->adsdata.Buffer) ExFreePool(fcb->adsdata.Buffer); while (!IsListEmpty(&fcb->extents)) { LIST_ENTRY* le = RemoveHeadList(&fcb->extents); extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (ext->csum) ExFreePool(ext->csum); ExFreePool(ext); } while (!IsListEmpty(&fcb->hardlinks)) { LIST_ENTRY* le = RemoveHeadList(&fcb->hardlinks); hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry); if (hl->name.Buffer) ExFreePool(hl->name.Buffer); if (hl->utf8.Buffer) ExFreePool(hl->utf8.Buffer); ExFreePool(hl); } while (!IsListEmpty(&fcb->xattrs)) { xattr* xa = CONTAINING_RECORD(RemoveHeadList(&fcb->xattrs), xattr, list_entry); ExFreePool(xa); } while (!IsListEmpty(&fcb->dir_children_index)) { LIST_ENTRY* le = RemoveHeadList(&fcb->dir_children_index); dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index); ExFreePool(dc->utf8.Buffer); ExFreePool(dc->name.Buffer); ExFreePool(dc->name_uc.Buffer); ExFreePool(dc); } if (fcb->hash_ptrs) ExFreePool(fcb->hash_ptrs); if (fcb->hash_ptrs_uc) ExFreePool(fcb->hash_ptrs_uc); FsRtlUninitializeFileLock(&fcb->lock); FsRtlUninitializeOplock(fcb_oplock(fcb)); if (fcb->pool_type == NonPagedPool) ExFreePool(fcb); else ExFreeToPagedLookasideList(&fcb->Vcb->fcb_lookaside, fcb); } void reap_fcbs(device_extension* Vcb) { LIST_ENTRY* le; le = Vcb->all_fcbs.Flink; while (le != &Vcb->all_fcbs) { fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry_all); LIST_ENTRY* le2 = le->Flink; if (fcb->refcount == 0) reap_fcb(fcb); le = le2; } } void free_fileref(_Inout_ file_ref* fr) { #if defined(_DEBUG) || defined(DEBUG_FCB_REFCOUNTS) LONG rc = InterlockedDecrement(&fr->refcount); #ifdef DEBUG_FCB_REFCOUNTS ERR("fileref %p: refcount now %i\n", fr, rc); #endif #ifdef _DEBUG if (rc < 0) { ERR("fileref %p: refcount now %li\n", fr, rc); int3; } #endif #else InterlockedDecrement(&fr->refcount); #endif } void reap_fileref(device_extension* Vcb, file_ref* fr) { // FIXME - do we need a file_ref lock? // FIXME - do delete if needed // FIXME - throw error if children not empty if (fr->fcb->fileref == fr) fr->fcb->fileref = NULL; if (fr->dc) { if (fr->fcb->ads) fr->dc->size = fr->fcb->adsdata.Length; fr->dc->fileref = NULL; } if (fr->list_entry.Flink) RemoveEntryList(&fr->list_entry); if (fr->parent) free_fileref(fr->parent); free_fcb(fr->fcb); if (fr->oldutf8.Buffer) ExFreePool(fr->oldutf8.Buffer); ExFreeToPagedLookasideList(&Vcb->fileref_lookaside, fr); } void reap_filerefs(device_extension* Vcb, file_ref* fr) { LIST_ENTRY* le; // FIXME - recursion is a bad idea in kernel mode le = fr->children.Flink; while (le != &fr->children) { file_ref* c = CONTAINING_RECORD(le, file_ref, list_entry); LIST_ENTRY* le2 = le->Flink; reap_filerefs(Vcb, c); le = le2; } if (fr->refcount == 0) reap_fileref(Vcb, fr); } static NTSTATUS close_file(_In_ PFILE_OBJECT FileObject, _In_ PIRP Irp) { fcb* fcb; ccb* ccb; file_ref* fileref = NULL; LONG open_files; UNUSED(Irp); TRACE("FileObject = %p\n", FileObject); fcb = FileObject->FsContext; if (!fcb) { TRACE("FCB was NULL, returning success\n"); return STATUS_SUCCESS; } open_files = InterlockedDecrement(&fcb->Vcb->open_files); ccb = FileObject->FsContext2; TRACE("close called for fcb %p)\n", fcb); // FIXME - make sure notification gets sent if file is being deleted if (ccb) { if (ccb->query_string.Buffer) RtlFreeUnicodeString(&ccb->query_string); if (ccb->filename.Buffer) ExFreePool(ccb->filename.Buffer); // FIXME - use refcounts for fileref fileref = ccb->fileref; if (fcb->Vcb->running_sends > 0) { bool send_cancelled = false; ExAcquireResourceExclusiveLite(&fcb->Vcb->send_load_lock, true); if (ccb->send) { ccb->send->cancelling = true; send_cancelled = true; KeSetEvent(&ccb->send->cleared_event, 0, false); } ExReleaseResourceLite(&fcb->Vcb->send_load_lock); if (send_cancelled) { while (ccb->send) { ExAcquireResourceExclusiveLite(&fcb->Vcb->send_load_lock, true); ExReleaseResourceLite(&fcb->Vcb->send_load_lock); } } } ExFreePool(ccb); } CcUninitializeCacheMap(FileObject, NULL, NULL); if (open_files == 0 && fcb->Vcb->removing) { uninit(fcb->Vcb); return STATUS_SUCCESS; } if (!(fcb->Vcb->Vpb->Flags & VPB_MOUNTED)) return STATUS_SUCCESS; if (fileref) free_fileref(fileref); else free_fcb(fcb); return STATUS_SUCCESS; } void uninit(_In_ device_extension* Vcb) { uint64_t i; KIRQL irql; NTSTATUS Status; LIST_ENTRY* le; LARGE_INTEGER time; if (!Vcb->removing) { ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); Vcb->removing = true; ExReleaseResourceLite(&Vcb->tree_lock); } if (Vcb->vde && Vcb->vde->mounted_device == Vcb->devobj) Vcb->vde->mounted_device = NULL; IoAcquireVpbSpinLock(&irql); Vcb->Vpb->Flags &= ~VPB_MOUNTED; Vcb->Vpb->Flags |= VPB_DIRECT_WRITES_ALLOWED; Vcb->Vpb->DeviceObject = NULL; IoReleaseVpbSpinLock(irql); // FIXME - needs global_loading_lock to be held if (Vcb->list_entry.Flink) RemoveEntryList(&Vcb->list_entry); if (Vcb->balance.thread) { Vcb->balance.paused = false; Vcb->balance.stopping = true; KeSetEvent(&Vcb->balance.event, 0, false); KeWaitForSingleObject(&Vcb->balance.finished, Executive, KernelMode, false, NULL); } if (Vcb->scrub.thread) { Vcb->scrub.paused = false; Vcb->scrub.stopping = true; KeSetEvent(&Vcb->scrub.event, 0, false); KeWaitForSingleObject(&Vcb->scrub.finished, Executive, KernelMode, false, NULL); } if (Vcb->running_sends != 0) { bool send_cancelled = false; ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, true); le = Vcb->send_ops.Flink; while (le != &Vcb->send_ops) { send_info* send = CONTAINING_RECORD(le, send_info, list_entry); if (!send->cancelling) { send->cancelling = true; send_cancelled = true; send->ccb = NULL; KeSetEvent(&send->cleared_event, 0, false); } le = le->Flink; } ExReleaseResourceLite(&Vcb->send_load_lock); if (send_cancelled) { while (Vcb->running_sends != 0) { ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, true); ExReleaseResourceLite(&Vcb->send_load_lock); } } } Status = registry_mark_volume_unmounted(&Vcb->superblock.uuid); if (!NT_SUCCESS(Status) && Status != STATUS_TOO_LATE) WARN("registry_mark_volume_unmounted returned %08lx\n", Status); for (i = 0; i < Vcb->calcthreads.num_threads; i++) { Vcb->calcthreads.threads[i].quit = true; } KeSetEvent(&Vcb->calcthreads.event, 0, false); for (i = 0; i < Vcb->calcthreads.num_threads; i++) { KeWaitForSingleObject(&Vcb->calcthreads.threads[i].finished, Executive, KernelMode, false, NULL); ZwClose(Vcb->calcthreads.threads[i].handle); } ExFreePool(Vcb->calcthreads.threads); time.QuadPart = 0; KeSetTimer(&Vcb->flush_thread_timer, time, NULL); // trigger the timer early KeWaitForSingleObject(&Vcb->flush_thread_finished, Executive, KernelMode, false, NULL); reap_fcb(Vcb->volume_fcb); reap_fcb(Vcb->dummy_fcb); if (Vcb->root_file) ObDereferenceObject(Vcb->root_file); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry); if (c->cache) { reap_fcb(c->cache); c->cache = NULL; } le = le->Flink; } while (!IsListEmpty(&Vcb->all_fcbs)) { fcb* fcb = CONTAINING_RECORD(Vcb->all_fcbs.Flink, struct _fcb, list_entry_all); reap_fcb(fcb); } while (!IsListEmpty(&Vcb->sys_chunks)) { sys_chunk* sc = CONTAINING_RECORD(RemoveHeadList(&Vcb->sys_chunks), sys_chunk, list_entry); if (sc->data) ExFreePool(sc->data); ExFreePool(sc); } while (!IsListEmpty(&Vcb->roots)) { root* r = CONTAINING_RECORD(RemoveHeadList(&Vcb->roots), root, list_entry); ExDeleteResourceLite(&r->nonpaged->load_tree_lock); ExFreePool(r->nonpaged); ExFreePool(r); } while (!IsListEmpty(&Vcb->chunks)) { chunk* c = CONTAINING_RECORD(RemoveHeadList(&Vcb->chunks), chunk, list_entry); while (!IsListEmpty(&c->space)) { LIST_ENTRY* le2 = RemoveHeadList(&c->space); space* s = CONTAINING_RECORD(le2, space, list_entry); ExFreePool(s); } while (!IsListEmpty(&c->deleting)) { LIST_ENTRY* le2 = RemoveHeadList(&c->deleting); space* s = CONTAINING_RECORD(le2, space, list_entry); ExFreePool(s); } if (c->devices) ExFreePool(c->devices); if (c->cache) reap_fcb(c->cache); ExDeleteResourceLite(&c->range_locks_lock); ExDeleteResourceLite(&c->partial_stripes_lock); ExDeleteResourceLite(&c->lock); ExDeleteResourceLite(&c->changed_extents_lock); ExFreePool(c->chunk_item); ExFreePool(c); } while (!IsListEmpty(&Vcb->devices)) { device* dev = CONTAINING_RECORD(RemoveHeadList(&Vcb->devices), device, list_entry); while (!IsListEmpty(&dev->space)) { LIST_ENTRY* le2 = RemoveHeadList(&dev->space); space* s = CONTAINING_RECORD(le2, space, list_entry); ExFreePool(s); } ExFreePool(dev); } ExAcquireResourceExclusiveLite(&Vcb->scrub.stats_lock, true); while (!IsListEmpty(&Vcb->scrub.errors)) { scrub_error* err = CONTAINING_RECORD(RemoveHeadList(&Vcb->scrub.errors), scrub_error, list_entry); ExFreePool(err); } ExReleaseResourceLite(&Vcb->scrub.stats_lock); ExDeleteResourceLite(&Vcb->fcb_lock); ExDeleteResourceLite(&Vcb->fileref_lock); ExDeleteResourceLite(&Vcb->load_lock); ExDeleteResourceLite(&Vcb->tree_lock); ExDeleteResourceLite(&Vcb->chunk_lock); ExDeleteResourceLite(&Vcb->dirty_fcbs_lock); ExDeleteResourceLite(&Vcb->dirty_filerefs_lock); ExDeleteResourceLite(&Vcb->dirty_subvols_lock); ExDeleteResourceLite(&Vcb->scrub.stats_lock); ExDeleteResourceLite(&Vcb->send_load_lock); ExDeletePagedLookasideList(&Vcb->tree_data_lookaside); ExDeletePagedLookasideList(&Vcb->traverse_ptr_lookaside); ExDeletePagedLookasideList(&Vcb->batch_item_lookaside); ExDeletePagedLookasideList(&Vcb->fileref_lookaside); ExDeletePagedLookasideList(&Vcb->fcb_lookaside); ExDeletePagedLookasideList(&Vcb->name_bit_lookaside); ExDeleteNPagedLookasideList(&Vcb->range_lock_lookaside); ExDeleteNPagedLookasideList(&Vcb->fcb_np_lookaside); ZwClose(Vcb->flush_thread_handle); if (Vcb->devobj->AttachedDevice) IoDetachDevice(Vcb->devobj); IoDeleteDevice(Vcb->devobj); } static NTSTATUS delete_fileref_fcb(_In_ file_ref* fileref, _In_opt_ PFILE_OBJECT FileObject, _In_opt_ PIRP Irp, _In_ LIST_ENTRY* rollback) { NTSTATUS Status; LIST_ENTRY* le; // excise extents if (fileref->fcb->type != BTRFS_TYPE_DIRECTORY && fileref->fcb->inode_item.st_size > 0) { 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); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); return Status; } } fileref->fcb->Header.AllocationSize.QuadPart = 0; fileref->fcb->Header.FileSize.QuadPart = 0; fileref->fcb->Header.ValidDataLength.QuadPart = 0; if (FileObject) { CC_FILE_SIZES ccfs; ccfs.AllocationSize = fileref->fcb->Header.AllocationSize; ccfs.FileSize = fileref->fcb->Header.FileSize; ccfs.ValidDataLength = fileref->fcb->Header.ValidDataLength; Status = STATUS_SUCCESS; try { CcSetFileSizes(FileObject, &ccfs); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("CcSetFileSizes threw exception %08lx\n", Status); return Status; } } fileref->fcb->deleted = true; le = fileref->children.Flink; while (le != &fileref->children) { file_ref* fr2 = CONTAINING_RECORD(le, file_ref, list_entry); if (fr2->fcb->ads) { fr2->fcb->deleted = true; mark_fcb_dirty(fr2->fcb); } le = le->Flink; } return STATUS_SUCCESS; } NTSTATUS delete_fileref(_In_ file_ref* fileref, _In_opt_ PFILE_OBJECT FileObject, _In_ bool make_orphan, _In_opt_ PIRP Irp, _In_ LIST_ENTRY* rollback) { LARGE_INTEGER time; BTRFS_TIME now; NTSTATUS Status; ULONG utf8len = 0; KeQuerySystemTime(&time); win_time_to_unix(time, &now); ExAcquireResourceExclusiveLite(fileref->fcb->Header.Resource, true); if (fileref->deleted) { ExReleaseResourceLite(fileref->fcb->Header.Resource); return STATUS_SUCCESS; } if (fileref->fcb->subvol->send_ops > 0) { ExReleaseResourceLite(fileref->fcb->Header.Resource); return STATUS_ACCESS_DENIED; } fileref->deleted = true; mark_fileref_dirty(fileref); // delete INODE_ITEM (0x1) TRACE("nlink = %u\n", fileref->fcb->inode_item.st_nlink); if (!fileref->fcb->ads) { if (fileref->parent->fcb->subvol == fileref->fcb->subvol) { LIST_ENTRY* le; mark_fcb_dirty(fileref->fcb); fileref->fcb->inode_item_changed = true; if (fileref->fcb->inode_item.st_nlink > 1 || make_orphan) { fileref->fcb->inode_item.st_nlink--; fileref->fcb->inode_item.transid = fileref->fcb->Vcb->superblock.generation; fileref->fcb->inode_item.sequence++; fileref->fcb->inode_item.st_ctime = now; } else { Status = delete_fileref_fcb(fileref, FileObject, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("delete_fileref_fcb returned %08lx\n", Status); ExReleaseResourceLite(fileref->fcb->Header.Resource); return Status; } } if (fileref->dc) { le = fileref->fcb->hardlinks.Flink; while (le != &fileref->fcb->hardlinks) { hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry); if (hl->parent == fileref->parent->fcb->inode && hl->index == fileref->dc->index) { RemoveEntryList(&hl->list_entry); if (hl->name.Buffer) ExFreePool(hl->name.Buffer); if (hl->utf8.Buffer) ExFreePool(hl->utf8.Buffer); ExFreePool(hl); break; } le = le->Flink; } } } else if (fileref->fcb->subvol->parent == fileref->parent->fcb->subvol->id) { // valid subvolume if (fileref->fcb->subvol->root_item.num_references > 1) { fileref->fcb->subvol->root_item.num_references--; mark_fcb_dirty(fileref->fcb); // so ROOT_ITEM gets updated } else { LIST_ENTRY* le; // FIXME - we need a lock here RemoveEntryList(&fileref->fcb->subvol->list_entry); InsertTailList(&fileref->fcb->Vcb->drop_roots, &fileref->fcb->subvol->list_entry); le = fileref->children.Flink; while (le != &fileref->children) { file_ref* fr2 = CONTAINING_RECORD(le, file_ref, list_entry); if (fr2->fcb->ads) { fr2->fcb->deleted = true; mark_fcb_dirty(fr2->fcb); } le = le->Flink; } } } } else { fileref->fcb->deleted = true; mark_fcb_dirty(fileref->fcb); } // remove dir_child from parent if (fileref->dc) { TRACE("delete file %.*S\n", (int)(fileref->dc->name.Length / sizeof(WCHAR)), fileref->dc->name.Buffer); ExAcquireResourceExclusiveLite(&fileref->parent->fcb->nonpaged->dir_children_lock, true); RemoveEntryList(&fileref->dc->list_entry_index); if (!fileref->fcb->ads) remove_dir_child_from_hash_lists(fileref->parent->fcb, fileref->dc); ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock); if (!fileref->oldutf8.Buffer) fileref->oldutf8 = fileref->dc->utf8; else ExFreePool(fileref->dc->utf8.Buffer); utf8len = fileref->dc->utf8.Length; fileref->oldindex = fileref->dc->index; ExFreePool(fileref->dc->name.Buffer); ExFreePool(fileref->dc->name_uc.Buffer); ExFreePool(fileref->dc); fileref->dc = NULL; } // update INODE_ITEM of parent ExAcquireResourceExclusiveLite(fileref->parent->fcb->Header.Resource, true); fileref->parent->fcb->inode_item.transid = fileref->fcb->Vcb->superblock.generation; fileref->parent->fcb->inode_item.sequence++; fileref->parent->fcb->inode_item.st_ctime = now; if (!fileref->fcb->ads) { TRACE("fileref->parent->fcb->inode_item.st_size (inode %I64x) was %I64x\n", fileref->parent->fcb->inode, fileref->parent->fcb->inode_item.st_size); fileref->parent->fcb->inode_item.st_size -= utf8len * 2; TRACE("fileref->parent->fcb->inode_item.st_size (inode %I64x) now %I64x\n", fileref->parent->fcb->inode, fileref->parent->fcb->inode_item.st_size); fileref->parent->fcb->inode_item.st_mtime = now; } fileref->parent->fcb->inode_item_changed = true; ExReleaseResourceLite(fileref->parent->fcb->Header.Resource); if (!fileref->fcb->ads && fileref->parent->dc) send_notification_fcb(fileref->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL); mark_fcb_dirty(fileref->parent->fcb); fileref->fcb->subvol->root_item.ctransid = fileref->fcb->Vcb->superblock.generation; fileref->fcb->subvol->root_item.ctime = now; ExReleaseResourceLite(fileref->fcb->Header.Resource); return STATUS_SUCCESS; } _Dispatch_type_(IRP_MJ_CLEANUP) _Function_class_(DRIVER_DISPATCH) static NTSTATUS __stdcall drv_cleanup(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { NTSTATUS Status; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; device_extension* Vcb = DeviceObject->DeviceExtension; fcb* fcb = FileObject->FsContext; bool top_level; FsRtlEnterFileSystem(); TRACE("cleanup\n"); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Irp->IoStatus.Information = 0; Status = STATUS_SUCCESS; goto exit; } else if (DeviceObject == master_devobj) { TRACE("closing file system\n"); Status = STATUS_SUCCESS; goto exit; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto exit; } if (FileObject->Flags & FO_CLEANUP_COMPLETE) { TRACE("FileObject %p already cleaned up\n", FileObject); Status = STATUS_SUCCESS; goto exit; } if (!fcb) { ERR("fcb was NULL\n"); Status = STATUS_INVALID_PARAMETER; goto exit; } FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL); // We have to use the pointer to Vcb stored in the fcb, as we can receive cleanup // messages belonging to other devices. if (FileObject && FileObject->FsContext) { ccb* ccb; file_ref* fileref; bool locked = true; bool uninitialize_cache_map = false; bool flush_and_purge_cache = false; ccb = FileObject->FsContext2; fileref = ccb ? ccb->fileref : NULL; TRACE("cleanup called for FileObject %p\n", FileObject); TRACE("fileref %p, refcount = %li, open_count = %li\n", fileref, fileref ? fileref->refcount : 0, fileref ? fileref->open_count : 0); ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); IoRemoveShareAccess(FileObject, &fcb->share_access); FsRtlFastUnlockAll(&fcb->lock, FileObject, IoGetRequestorProcess(Irp), NULL); if (ccb) FsRtlNotifyCleanup(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, ccb); if (ccb && ccb->options & FILE_DELETE_ON_CLOSE && fileref) fileref->delete_on_close = true; if (fileref && fileref->delete_on_close && fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0 && fcb != fcb->Vcb->dummy_fcb) fileref->delete_on_close = false; if (fcb->Vcb->locked && fcb->Vcb->locked_fileobj == FileObject) { TRACE("unlocking volume\n"); do_unlock_volume(fcb->Vcb); FsRtlNotifyVolumeEvent(FileObject, FSRTL_VOLUME_UNLOCK); } if (ccb && ccb->reserving) { fcb->subvol->reserved = NULL; ccb->reserving = false; // FIXME - flush all of subvol's fcbs } if (fileref) { LONG oc = InterlockedDecrement(&fileref->open_count); #ifdef DEBUG_FCB_REFCOUNTS ERR("fileref %p: open_count now %i\n", fileref, oc); #endif if (oc == 0 || (fileref->delete_on_close && fileref->posix_delete)) { if (!fcb->Vcb->removing) { if (oc == 0 && fileref->fcb->inode_item.st_nlink == 0 && fileref != fcb->Vcb->root_fileref && fcb != fcb->Vcb->volume_fcb && !fcb->ads) { // last handle closed on POSIX-deleted file LIST_ENTRY rollback; InitializeListHead(&rollback); Status = delete_fileref_fcb(fileref, FileObject, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("delete_fileref_fcb returned %08lx\n", Status); do_rollback(fcb->Vcb, &rollback); ExReleaseResourceLite(fileref->fcb->Header.Resource); ExReleaseResourceLite(&fcb->Vcb->tree_lock); goto exit; } clear_rollback(&rollback); mark_fcb_dirty(fileref->fcb); } else if (fileref->delete_on_close && fileref != fcb->Vcb->root_fileref && fcb != fcb->Vcb->volume_fcb) { LIST_ENTRY rollback; InitializeListHead(&rollback); if (!fileref->fcb->ads || fileref->dc) { if (fileref->fcb->ads) { send_notification_fileref(fileref->parent, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_REMOVED, &fileref->dc->name); } else send_notification_fileref(fileref, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_REMOVED, NULL); } ExReleaseResourceLite(fcb->Header.Resource); locked = false; // fileref_lock needs to be acquired before fcb->Header.Resource ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true); Status = delete_fileref(fileref, FileObject, oc > 0 && fileref->posix_delete, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("delete_fileref returned %08lx\n", Status); do_rollback(fcb->Vcb, &rollback); ExReleaseResourceLite(&fcb->Vcb->fileref_lock); ExReleaseResourceLite(&fcb->Vcb->tree_lock); goto exit; } ExReleaseResourceLite(&fcb->Vcb->fileref_lock); clear_rollback(&rollback); } else if (FileObject->Flags & FO_CACHE_SUPPORTED && FileObject->SectionObjectPointer->DataSectionObject) flush_and_purge_cache = true; } if (fcb->Vcb && fcb != fcb->Vcb->volume_fcb) uninitialize_cache_map = true; } } if (locked) ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&fcb->Vcb->tree_lock); if (flush_and_purge_cache) { IO_STATUS_BLOCK iosb; CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &iosb); if (!NT_SUCCESS(iosb.Status)) ERR("CcFlushCache returned %08lx\n", iosb.Status); if (!ExIsResourceAcquiredSharedLite(fcb->Header.PagingIoResource)) { ExAcquireResourceExclusiveLite(fcb->Header.PagingIoResource, true); ExReleaseResourceLite(fcb->Header.PagingIoResource); } CcPurgeCacheSection(FileObject->SectionObjectPointer, NULL, 0, false); TRACE("flushed cache on close (FileObject = %p, fcb = %p, AllocationSize = %I64x, FileSize = %I64x, ValidDataLength = %I64x)\n", FileObject, fcb, fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart); } /* In rare instances CcUninitializeCacheMap can block - we need to make sure we're not holding tree_lock if it does. */ if (uninitialize_cache_map) CcUninitializeCacheMap(FileObject, NULL, NULL); FileObject->Flags |= FO_CLEANUP_COMPLETE; } Status = STATUS_SUCCESS; exit: TRACE("returning %08lx\n", Status); Irp->IoStatus.Status = Status; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } _Success_(return) bool get_file_attributes_from_xattr(_In_reads_bytes_(len) char* val, _In_ uint16_t len, _Out_ ULONG* atts) { if (len > 2 && val[0] == '0' && val[1] == 'x') { int i; ULONG dosnum = 0; for (i = 2; i < len; i++) { dosnum *= 0x10; if (val[i] >= '0' && val[i] <= '9') dosnum |= val[i] - '0'; else if (val[i] >= 'a' && val[i] <= 'f') dosnum |= val[i] + 10 - 'a'; else if (val[i] >= 'A' && val[i] <= 'F') dosnum |= val[i] + 10 - 'a'; } TRACE("DOSATTRIB: %08lx\n", dosnum); *atts = dosnum; return true; } return false; } ULONG get_file_attributes(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _In_ uint64_t inode, _In_ uint8_t type, _In_ bool dotfile, _In_ bool ignore_xa, _In_opt_ PIRP Irp) { ULONG att; char* eaval; uint16_t ealen; if (!ignore_xa && get_xattr(Vcb, r, inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, (uint8_t**)&eaval, &ealen, Irp)) { ULONG dosnum = 0; if (get_file_attributes_from_xattr(eaval, ealen, &dosnum)) { ExFreePool(eaval); if (type == BTRFS_TYPE_DIRECTORY) dosnum |= FILE_ATTRIBUTE_DIRECTORY; else if (type == BTRFS_TYPE_SYMLINK) dosnum |= FILE_ATTRIBUTE_REPARSE_POINT; if (type != BTRFS_TYPE_DIRECTORY) dosnum &= ~FILE_ATTRIBUTE_DIRECTORY; if (inode == SUBVOL_ROOT_INODE) { if (r->root_item.flags & BTRFS_SUBVOL_READONLY) dosnum |= FILE_ATTRIBUTE_READONLY; else dosnum &= ~FILE_ATTRIBUTE_READONLY; } return dosnum; } ExFreePool(eaval); } switch (type) { case BTRFS_TYPE_DIRECTORY: att = FILE_ATTRIBUTE_DIRECTORY; break; case BTRFS_TYPE_SYMLINK: att = FILE_ATTRIBUTE_REPARSE_POINT; break; default: att = 0; break; } if (dotfile || (r->id == BTRFS_ROOT_FSTREE && inode == SUBVOL_ROOT_INODE)) att |= FILE_ATTRIBUTE_HIDDEN; att |= FILE_ATTRIBUTE_ARCHIVE; if (inode == SUBVOL_ROOT_INODE) { if (r->root_item.flags & BTRFS_SUBVOL_READONLY) att |= FILE_ATTRIBUTE_READONLY; else att &= ~FILE_ATTRIBUTE_READONLY; } // FIXME - get READONLY from ii->st_mode // FIXME - return SYSTEM for block/char devices? if (att == 0) att = FILE_ATTRIBUTE_NORMAL; return att; } NTSTATUS sync_read_phys(_In_ PDEVICE_OBJECT DeviceObject, _In_ PFILE_OBJECT FileObject, _In_ uint64_t StartingOffset, _In_ ULONG Length, _Out_writes_bytes_(Length) PUCHAR Buffer, _In_ bool override) { IO_STATUS_BLOCK IoStatus; LARGE_INTEGER Offset; PIRP Irp; PIO_STACK_LOCATION IrpSp; NTSTATUS Status; read_context context; RtlZeroMemory(&context, sizeof(read_context)); KeInitializeEvent(&context.Event, NotificationEvent, false); Offset.QuadPart = (LONGLONG)StartingOffset; Irp = IoAllocateIrp(DeviceObject->StackSize, false); if (!Irp) { ERR("IoAllocateIrp failed\n"); return STATUS_INSUFFICIENT_RESOURCES; } Irp->Flags |= IRP_NOCACHE; IrpSp = IoGetNextIrpStackLocation(Irp); IrpSp->MajorFunction = IRP_MJ_READ; IrpSp->FileObject = FileObject; if (override) IrpSp->Flags |= SL_OVERRIDE_VERIFY_VOLUME; if (DeviceObject->Flags & DO_BUFFERED_IO) { Irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPool, Length, ALLOC_TAG); if (!Irp->AssociatedIrp.SystemBuffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION; Irp->UserBuffer = Buffer; } else if (DeviceObject->Flags & DO_DIRECT_IO) { Irp->MdlAddress = IoAllocateMdl(Buffer, Length, false, false, NULL); if (!Irp->MdlAddress) { ERR("IoAllocateMdl failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Status = STATUS_SUCCESS; try { MmProbeAndLockPages(Irp->MdlAddress, KernelMode, IoWriteAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); IoFreeMdl(Irp->MdlAddress); goto exit; } } else Irp->UserBuffer = Buffer; IrpSp->Parameters.Read.Length = Length; IrpSp->Parameters.Read.ByteOffset = Offset; Irp->UserIosb = &IoStatus; Irp->UserEvent = &context.Event; IoSetCompletionRoutine(Irp, read_completion, &context, true, true, true); Status = IoCallDriver(DeviceObject, Irp); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL); Status = context.iosb.Status; } if (DeviceObject->Flags & DO_DIRECT_IO) { MmUnlockPages(Irp->MdlAddress); IoFreeMdl(Irp->MdlAddress); } exit: IoFreeIrp(Irp); return Status; } bool check_superblock_checksum(superblock* sb) { switch (sb->csum_type) { case CSUM_TYPE_CRC32C: { uint32_t crc32 = ~calc_crc32c(0xffffffff, (uint8_t*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum)); if (crc32 == *((uint32_t*)sb->checksum)) return true; WARN("crc32 was %08x, expected %08x\n", crc32, *((uint32_t*)sb->checksum)); break; } case CSUM_TYPE_XXHASH: { uint64_t hash = XXH64(&sb->uuid, sizeof(superblock) - sizeof(sb->checksum), 0); if (hash == *((uint64_t*)sb->checksum)) return true; WARN("superblock hash was %I64x, expected %I64x\n", hash, *((uint64_t*)sb->checksum)); break; } case CSUM_TYPE_SHA256: { uint8_t hash[SHA256_HASH_SIZE]; calc_sha256(hash, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum)); if (RtlCompareMemory(hash, sb, SHA256_HASH_SIZE) == SHA256_HASH_SIZE) return true; WARN("superblock hash was invalid\n"); break; } case CSUM_TYPE_BLAKE2: { uint8_t hash[BLAKE2_HASH_SIZE]; blake2b(hash, sizeof(hash), &sb->uuid, sizeof(superblock) - sizeof(sb->checksum)); if (RtlCompareMemory(hash, sb, BLAKE2_HASH_SIZE) == BLAKE2_HASH_SIZE) return true; WARN("superblock hash was invalid\n"); break; } default: WARN("unrecognized csum type %x\n", sb->csum_type); } return false; } static NTSTATUS read_superblock(_In_ device_extension* Vcb, _In_ PDEVICE_OBJECT device, _In_ PFILE_OBJECT fileobj, _In_ uint64_t length) { NTSTATUS Status; superblock* sb; ULONG i, to_read; uint8_t valid_superblocks; to_read = device->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), device->SectorSize); sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG); if (!sb) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } if (superblock_addrs[0] + to_read > length) { WARN("device was too short to have any superblock\n"); ExFreePool(sb); return STATUS_UNRECOGNIZED_VOLUME; } i = 0; valid_superblocks = 0; while (superblock_addrs[i] > 0) { if (i > 0 && superblock_addrs[i] + to_read > length) break; Status = sync_read_phys(device, fileobj, superblock_addrs[i], to_read, (PUCHAR)sb, false); if (!NT_SUCCESS(Status)) { ERR("Failed to read superblock %lu: %08lx\n", i, Status); ExFreePool(sb); return Status; } if (sb->magic != BTRFS_MAGIC) { if (i == 0) { TRACE("not a BTRFS volume\n"); ExFreePool(sb); return STATUS_UNRECOGNIZED_VOLUME; } } else { TRACE("got superblock %lu!\n", i); if (sb->sector_size == 0) WARN("superblock sector size was 0\n"); else if (sb->sector_size & (sb->sector_size - 1)) WARN("superblock sector size was not power of 2\n"); else if (sb->node_size < sizeof(tree_header) + sizeof(internal_node) || sb->node_size > 0x10000) WARN("invalid node size %x\n", sb->node_size); else if ((sb->node_size % sb->sector_size) != 0) WARN("node size %x was not a multiple of sector_size %x\n", sb->node_size, sb->sector_size); else if (check_superblock_checksum(sb) && (valid_superblocks == 0 || sb->generation > Vcb->superblock.generation)) { RtlCopyMemory(&Vcb->superblock, sb, sizeof(superblock)); valid_superblocks++; } } i++; } ExFreePool(sb); if (valid_superblocks == 0) { ERR("could not find any valid superblocks\n"); return STATUS_INTERNAL_ERROR; } TRACE("label is %s\n", Vcb->superblock.label); return STATUS_SUCCESS; } NTSTATUS dev_ioctl(_In_ PDEVICE_OBJECT DeviceObject, _In_ ULONG ControlCode, _In_reads_bytes_opt_(InputBufferSize) PVOID InputBuffer, _In_ ULONG InputBufferSize, _Out_writes_bytes_opt_(OutputBufferSize) PVOID OutputBuffer, _In_ ULONG OutputBufferSize, _In_ bool Override, _Out_opt_ IO_STATUS_BLOCK* iosb) { PIRP Irp; KEVENT Event; NTSTATUS Status; PIO_STACK_LOCATION IrpSp; IO_STATUS_BLOCK IoStatus; KeInitializeEvent(&Event, NotificationEvent, false); Irp = IoBuildDeviceIoControlRequest(ControlCode, DeviceObject, InputBuffer, InputBufferSize, OutputBuffer, OutputBufferSize, false, &Event, &IoStatus); if (!Irp) return STATUS_INSUFFICIENT_RESOURCES; if (Override) { IrpSp = IoGetNextIrpStackLocation(Irp); IrpSp->Flags |= SL_OVERRIDE_VERIFY_VOLUME; } Status = IoCallDriver(DeviceObject, Irp); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&Event, Executive, KernelMode, false, NULL); Status = IoStatus.Status; } if (iosb) *iosb = IoStatus; return Status; } _Requires_exclusive_lock_held_(Vcb->tree_lock) static NTSTATUS add_root(_Inout_ device_extension* Vcb, _In_ uint64_t id, _In_ uint64_t addr, _In_ uint64_t generation, _In_opt_ traverse_ptr* tp) { root* r = ExAllocatePoolWithTag(PagedPool, sizeof(root), ALLOC_TAG); if (!r) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } r->id = id; r->dirty = false; r->received = false; r->reserved = NULL; r->treeholder.address = addr; r->treeholder.tree = NULL; r->treeholder.generation = generation; r->parent = 0; r->send_ops = 0; r->fcbs_version = 0; r->checked_for_orphans = false; r->dropped = false; InitializeListHead(&r->fcbs); RtlZeroMemory(r->fcbs_ptrs, sizeof(LIST_ENTRY*) * 256); r->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(root_nonpaged), ALLOC_TAG); if (!r->nonpaged) { ERR("out of memory\n"); ExFreePool(r); return STATUS_INSUFFICIENT_RESOURCES; } ExInitializeResourceLite(&r->nonpaged->load_tree_lock); r->lastinode = 0; if (tp) { RtlCopyMemory(&r->root_item, tp->item->data, min(sizeof(ROOT_ITEM), tp->item->size)); if (tp->item->size < sizeof(ROOT_ITEM)) RtlZeroMemory(((uint8_t*)&r->root_item) + tp->item->size, sizeof(ROOT_ITEM) - tp->item->size); } else RtlZeroMemory(&r->root_item, sizeof(ROOT_ITEM)); if (!Vcb->readonly && (r->id == BTRFS_ROOT_ROOT || r->id == BTRFS_ROOT_FSTREE || (r->id >= 0x100 && !(r->id & 0xf000000000000000)))) { // FS tree root // FIXME - don't call this if subvol is readonly (though we will have to if we ever toggle this flag) get_last_inode(Vcb, r, NULL); if (r->id == BTRFS_ROOT_ROOT && r->lastinode < 0x100) r->lastinode = 0x100; } InsertTailList(&Vcb->roots, &r->list_entry); switch (r->id) { case BTRFS_ROOT_ROOT: Vcb->root_root = r; break; case BTRFS_ROOT_EXTENT: Vcb->extent_root = r; break; case BTRFS_ROOT_CHUNK: Vcb->chunk_root = r; break; case BTRFS_ROOT_DEVTREE: Vcb->dev_root = r; break; case BTRFS_ROOT_CHECKSUM: Vcb->checksum_root = r; break; case BTRFS_ROOT_UUID: Vcb->uuid_root = r; break; case BTRFS_ROOT_FREE_SPACE: Vcb->space_root = r; break; case BTRFS_ROOT_DATA_RELOC: Vcb->data_reloc_root = r; break; case BTRFS_ROOT_BLOCK_GROUP: if (Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE) Vcb->block_group_root = r; break; } return STATUS_SUCCESS; } static NTSTATUS look_for_roots(_Requires_exclusive_lock_held_(_Curr_->tree_lock) _In_ device_extension* Vcb, _In_opt_ PIRP Irp) { traverse_ptr tp, next_tp; KEY searchkey; bool b; NTSTATUS Status; searchkey.obj_id = 0; searchkey.obj_type = 0; searchkey.offset = 0; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } do { TRACE("(%I64x,%x,%I64x)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); if (tp.item->key.obj_type == TYPE_ROOT_ITEM) { ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data; if (tp.item->size < offsetof(ROOT_ITEM, byte_limit)) { 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)); } else { TRACE("root %I64x - address %I64x\n", tp.item->key.obj_id, ri->block_number); Status = add_root(Vcb, tp.item->key.obj_id, ri->block_number, ri->generation, &tp); if (!NT_SUCCESS(Status)) { ERR("add_root returned %08lx\n", Status); return Status; } } } else if (tp.item->key.obj_type == TYPE_ROOT_BACKREF && !IsListEmpty(&Vcb->roots)) { root* lastroot = CONTAINING_RECORD(Vcb->roots.Blink, root, list_entry); if (lastroot->id == tp.item->key.obj_id) lastroot->parent = tp.item->key.offset; } b = find_next_item(Vcb, &tp, &next_tp, false, Irp); if (b) tp = next_tp; } while (b); if (!Vcb->readonly && !Vcb->data_reloc_root) { root* reloc_root; INODE_ITEM* ii; uint16_t irlen; INODE_REF* ir; LARGE_INTEGER time; BTRFS_TIME now; WARN("data reloc root doesn't exist, creating it\n"); Status = create_root(Vcb, BTRFS_ROOT_DATA_RELOC, &reloc_root, false, 0, Irp); if (!NT_SUCCESS(Status)) { ERR("create_root returned %08lx\n", Status); return Status; } reloc_root->root_item.inode.generation = 1; reloc_root->root_item.inode.st_size = 3; reloc_root->root_item.inode.st_blocks = Vcb->superblock.node_size; reloc_root->root_item.inode.st_nlink = 1; reloc_root->root_item.inode.st_mode = 040755; reloc_root->root_item.inode.flags = 0x80000000; reloc_root->root_item.inode.flags_ro = 0xffffffff; reloc_root->root_item.objid = SUBVOL_ROOT_INODE; reloc_root->root_item.bytes_used = Vcb->superblock.node_size; ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG); if (!ii) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } KeQuerySystemTime(&time); win_time_to_unix(time, &now); RtlZeroMemory(ii, sizeof(INODE_ITEM)); ii->generation = Vcb->superblock.generation; ii->st_blocks = Vcb->superblock.node_size; ii->st_nlink = 1; ii->st_mode = 040755; ii->st_atime = now; ii->st_ctime = now; ii->st_mtime = now; Status = insert_tree_item(Vcb, reloc_root, SUBVOL_ROOT_INODE, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(ii); return Status; } irlen = (uint16_t)offsetof(INODE_REF, name[0]) + 2; ir = ExAllocatePoolWithTag(PagedPool, irlen, ALLOC_TAG); if (!ir) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ir->index = 0; ir->n = 2; ir->name[0] = '.'; ir->name[1] = '.'; Status = insert_tree_item(Vcb, reloc_root, SUBVOL_ROOT_INODE, TYPE_INODE_REF, SUBVOL_ROOT_INODE, ir, irlen, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(ir); return Status; } Vcb->data_reloc_root = reloc_root; Vcb->need_write = true; } return STATUS_SUCCESS; } static NTSTATUS find_disk_holes(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ device* dev, _In_opt_ PIRP Irp) { KEY searchkey; traverse_ptr tp, next_tp; bool b; uint64_t lastaddr; NTSTATUS Status; InitializeListHead(&dev->space); searchkey.obj_id = 0; searchkey.obj_type = TYPE_DEV_STATS; searchkey.offset = dev->devitem.dev_id; Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, Irp); if (NT_SUCCESS(Status) && !keycmp(tp.item->key, searchkey)) RtlCopyMemory(dev->stats, tp.item->data, min(sizeof(uint64_t) * 5, tp.item->size)); searchkey.obj_id = dev->devitem.dev_id; searchkey.obj_type = TYPE_DEV_EXTENT; searchkey.offset = 0; Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } lastaddr = 0; do { if (tp.item->key.obj_id == dev->devitem.dev_id && tp.item->key.obj_type == TYPE_DEV_EXTENT) { if (tp.item->size >= sizeof(DEV_EXTENT)) { DEV_EXTENT* de = (DEV_EXTENT*)tp.item->data; if (tp.item->key.offset > lastaddr) { Status = add_space_entry(&dev->space, NULL, lastaddr, tp.item->key.offset - lastaddr); if (!NT_SUCCESS(Status)) { ERR("add_space_entry returned %08lx\n", Status); return Status; } } lastaddr = tp.item->key.offset + de->length; } else { 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)); } } b = find_next_item(Vcb, &tp, &next_tp, false, Irp); if (b) { tp = next_tp; if (tp.item->key.obj_id > searchkey.obj_id || tp.item->key.obj_type > searchkey.obj_type) break; } } while (b); if (lastaddr < dev->devitem.num_bytes) { Status = add_space_entry(&dev->space, NULL, lastaddr, dev->devitem.num_bytes - lastaddr); if (!NT_SUCCESS(Status)) { ERR("add_space_entry returned %08lx\n", Status); return Status; } } // The Linux driver doesn't like to allocate chunks within the first megabyte of a device. space_list_subtract2(&dev->space, NULL, 0, 0x100000, NULL, NULL); return STATUS_SUCCESS; } static void add_device_to_list(_In_ device_extension* Vcb, _In_ device* dev) { LIST_ENTRY* le; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev2 = CONTAINING_RECORD(le, device, list_entry); if (dev2->devitem.dev_id > dev->devitem.dev_id) { InsertHeadList(le->Blink, &dev->list_entry); return; } le = le->Flink; } InsertTailList(&Vcb->devices, &dev->list_entry); } _Ret_maybenull_ device* find_device_from_uuid(_In_ device_extension* Vcb, _In_ BTRFS_UUID* uuid) { volume_device_extension* vde; pdo_device_extension* pdode; LIST_ENTRY* le; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); 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, 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], 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]); if (RtlCompareMemory(&dev->devitem.device_uuid, uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { TRACE("returning device %I64x\n", dev->devitem.dev_id); return dev; } le = le->Flink; } vde = Vcb->vde; if (!vde) goto end; pdode = vde->pdode; ExAcquireResourceSharedLite(&pdode->child_lock, true); if (Vcb->devices_loaded < Vcb->superblock.num_devices) { le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); if (RtlCompareMemory(uuid, &vc->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { device* dev; dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG); if (!dev) { ExReleaseResourceLite(&pdode->child_lock); ERR("out of memory\n"); return NULL; } RtlZeroMemory(dev, sizeof(device)); dev->devobj = vc->devobj; dev->fileobj = vc->fileobj; dev->devitem.device_uuid = *uuid; dev->devitem.dev_id = vc->devid; dev->devitem.num_bytes = vc->size; dev->seeding = vc->seeding; dev->readonly = dev->seeding; dev->reloc = false; dev->removable = false; dev->disk_num = vc->disk_num; dev->part_num = vc->part_num; dev->num_trim_entries = 0; InitializeListHead(&dev->trim_list); add_device_to_list(Vcb, dev); Vcb->devices_loaded++; ExReleaseResourceLite(&pdode->child_lock); return dev; } le = le->Flink; } } ExReleaseResourceLite(&pdode->child_lock); end: WARN("could not find device with uuid %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\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], uuid->uuid[8], uuid->uuid[9], uuid->uuid[10], uuid->uuid[11], uuid->uuid[12], uuid->uuid[13], uuid->uuid[14], uuid->uuid[15]); return NULL; } static bool is_device_removable(_In_ PDEVICE_OBJECT devobj) { NTSTATUS Status; STORAGE_HOTPLUG_INFO shi; Status = dev_ioctl(devobj, IOCTL_STORAGE_GET_HOTPLUG_INFO, NULL, 0, &shi, sizeof(STORAGE_HOTPLUG_INFO), true, NULL); if (!NT_SUCCESS(Status)) { ERR("dev_ioctl returned %08lx\n", Status); return false; } return shi.MediaRemovable != 0 ? true : false; } static ULONG get_device_change_count(_In_ PDEVICE_OBJECT devobj) { NTSTATUS Status; ULONG cc; IO_STATUS_BLOCK iosb; Status = dev_ioctl(devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, &cc, sizeof(ULONG), true, &iosb); if (!NT_SUCCESS(Status)) { ERR("dev_ioctl returned %08lx\n", Status); return 0; } if (iosb.Information < sizeof(ULONG)) { ERR("iosb.Information was too short\n"); return 0; } return cc; } void init_device(_In_ device_extension* Vcb, _Inout_ device* dev, _In_ bool get_nums) { NTSTATUS Status; ULONG aptelen; ATA_PASS_THROUGH_EX* apte; STORAGE_PROPERTY_QUERY spq; DEVICE_TRIM_DESCRIPTOR dtd; dev->removable = is_device_removable(dev->devobj); dev->change_count = dev->removable ? get_device_change_count(dev->devobj) : 0; if (get_nums) { STORAGE_DEVICE_NUMBER sdn; Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(STORAGE_DEVICE_NUMBER), true, NULL); if (!NT_SUCCESS(Status)) { WARN("IOCTL_STORAGE_GET_DEVICE_NUMBER returned %08lx\n", Status); dev->disk_num = 0xffffffff; dev->part_num = 0xffffffff; } else { dev->disk_num = sdn.DeviceNumber; dev->part_num = sdn.PartitionNumber; } } dev->trim = false; dev->readonly = dev->seeding; dev->reloc = false; dev->num_trim_entries = 0; dev->stats_changed = false; InitializeListHead(&dev->trim_list); if (!dev->readonly) { Status = dev_ioctl(dev->devobj, IOCTL_DISK_IS_WRITABLE, NULL, 0, NULL, 0, true, NULL); if (Status == STATUS_MEDIA_WRITE_PROTECTED) dev->readonly = true; } aptelen = sizeof(ATA_PASS_THROUGH_EX) + 512; apte = ExAllocatePoolWithTag(NonPagedPool, aptelen, ALLOC_TAG); if (!apte) { ERR("out of memory\n"); return; } RtlZeroMemory(apte, aptelen); apte->Length = sizeof(ATA_PASS_THROUGH_EX); apte->AtaFlags = ATA_FLAGS_DATA_IN; apte->DataTransferLength = aptelen - sizeof(ATA_PASS_THROUGH_EX); apte->TimeOutValue = 3; apte->DataBufferOffset = apte->Length; apte->CurrentTaskFile[6] = IDE_COMMAND_IDENTIFY; Status = dev_ioctl(dev->devobj, IOCTL_ATA_PASS_THROUGH, apte, aptelen, apte, aptelen, true, NULL); if (!NT_SUCCESS(Status)) TRACE("IOCTL_ATA_PASS_THROUGH returned %08lx for IDENTIFY DEVICE\n", Status); else { IDENTIFY_DEVICE_DATA* idd = (IDENTIFY_DEVICE_DATA*)((uint8_t*)apte + sizeof(ATA_PASS_THROUGH_EX)); if (idd->CommandSetSupport.FlushCache) { dev->can_flush = true; TRACE("FLUSH CACHE supported\n"); } else TRACE("FLUSH CACHE not supported\n"); } ExFreePool(apte); #ifdef DEBUG_TRIM_EMULATION dev->trim = true; Vcb->trim = true; #else spq.PropertyId = StorageDeviceTrimProperty; spq.QueryType = PropertyStandardQuery; spq.AdditionalParameters[0] = 0; Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_QUERY_PROPERTY, &spq, sizeof(STORAGE_PROPERTY_QUERY), &dtd, sizeof(DEVICE_TRIM_DESCRIPTOR), true, NULL); if (NT_SUCCESS(Status)) { if (dtd.TrimEnabled) { dev->trim = true; Vcb->trim = true; TRACE("TRIM supported\n"); } else TRACE("TRIM not supported\n"); } #endif RtlZeroMemory(dev->stats, sizeof(uint64_t) * 5); } static NTSTATUS load_chunk_root(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp) { traverse_ptr tp, next_tp; KEY searchkey; bool b; chunk* c; NTSTATUS Status; searchkey.obj_id = 0; searchkey.obj_type = 0; searchkey.offset = 0; Vcb->data_flags = 0; Vcb->metadata_flags = 0; Vcb->system_flags = 0; Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } do { TRACE("(%I64x,%x,%I64x)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); if (tp.item->key.obj_id == 1 && tp.item->key.obj_type == TYPE_DEV_ITEM) { if (tp.item->size < sizeof(DEV_ITEM)) { 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)); } else { DEV_ITEM* di = (DEV_ITEM*)tp.item->data; LIST_ENTRY* le; bool done = false; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (dev->devobj && RtlCompareMemory(&dev->devitem.device_uuid, &di->device_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { RtlCopyMemory(&dev->devitem, tp.item->data, min(tp.item->size, sizeof(DEV_ITEM))); if (le != Vcb->devices.Flink) init_device(Vcb, dev, true); done = true; break; } le = le->Flink; } if (!done && Vcb->vde) { volume_device_extension* vde = Vcb->vde; pdo_device_extension* pdode = vde->pdode; ExAcquireResourceSharedLite(&pdode->child_lock, true); if (Vcb->devices_loaded < Vcb->superblock.num_devices) { le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); if (RtlCompareMemory(&di->device_uuid, &vc->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { device* dev; dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG); if (!dev) { ExReleaseResourceLite(&pdode->child_lock); ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(dev, sizeof(device)); dev->devobj = vc->devobj; dev->fileobj = vc->fileobj; RtlCopyMemory(&dev->devitem, di, min(tp.item->size, sizeof(DEV_ITEM))); dev->seeding = vc->seeding; init_device(Vcb, dev, false); if (dev->devitem.num_bytes > vc->size) { WARN("device %I64x: DEV_ITEM says %I64x bytes, but Windows only reports %I64x\n", tp.item->key.offset, dev->devitem.num_bytes, vc->size); dev->devitem.num_bytes = vc->size; } dev->disk_num = vc->disk_num; dev->part_num = vc->part_num; add_device_to_list(Vcb, dev); Vcb->devices_loaded++; done = true; break; } le = le->Flink; } if (!done) { if (!Vcb->options.allow_degraded) { 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, 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], 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]); } else { device* dev; dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG); if (!dev) { ExReleaseResourceLite(&pdode->child_lock); ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(dev, sizeof(device)); // Missing device, so we keep dev->devobj as NULL RtlCopyMemory(&dev->devitem, di, min(tp.item->size, sizeof(DEV_ITEM))); InitializeListHead(&dev->trim_list); add_device_to_list(Vcb, dev); Vcb->devices_loaded++; } } } else ERR("unexpected device %I64x found\n", tp.item->key.offset); ExReleaseResourceLite(&pdode->child_lock); } } } else if (tp.item->key.obj_type == TYPE_CHUNK_ITEM) { if (tp.item->size < sizeof(CHUNK_ITEM)) { 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)); } else { c = ExAllocatePoolWithTag(NonPagedPool, sizeof(chunk), ALLOC_TAG); if (!c) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } c->size = tp.item->size; c->offset = tp.item->key.offset; c->used = c->oldused = 0; c->cache = c->old_cache = NULL; c->created = false; c->readonly = false; c->reloc = false; c->cache_loaded = false; c->changed = false; c->space_changed = false; c->balance_num = 0; c->chunk_item = ExAllocatePoolWithTag(NonPagedPool, tp.item->size, ALLOC_TAG); if (!c->chunk_item) { ERR("out of memory\n"); ExFreePool(c); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(c->chunk_item, tp.item->data, tp.item->size); if (c->chunk_item->type & BLOCK_FLAG_DATA && c->chunk_item->type > Vcb->data_flags) Vcb->data_flags = c->chunk_item->type; if (c->chunk_item->type & BLOCK_FLAG_METADATA && c->chunk_item->type > Vcb->metadata_flags) Vcb->metadata_flags = c->chunk_item->type; if (c->chunk_item->type & BLOCK_FLAG_SYSTEM && c->chunk_item->type > Vcb->system_flags) Vcb->system_flags = c->chunk_item->type; if (c->chunk_item->type & BLOCK_FLAG_RAID10) { if (c->chunk_item->sub_stripes == 0 || c->chunk_item->sub_stripes > c->chunk_item->num_stripes) { ERR("chunk %I64x: invalid stripes (num_stripes %u, sub_stripes %u)\n", c->offset, c->chunk_item->num_stripes, c->chunk_item->sub_stripes); ExFreePool(c->chunk_item); ExFreePool(c); return STATUS_INTERNAL_ERROR; } } if (c->chunk_item->num_stripes > 0) { CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; uint16_t i; c->devices = ExAllocatePoolWithTag(NonPagedPool, sizeof(device*) * c->chunk_item->num_stripes, ALLOC_TAG); if (!c->devices) { ERR("out of memory\n"); ExFreePool(c->chunk_item); ExFreePool(c); return STATUS_INSUFFICIENT_RESOURCES; } for (i = 0; i < c->chunk_item->num_stripes; i++) { c->devices[i] = find_device_from_uuid(Vcb, &cis[i].dev_uuid); TRACE("device %u = %p\n", i, c->devices[i]); if (!c->devices[i]) { ERR("missing device\n"); ExFreePool(c->chunk_item); ExFreePool(c); return STATUS_INTERNAL_ERROR; } if (c->devices[i]->readonly) c->readonly = true; } } else { ERR("chunk %I64x: number of stripes is 0\n", c->offset); ExFreePool(c->chunk_item); ExFreePool(c); return STATUS_INTERNAL_ERROR; } ExInitializeResourceLite(&c->lock); ExInitializeResourceLite(&c->changed_extents_lock); InitializeListHead(&c->space); InitializeListHead(&c->space_size); InitializeListHead(&c->deleting); InitializeListHead(&c->changed_extents); InitializeListHead(&c->range_locks); ExInitializeResourceLite(&c->range_locks_lock); KeInitializeEvent(&c->range_locks_event, NotificationEvent, false); InitializeListHead(&c->partial_stripes); ExInitializeResourceLite(&c->partial_stripes_lock); c->last_alloc_set = false; c->last_stripe = 0; InsertTailList(&Vcb->chunks, &c->list_entry); c->list_entry_balance.Flink = NULL; } } b = find_next_item(Vcb, &tp, &next_tp, false, Irp); if (b) tp = next_tp; } while (b); Vcb->log_to_phys_loaded = true; if (Vcb->data_flags == 0) Vcb->data_flags = BLOCK_FLAG_DATA | (Vcb->superblock.num_devices > 1 ? BLOCK_FLAG_RAID0 : 0); if (Vcb->metadata_flags == 0) Vcb->metadata_flags = BLOCK_FLAG_METADATA | (Vcb->superblock.num_devices > 1 ? BLOCK_FLAG_RAID1 : BLOCK_FLAG_DUPLICATE); if (Vcb->system_flags == 0) Vcb->system_flags = BLOCK_FLAG_SYSTEM | (Vcb->superblock.num_devices > 1 ? BLOCK_FLAG_RAID1 : BLOCK_FLAG_DUPLICATE); if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS) { Vcb->metadata_flags |= BLOCK_FLAG_DATA; Vcb->data_flags = Vcb->metadata_flags; } return STATUS_SUCCESS; } void protect_superblocks(_Inout_ chunk* c) { uint16_t i = 0, j; uint64_t off_start, off_end; // The Linux driver also protects all the space before the first superblock. // I realize this confuses physical and logical addresses, but this is what btrfs-progs does - // evidently Linux assumes the chunk at 0 is always SINGLE. if (c->offset < superblock_addrs[0]) space_list_subtract(c, c->offset, superblock_addrs[0] - c->offset, NULL); while (superblock_addrs[i] != 0) { CHUNK_ITEM* ci = c->chunk_item; CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1]; if (ci->type & BLOCK_FLAG_RAID0 || ci->type & BLOCK_FLAG_RAID10) { for (j = 0; j < ci->num_stripes; j++) { uint16_t sub_stripes = max(ci->sub_stripes, 1); if (cis[j].offset + (ci->size * ci->num_stripes / sub_stripes) > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) { #ifdef _DEBUG uint64_t startoff; uint16_t startoffstripe; #endif TRACE("cut out superblock in chunk %I64x\n", c->offset); off_start = superblock_addrs[i] - cis[j].offset; off_start -= off_start % ci->stripe_length; off_start *= ci->num_stripes / sub_stripes; off_start += (j / sub_stripes) * ci->stripe_length; off_end = off_start + ci->stripe_length; #ifdef _DEBUG get_raid0_offset(off_start, ci->stripe_length, ci->num_stripes / sub_stripes, &startoff, &startoffstripe); TRACE("j = %u, startoffstripe = %u\n", j, startoffstripe); TRACE("startoff = %I64x, superblock = %I64x\n", startoff + cis[j].offset, superblock_addrs[i]); #endif space_list_subtract(c, c->offset + off_start, off_end - off_start, NULL); } } } else if (ci->type & BLOCK_FLAG_RAID5) { uint64_t stripe_size = ci->size / (ci->num_stripes - 1); for (j = 0; j < ci->num_stripes; j++) { if (cis[j].offset + stripe_size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) { TRACE("cut out superblock in chunk %I64x\n", c->offset); off_start = superblock_addrs[i] - cis[j].offset; off_start -= off_start % ci->stripe_length; off_start *= ci->num_stripes - 1; off_end = sector_align(superblock_addrs[i] - cis[j].offset + sizeof(superblock), ci->stripe_length); off_end *= ci->num_stripes - 1; TRACE("cutting out %I64x, size %I64x\n", c->offset + off_start, off_end - off_start); space_list_subtract(c, c->offset + off_start, off_end - off_start, NULL); } } } else if (ci->type & BLOCK_FLAG_RAID6) { uint64_t stripe_size = ci->size / (ci->num_stripes - 2); for (j = 0; j < ci->num_stripes; j++) { if (cis[j].offset + stripe_size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) { TRACE("cut out superblock in chunk %I64x\n", c->offset); off_start = superblock_addrs[i] - cis[j].offset; off_start -= off_start % ci->stripe_length; off_start *= ci->num_stripes - 2; off_end = sector_align(superblock_addrs[i] - cis[j].offset + sizeof(superblock), ci->stripe_length); off_end *= ci->num_stripes - 2; TRACE("cutting out %I64x, size %I64x\n", c->offset + off_start, off_end - off_start); space_list_subtract(c, c->offset + off_start, off_end - off_start, NULL); } } } else { // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4 for (j = 0; j < ci->num_stripes; j++) { if (cis[j].offset + ci->size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) { TRACE("cut out superblock in chunk %I64x\n", c->offset); // The Linux driver protects the whole stripe in which the superblock lives off_start = ((superblock_addrs[i] - cis[j].offset) / c->chunk_item->stripe_length) * c->chunk_item->stripe_length; off_end = sector_align(superblock_addrs[i] - cis[j].offset + sizeof(superblock), c->chunk_item->stripe_length); space_list_subtract(c, c->offset + off_start, off_end - off_start, NULL); } } } i++; } } NTSTATUS find_chunk_usage(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp) { LIST_ENTRY* le = Vcb->chunks.Flink; chunk* c; KEY searchkey; traverse_ptr tp; BLOCK_GROUP_ITEM* bgi; NTSTATUS Status; root* r; if (Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE) { r = Vcb->block_group_root; if (!r) { ERR("block_group_tree compat_ro flag set but no block group root found\n"); return STATUS_INVALID_PARAMETER; } } else r = Vcb->extent_root; searchkey.obj_type = TYPE_BLOCK_GROUP_ITEM; Vcb->superblock.bytes_used = 0; while (le != &Vcb->chunks) { c = CONTAINING_RECORD(le, chunk, list_entry); searchkey.obj_id = c->offset; searchkey.offset = c->chunk_item->size; Status = find_item(Vcb, r, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (!keycmp(searchkey, tp.item->key)) { if (tp.item->size >= sizeof(BLOCK_GROUP_ITEM)) { bgi = (BLOCK_GROUP_ITEM*)tp.item->data; c->used = c->oldused = bgi->used; TRACE("chunk %I64x has %I64x bytes used\n", c->offset, c->used); Vcb->superblock.bytes_used += bgi->used; } else { ERR("(%I64x;%I64x,%x,%I64x) is %u bytes, expected %Iu\n", r->id, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(BLOCK_GROUP_ITEM)); } } le = le->Flink; } Vcb->chunk_usage_found = true; return STATUS_SUCCESS; } static NTSTATUS load_sys_chunks(_In_ device_extension* Vcb) { KEY key; ULONG n = Vcb->superblock.n; while (n > 0) { if (n > sizeof(KEY)) { RtlCopyMemory(&key, &Vcb->superblock.sys_chunk_array[Vcb->superblock.n - n], sizeof(KEY)); n -= sizeof(KEY); } else return STATUS_SUCCESS; TRACE("bootstrap: %I64x,%x,%I64x\n", key.obj_id, key.obj_type, key.offset); if (key.obj_type == TYPE_CHUNK_ITEM) { CHUNK_ITEM* ci; USHORT cisize; sys_chunk* sc; if (n < sizeof(CHUNK_ITEM)) return STATUS_SUCCESS; ci = (CHUNK_ITEM*)&Vcb->superblock.sys_chunk_array[Vcb->superblock.n - n]; cisize = sizeof(CHUNK_ITEM) + (ci->num_stripes * sizeof(CHUNK_ITEM_STRIPE)); if (n < cisize) return STATUS_SUCCESS; sc = ExAllocatePoolWithTag(PagedPool, sizeof(sys_chunk), ALLOC_TAG); if (!sc) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } sc->key = key; sc->size = cisize; sc->data = ExAllocatePoolWithTag(PagedPool, sc->size, ALLOC_TAG); if (!sc->data) { ERR("out of memory\n"); ExFreePool(sc); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(sc->data, ci, sc->size); InsertTailList(&Vcb->sys_chunks, &sc->list_entry); n -= cisize; } else { ERR("unexpected item %I64x,%x,%I64x in bootstrap\n", key.obj_id, key.obj_type, key.offset); return STATUS_INTERNAL_ERROR; } } return STATUS_SUCCESS; } _Ret_maybenull_ root* find_default_subvol(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp) { LIST_ENTRY* le; static const char fn[] = "default"; static uint32_t crc32 = 0x8dbfc2d2; if (Vcb->options.subvol_id != 0) { le = Vcb->roots.Flink; while (le != &Vcb->roots) { root* r = CONTAINING_RECORD(le, root, list_entry); if (r->id == Vcb->options.subvol_id) return r; le = le->Flink; } } if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_DEFAULT_SUBVOL) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; DIR_ITEM* di; searchkey.obj_id = Vcb->superblock.root_dir_objectid; searchkey.obj_type = TYPE_DIR_ITEM; searchkey.offset = crc32; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); goto end; } if (keycmp(tp.item->key, searchkey)) { ERR("could not find (%I64x,%x,%I64x) in root tree\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); goto end; } if (tp.item->size < sizeof(DIR_ITEM)) { 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)); goto end; } di = (DIR_ITEM*)tp.item->data; if (tp.item->size < sizeof(DIR_ITEM) - 1 + di->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); goto end; } if (di->n != strlen(fn) || RtlCompareMemory(di->name, fn, di->n) != di->n) { ERR("root DIR_ITEM had same CRC32, but was not \"default\"\n"); goto end; } if (di->key.obj_type != TYPE_ROOT_ITEM) { ERR("default root has key (%I64x,%x,%I64x), expected subvolume\n", di->key.obj_id, di->key.obj_type, di->key.offset); goto end; } le = Vcb->roots.Flink; while (le != &Vcb->roots) { root* r = CONTAINING_RECORD(le, root, list_entry); if (r->id == di->key.obj_id) return r; le = le->Flink; } ERR("could not find root %I64x, using default instead\n", di->key.obj_id); } end: le = Vcb->roots.Flink; while (le != &Vcb->roots) { root* r = CONTAINING_RECORD(le, root, list_entry); if (r->id == BTRFS_ROOT_FSTREE) return r; le = le->Flink; } return NULL; } void init_file_cache(_In_ PFILE_OBJECT FileObject, _In_ CC_FILE_SIZES* ccfs) { TRACE("(%p, %p)\n", FileObject, ccfs); CcInitializeCacheMap(FileObject, ccfs, false, &cache_callbacks, FileObject); if (diskacc) fCcSetAdditionalCacheAttributesEx(FileObject, CC_ENABLE_DISK_IO_ACCOUNTING); CcSetReadAheadGranularity(FileObject, READ_AHEAD_GRANULARITY); } uint32_t get_num_of_processors() { KAFFINITY p = KeQueryActiveProcessors(); uint32_t r = 0; while (p != 0) { if (p & 1) r++; p >>= 1; } return r; } static NTSTATUS create_calc_threads(_In_ PDEVICE_OBJECT DeviceObject) { device_extension* Vcb = DeviceObject->DeviceExtension; OBJECT_ATTRIBUTES oa; ULONG i; Vcb->calcthreads.num_threads = get_num_of_processors(); Vcb->calcthreads.threads = ExAllocatePoolWithTag(NonPagedPool, sizeof(drv_calc_thread) * Vcb->calcthreads.num_threads, ALLOC_TAG); if (!Vcb->calcthreads.threads) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } InitializeListHead(&Vcb->calcthreads.job_list); KeInitializeSpinLock(&Vcb->calcthreads.spinlock); KeInitializeEvent(&Vcb->calcthreads.event, NotificationEvent, false); RtlZeroMemory(Vcb->calcthreads.threads, sizeof(drv_calc_thread) * Vcb->calcthreads.num_threads); InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); for (i = 0; i < Vcb->calcthreads.num_threads; i++) { NTSTATUS Status; Vcb->calcthreads.threads[i].DeviceObject = DeviceObject; Vcb->calcthreads.threads[i].number = i; KeInitializeEvent(&Vcb->calcthreads.threads[i].finished, NotificationEvent, false); Status = PsCreateSystemThread(&Vcb->calcthreads.threads[i].handle, 0, &oa, NULL, NULL, calc_thread, &Vcb->calcthreads.threads[i]); if (!NT_SUCCESS(Status)) { ULONG j; ERR("PsCreateSystemThread returned %08lx\n", Status); for (j = 0; j < i; j++) { Vcb->calcthreads.threads[i].quit = true; } KeSetEvent(&Vcb->calcthreads.event, 0, false); return Status; } } return STATUS_SUCCESS; } static bool is_btrfs_volume(_In_ PDEVICE_OBJECT DeviceObject) { NTSTATUS Status; MOUNTDEV_NAME mdn, *mdn2; ULONG mdnsize; Status = dev_ioctl(DeviceObject, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) { ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status); return false; } mdnsize = (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength; mdn2 = ExAllocatePoolWithTag(PagedPool, mdnsize, ALLOC_TAG); if (!mdn2) { ERR("out of memory\n"); return false; } Status = dev_ioctl(DeviceObject, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, mdn2, mdnsize, true, NULL); if (!NT_SUCCESS(Status)) { ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status); ExFreePool(mdn2); return false; } if (mdn2->NameLength > (sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)) && RtlCompareMemory(mdn2->Name, BTRFS_VOLUME_PREFIX, sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)) == sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)) { ExFreePool(mdn2); return true; } ExFreePool(mdn2); return false; } static NTSTATUS get_device_pnp_name_guid(_In_ PDEVICE_OBJECT DeviceObject, _Out_ PUNICODE_STRING pnp_name, _In_ const GUID* guid) { NTSTATUS Status; WCHAR *list = NULL, *s; Status = IoGetDeviceInterfaces((PVOID)guid, NULL, 0, &list); if (!NT_SUCCESS(Status)) { ERR("IoGetDeviceInterfaces returned %08lx\n", Status); return Status; } s = list; while (s[0] != 0) { PFILE_OBJECT FileObject; PDEVICE_OBJECT devobj; UNICODE_STRING name; name.Length = name.MaximumLength = (USHORT)wcslen(s) * sizeof(WCHAR); name.Buffer = s; if (NT_SUCCESS(IoGetDeviceObjectPointer(&name, FILE_READ_ATTRIBUTES, &FileObject, &devobj))) { if (DeviceObject == devobj || DeviceObject == FileObject->DeviceObject) { ObDereferenceObject(FileObject); pnp_name->Buffer = ExAllocatePoolWithTag(PagedPool, name.Length, ALLOC_TAG); if (!pnp_name->Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(pnp_name->Buffer, name.Buffer, name.Length); pnp_name->Length = pnp_name->MaximumLength = name.Length; Status = STATUS_SUCCESS; goto end; } ObDereferenceObject(FileObject); } s = &s[wcslen(s) + 1]; } pnp_name->Length = pnp_name->MaximumLength = 0; pnp_name->Buffer = 0; Status = STATUS_NOT_FOUND; end: if (list) ExFreePool(list); return Status; } NTSTATUS get_device_pnp_name(_In_ PDEVICE_OBJECT DeviceObject, _Out_ PUNICODE_STRING pnp_name, _Out_ const GUID** guid) { NTSTATUS Status; Status = get_device_pnp_name_guid(DeviceObject, pnp_name, &GUID_DEVINTERFACE_VOLUME); if (NT_SUCCESS(Status)) { *guid = &GUID_DEVINTERFACE_VOLUME; return Status; } Status = get_device_pnp_name_guid(DeviceObject, pnp_name, &GUID_DEVINTERFACE_HIDDEN_VOLUME); if (NT_SUCCESS(Status)) { *guid = &GUID_DEVINTERFACE_HIDDEN_VOLUME; return Status; } Status = get_device_pnp_name_guid(DeviceObject, pnp_name, &GUID_DEVINTERFACE_DISK); if (NT_SUCCESS(Status)) { *guid = &GUID_DEVINTERFACE_DISK; return Status; } return STATUS_NOT_FOUND; } _Success_(return>=0) static NTSTATUS check_mount_device(_In_ PDEVICE_OBJECT DeviceObject, _Out_ bool* pno_pnp) { NTSTATUS Status; ULONG to_read; superblock* sb; UNICODE_STRING pnp_name; const GUID* guid; to_read = DeviceObject->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), DeviceObject->SectorSize); sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG); if (!sb) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } Status = sync_read_phys(DeviceObject, NULL, superblock_addrs[0], to_read, (PUCHAR)sb, true); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); goto end; } if (sb->magic != BTRFS_MAGIC) { Status = STATUS_SUCCESS; goto end; } if (!check_superblock_checksum(sb)) { Status = STATUS_SUCCESS; goto end; } DeviceObject->Flags &= ~DO_VERIFY_VOLUME; pnp_name.Buffer = NULL; Status = get_device_pnp_name(DeviceObject, &pnp_name, &guid); if (!NT_SUCCESS(Status)) { WARN("get_device_pnp_name returned %08lx\n", Status); pnp_name.Length = 0; } *pno_pnp = pnp_name.Length == 0; if (pnp_name.Buffer) ExFreePool(pnp_name.Buffer); Status = STATUS_SUCCESS; end: ExFreePool(sb); return Status; } static bool still_has_superblock(_In_ PDEVICE_OBJECT device, _In_ PFILE_OBJECT fileobj) { NTSTATUS Status; ULONG to_read; superblock* sb; if (!device) return false; to_read = device->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), device->SectorSize); sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG); if (!sb) { ERR("out of memory\n"); return false; } Status = sync_read_phys(device, fileobj, superblock_addrs[0], to_read, (PUCHAR)sb, true); if (!NT_SUCCESS(Status)) { ERR("Failed to read superblock: %08lx\n", Status); ExFreePool(sb); return false; } if (sb->magic != BTRFS_MAGIC) { TRACE("not a BTRFS volume\n"); ExFreePool(sb); return false; } else { if (!check_superblock_checksum(sb)) { ExFreePool(sb); return false; } } ObReferenceObject(device); while (device) { PDEVICE_OBJECT device2 = IoGetLowerDeviceObject(device); device->Flags &= ~DO_VERIFY_VOLUME; ObDereferenceObject(device); device = device2; } ExFreePool(sb); return true; } static void calculate_sector_shift(device_extension* Vcb) { uint32_t ss = Vcb->superblock.sector_size; Vcb->sector_shift = 0; while (!(ss & 1)) { Vcb->sector_shift++; ss >>= 1; } } static NTSTATUS mount_vol(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { PIO_STACK_LOCATION IrpSp; PDEVICE_OBJECT NewDeviceObject = NULL; PDEVICE_OBJECT DeviceToMount, readobj; PFILE_OBJECT fileobj; NTSTATUS Status; device_extension* Vcb = NULL; LIST_ENTRY *le, batchlist; KEY searchkey; traverse_ptr tp; fcb* root_fcb = NULL; ccb* root_ccb = NULL; bool init_lookaside = false; device* dev; volume_device_extension* vde = NULL; pdo_device_extension* pdode = NULL; volume_child* vc; uint64_t readobjsize; OBJECT_ATTRIBUTES oa; device_extension* real_devext; KIRQL irql; TRACE("(%p, %p)\n", DeviceObject, Irp); if (DeviceObject != master_devobj) return STATUS_INVALID_DEVICE_REQUEST; IrpSp = IoGetCurrentIrpStackLocation(Irp); DeviceToMount = IrpSp->Parameters.MountVolume.DeviceObject; real_devext = IrpSp->Parameters.MountVolume.Vpb->RealDevice->DeviceExtension; // Make sure we're not trying to mount the PDO if (IrpSp->Parameters.MountVolume.Vpb->RealDevice->DriverObject == drvobj && real_devext->type == VCB_TYPE_PDO) return STATUS_UNRECOGNIZED_VOLUME; if (!is_btrfs_volume(DeviceToMount)) { bool not_pnp = false; Status = check_mount_device(DeviceToMount, ¬_pnp); if (!NT_SUCCESS(Status)) WARN("check_mount_device returned %08lx\n", Status); if (!not_pnp) { Status = STATUS_UNRECOGNIZED_VOLUME; goto exit; } } else { PDEVICE_OBJECT pdo; pdo = DeviceToMount; ObReferenceObject(pdo); while (true) { PDEVICE_OBJECT pdo2 = IoGetLowerDeviceObject(pdo); ObDereferenceObject(pdo); if (!pdo2) break; else pdo = pdo2; } ExAcquireResourceSharedLite(&pdo_list_lock, true); le = pdo_list.Flink; while (le != &pdo_list) { pdo_device_extension* pdode2 = CONTAINING_RECORD(le, pdo_device_extension, list_entry); if (pdode2->pdo == pdo) { vde = pdode2->vde; break; } le = le->Flink; } ExReleaseResourceLite(&pdo_list_lock); if (!vde || vde->type != VCB_TYPE_VOLUME) { vde = NULL; Status = STATUS_UNRECOGNIZED_VOLUME; goto exit; } } if (vde) { pdode = vde->pdode; ExAcquireResourceExclusiveLite(&pdode->child_lock, true); le = pdode->children.Flink; while (le != &pdode->children) { LIST_ENTRY* le2 = le->Flink; vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry); if (!still_has_superblock(vc->devobj, vc->fileobj)) { remove_volume_child(vde, vc, false); if (pdode->num_children == 0) { ERR("error - number of devices is zero\n"); Status = STATUS_INTERNAL_ERROR; ExReleaseResourceLite(&pdode->child_lock); goto exit; } Status = STATUS_DEVICE_NOT_READY; ExReleaseResourceLite(&pdode->child_lock); goto exit; } le = le2; } if (pdode->num_children == 0 || pdode->children_loaded == 0) { ERR("error - number of devices is zero\n"); Status = STATUS_INTERNAL_ERROR; ExReleaseResourceLite(&pdode->child_lock); goto exit; } ExConvertExclusiveToSharedLite(&pdode->child_lock); vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry); readobj = vc->devobj; fileobj = vc->fileobj; readobjsize = vc->size; vde->device->Characteristics &= ~FILE_DEVICE_SECURE_OPEN; } else { GET_LENGTH_INFORMATION gli; vc = NULL; readobj = DeviceToMount; fileobj = NULL; Status = dev_ioctl(readobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli), true, NULL); if (!NT_SUCCESS(Status)) { ERR("error reading length information: %08lx\n", Status); goto exit; } readobjsize = gli.Length.QuadPart; } Status = IoCreateDevice(drvobj, sizeof(device_extension), NULL, FILE_DEVICE_DISK_FILE_SYSTEM, 0, false, &NewDeviceObject); if (!NT_SUCCESS(Status)) { ERR("IoCreateDevice returned %08lx\n", Status); Status = STATUS_UNRECOGNIZED_VOLUME; if (pdode) ExReleaseResourceLite(&pdode->child_lock); goto exit; } NewDeviceObject->Flags |= DO_DIRECT_IO; // Some programs seem to expect that the sector size will be 512, for // FILE_NO_INTERMEDIATE_BUFFERING and the like. NewDeviceObject->SectorSize = min(DeviceToMount->SectorSize, 512); Vcb = (PVOID)NewDeviceObject->DeviceExtension; RtlZeroMemory(Vcb, sizeof(device_extension)); Vcb->type = VCB_TYPE_FS; Vcb->vde = vde; ExInitializeResourceLite(&Vcb->tree_lock); Vcb->need_write = false; ExInitializeResourceLite(&Vcb->fcb_lock); ExInitializeResourceLite(&Vcb->fileref_lock); ExInitializeResourceLite(&Vcb->chunk_lock); ExInitializeResourceLite(&Vcb->dirty_fcbs_lock); ExInitializeResourceLite(&Vcb->dirty_filerefs_lock); ExInitializeResourceLite(&Vcb->dirty_subvols_lock); ExInitializeResourceLite(&Vcb->scrub.stats_lock); ExInitializeResourceLite(&Vcb->load_lock); ExAcquireResourceExclusiveLite(&Vcb->load_lock, true); ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); DeviceToMount->Flags |= DO_DIRECT_IO; Status = read_superblock(Vcb, readobj, fileobj, readobjsize); if (!NT_SUCCESS(Status)) { if (!IoIsErrorUserInduced(Status)) Status = STATUS_UNRECOGNIZED_VOLUME; else if (Irp->Tail.Overlay.Thread) IoSetHardErrorOrVerifyDevice(Irp, readobj); if (pdode) ExReleaseResourceLite(&pdode->child_lock); goto exit; } if (!vde && Vcb->superblock.num_devices > 1) { ERR("cannot mount multi-device FS with non-PNP device\n"); Status = STATUS_UNRECOGNIZED_VOLUME; goto exit; } Status = registry_load_volume_options(Vcb); if (!NT_SUCCESS(Status)) { ERR("registry_load_volume_options returned %08lx\n", Status); if (pdode) ExReleaseResourceLite(&pdode->child_lock); goto exit; } if (pdode) { if (RtlCompareMemory(&boot_uuid, &pdode->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID) && boot_subvol != 0) Vcb->options.subvol_id = boot_subvol; if (pdode->children_loaded < pdode->num_children && (!Vcb->options.allow_degraded || !finished_probing || degraded_wait)) { ERR("could not mount as %I64u device(s) missing\n", pdode->num_children - pdode->children_loaded); Status = STATUS_DEVICE_NOT_READY; ExReleaseResourceLite(&pdode->child_lock); goto exit; } // Windows holds DeviceObject->DeviceLock, guaranteeing that mount_vol is serialized ExReleaseResourceLite(&pdode->child_lock); } if (Vcb->options.ignore) { TRACE("ignoring volume\n"); Status = STATUS_UNRECOGNIZED_VOLUME; goto exit; } if (Vcb->superblock.incompat_flags & ~INCOMPAT_SUPPORTED) { WARN("cannot mount because of unsupported incompat flags (%I64x)\n", Vcb->superblock.incompat_flags & ~INCOMPAT_SUPPORTED); Status = STATUS_UNRECOGNIZED_VOLUME; goto exit; } if (!(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_METADATA_UUID)) Vcb->superblock.metadata_uuid = Vcb->superblock.uuid; Vcb->readonly = false; if (Vcb->superblock.compat_ro_flags & ~COMPAT_RO_SUPPORTED) { WARN("mounting read-only because of unsupported flags (%I64x)\n", Vcb->superblock.compat_ro_flags & ~COMPAT_RO_SUPPORTED); Vcb->readonly = true; } if (Vcb->options.readonly) Vcb->readonly = true; calculate_sector_shift(Vcb); Vcb->superblock.generation++; Vcb->superblock.incompat_flags |= BTRFS_INCOMPAT_FLAGS_MIXED_BACKREF; if (Vcb->superblock.log_tree_addr != 0) { FIXME("FIXME - replay transaction log (clearing for now)\n"); Vcb->superblock.log_tree_addr = 0; } switch (Vcb->superblock.csum_type) { case CSUM_TYPE_CRC32C: Vcb->csum_size = sizeof(uint32_t); break; case CSUM_TYPE_XXHASH: Vcb->csum_size = sizeof(uint64_t); break; case CSUM_TYPE_SHA256: Vcb->csum_size = SHA256_HASH_SIZE; break; case CSUM_TYPE_BLAKE2: Vcb->csum_size = BLAKE2_HASH_SIZE; break; default: ERR("unrecognized csum type %x\n", Vcb->superblock.csum_type); break; } InitializeListHead(&Vcb->devices); dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG); if (!dev) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } dev->devobj = readobj; dev->fileobj = fileobj; RtlCopyMemory(&dev->devitem, &Vcb->superblock.dev_item, sizeof(DEV_ITEM)); if (dev->devitem.num_bytes > readobjsize) { WARN("device %I64x: DEV_ITEM says %I64x bytes, but Windows only reports %I64x\n", dev->devitem.dev_id, dev->devitem.num_bytes, readobjsize); dev->devitem.num_bytes = readobjsize; } dev->seeding = Vcb->superblock.flags & BTRFS_SUPERBLOCK_FLAGS_SEEDING ? true : false; init_device(Vcb, dev, true); InsertTailList(&Vcb->devices, &dev->list_entry); Vcb->devices_loaded = 1; if (DeviceToMount->Flags & DO_SYSTEM_BOOT_PARTITION) Vcb->disallow_dismount = true; TRACE("DeviceToMount = %p\n", DeviceToMount); TRACE("IrpSp->Parameters.MountVolume.Vpb = %p\n", IrpSp->Parameters.MountVolume.Vpb); NewDeviceObject->StackSize = DeviceToMount->StackSize + 1; NewDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; InitializeListHead(&Vcb->roots); InitializeListHead(&Vcb->drop_roots); Vcb->log_to_phys_loaded = false; add_root(Vcb, BTRFS_ROOT_CHUNK, Vcb->superblock.chunk_tree_addr, Vcb->superblock.chunk_root_generation, NULL); if (!Vcb->chunk_root) { ERR("Could not load chunk root.\n"); Status = STATUS_INTERNAL_ERROR; goto exit; } InitializeListHead(&Vcb->sys_chunks); Status = load_sys_chunks(Vcb); if (!NT_SUCCESS(Status)) { ERR("load_sys_chunks returned %08lx\n", Status); goto exit; } InitializeListHead(&Vcb->chunks); InitializeListHead(&Vcb->trees); InitializeListHead(&Vcb->trees_hash); InitializeListHead(&Vcb->all_fcbs); InitializeListHead(&Vcb->dirty_fcbs); InitializeListHead(&Vcb->dirty_filerefs); InitializeListHead(&Vcb->dirty_subvols); InitializeListHead(&Vcb->send_ops); ExInitializeFastMutex(&Vcb->trees_list_mutex); InitializeListHead(&Vcb->DirNotifyList); InitializeListHead(&Vcb->scrub.errors); FsRtlNotifyInitializeSync(&Vcb->NotifySync); ExInitializePagedLookasideList(&Vcb->tree_data_lookaside, NULL, NULL, 0, sizeof(tree_data), ALLOC_TAG, 0); ExInitializePagedLookasideList(&Vcb->traverse_ptr_lookaside, NULL, NULL, 0, sizeof(traverse_ptr), ALLOC_TAG, 0); ExInitializePagedLookasideList(&Vcb->batch_item_lookaside, NULL, NULL, 0, sizeof(batch_item), ALLOC_TAG, 0); ExInitializePagedLookasideList(&Vcb->fileref_lookaside, NULL, NULL, 0, sizeof(file_ref), ALLOC_TAG, 0); ExInitializePagedLookasideList(&Vcb->fcb_lookaside, NULL, NULL, 0, sizeof(fcb), ALLOC_TAG, 0); ExInitializePagedLookasideList(&Vcb->name_bit_lookaside, NULL, NULL, 0, sizeof(name_bit), ALLOC_TAG, 0); ExInitializeNPagedLookasideList(&Vcb->range_lock_lookaside, NULL, NULL, 0, sizeof(range_lock), ALLOC_TAG, 0); ExInitializeNPagedLookasideList(&Vcb->fcb_np_lookaside, NULL, NULL, 0, sizeof(fcb_nonpaged), ALLOC_TAG, 0); init_lookaside = true; Vcb->Vpb = IrpSp->Parameters.MountVolume.Vpb; Status = load_chunk_root(Vcb, Irp); if (!NT_SUCCESS(Status)) { ERR("load_chunk_root returned %08lx\n", Status); goto exit; } if (Vcb->superblock.num_devices > 1) { if (Vcb->devices_loaded < Vcb->superblock.num_devices && (!Vcb->options.allow_degraded || !finished_probing)) { ERR("could not mount as %I64u device(s) missing\n", Vcb->superblock.num_devices - Vcb->devices_loaded); IoRaiseInformationalHardError(IO_ERR_INTERNAL_ERROR, NULL, NULL); Status = STATUS_INTERNAL_ERROR; goto exit; } if (dev->readonly && !Vcb->readonly) { Vcb->readonly = true; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev2 = CONTAINING_RECORD(le, device, list_entry); if (dev2->readonly && !dev2->seeding) break; if (!dev2->readonly) { Vcb->readonly = false; break; } le = le->Flink; } if (Vcb->readonly) WARN("setting volume to readonly\n"); } } else { if (dev->readonly) { WARN("setting volume to readonly as device is readonly\n"); Vcb->readonly = true; } } add_root(Vcb, BTRFS_ROOT_ROOT, Vcb->superblock.root_tree_addr, Vcb->superblock.generation - 1, NULL); if (!Vcb->root_root) { ERR("Could not load root of roots.\n"); Status = STATUS_INTERNAL_ERROR; goto exit; } Status = look_for_roots(Vcb, Irp); if (!NT_SUCCESS(Status)) { ERR("look_for_roots returned %08lx\n", Status); goto exit; } if (!Vcb->readonly) { Status = find_chunk_usage(Vcb, Irp); if (!NT_SUCCESS(Status)) { ERR("find_chunk_usage returned %08lx\n", Status); goto exit; } } InitializeListHead(&batchlist); // We've already increased the generation by one if (!Vcb->readonly && ( Vcb->options.clear_cache || (!(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE) && Vcb->superblock.generation - 1 != Vcb->superblock.cache_generation) || (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)))) { if (Vcb->options.clear_cache) WARN("ClearCache option was set, clearing cache...\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)) WARN("clearing free-space tree created by buggy Linux driver\n"); else WARN("generation was %I64x, free-space cache generation was %I64x; clearing cache...\n", Vcb->superblock.generation - 1, Vcb->superblock.cache_generation); Status = clear_free_space_cache(Vcb, &batchlist, Irp); if (!NT_SUCCESS(Status)) { ERR("clear_free_space_cache returned %08lx\n", Status); clear_batch_list(Vcb, &batchlist); goto exit; } } Status = commit_batch_list(Vcb, &batchlist, Irp); if (!NT_SUCCESS(Status)) { ERR("commit_batch_list returned %08lx\n", Status); goto exit; } Vcb->volume_fcb = create_fcb(Vcb, NonPagedPool); if (!Vcb->volume_fcb) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Vcb->volume_fcb->Vcb = Vcb; Vcb->volume_fcb->sd = NULL; Vcb->dummy_fcb = create_fcb(Vcb, NonPagedPool); if (!Vcb->dummy_fcb) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Vcb->dummy_fcb->Vcb = Vcb; Vcb->dummy_fcb->type = BTRFS_TYPE_DIRECTORY; Vcb->dummy_fcb->inode = 2; Vcb->dummy_fcb->subvol = Vcb->root_root; Vcb->dummy_fcb->atts = FILE_ATTRIBUTE_DIRECTORY; Vcb->dummy_fcb->inode_item.st_nlink = 1; Vcb->dummy_fcb->inode_item.st_mode = __S_IFDIR; Vcb->dummy_fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG); if (!Vcb->dummy_fcb->hash_ptrs) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlZeroMemory(Vcb->dummy_fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256); Vcb->dummy_fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG); if (!Vcb->dummy_fcb->hash_ptrs_uc) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlZeroMemory(Vcb->dummy_fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256); root_fcb = create_fcb(Vcb, NonPagedPool); if (!root_fcb) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } root_fcb->Vcb = Vcb; root_fcb->inode = SUBVOL_ROOT_INODE; root_fcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&root_fcb->inode, sizeof(uint64_t)); root_fcb->type = BTRFS_TYPE_DIRECTORY; #ifdef DEBUG_FCB_REFCOUNTS WARN("volume FCB = %p\n", Vcb->volume_fcb); WARN("root FCB = %p\n", root_fcb); #endif root_fcb->subvol = find_default_subvol(Vcb, Irp); if (!root_fcb->subvol) { ERR("could not find top subvol\n"); Status = STATUS_INTERNAL_ERROR; goto exit; } Status = load_dir_children(Vcb, root_fcb, true, Irp); if (!NT_SUCCESS(Status)) { ERR("load_dir_children returned %08lx\n", Status); goto exit; } searchkey.obj_id = root_fcb->inode; searchkey.obj_type = TYPE_INODE_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, root_fcb->subvol, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); goto exit; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { ERR("couldn't find INODE_ITEM for root directory\n"); Status = STATUS_INTERNAL_ERROR; goto exit; } if (tp.item->size > 0) RtlCopyMemory(&root_fcb->inode_item, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size)); fcb_get_sd(root_fcb, NULL, true, Irp); root_fcb->atts = get_file_attributes(Vcb, root_fcb->subvol, root_fcb->inode, root_fcb->type, false, false, Irp); if (root_fcb->subvol->id == BTRFS_ROOT_FSTREE) root_fcb->atts &= ~FILE_ATTRIBUTE_HIDDEN; Vcb->root_fileref = create_fileref(Vcb); if (!Vcb->root_fileref) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Vcb->root_fileref->fcb = root_fcb; InsertTailList(&root_fcb->subvol->fcbs, &root_fcb->list_entry); InsertTailList(&Vcb->all_fcbs, &root_fcb->list_entry_all); root_fcb->subvol->fcbs_ptrs[root_fcb->hash >> 24] = &root_fcb->list_entry; root_fcb->fileref = Vcb->root_fileref; root_ccb = ExAllocatePoolWithTag(PagedPool, sizeof(ccb), ALLOC_TAG); if (!root_ccb) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Vcb->root_file = IoCreateStreamFileObject(NULL, DeviceToMount); Vcb->root_file->FsContext = root_fcb; Vcb->root_file->SectionObjectPointer = &root_fcb->nonpaged->segment_object; Vcb->root_file->Vpb = DeviceObject->Vpb; RtlZeroMemory(root_ccb, sizeof(ccb)); root_ccb->NodeType = BTRFS_NODE_TYPE_CCB; root_ccb->NodeSize = sizeof(ccb); Vcb->root_file->FsContext2 = root_ccb; try { CcInitializeCacheMap(Vcb->root_file, (PCC_FILE_SIZES)(&root_fcb->Header.AllocationSize), false, &cache_callbacks, Vcb->root_file); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); goto exit; } le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev2 = CONTAINING_RECORD(le, device, list_entry); Status = find_disk_holes(Vcb, dev2, Irp); if (!NT_SUCCESS(Status)) { ERR("find_disk_holes returned %08lx\n", Status); goto exit; } le = le->Flink; } IoAcquireVpbSpinLock(&irql); NewDeviceObject->Vpb = IrpSp->Parameters.MountVolume.Vpb; IrpSp->Parameters.MountVolume.Vpb->DeviceObject = NewDeviceObject; IrpSp->Parameters.MountVolume.Vpb->Flags |= VPB_MOUNTED; NewDeviceObject->Vpb->VolumeLabelLength = 4; // FIXME NewDeviceObject->Vpb->VolumeLabel[0] = '?'; NewDeviceObject->Vpb->VolumeLabel[1] = 0; NewDeviceObject->Vpb->ReferenceCount++; IoReleaseVpbSpinLock(irql); KeInitializeEvent(&Vcb->flush_thread_finished, NotificationEvent, false); InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); Status = PsCreateSystemThread(&Vcb->flush_thread_handle, 0, &oa, NULL, NULL, flush_thread, NewDeviceObject); if (!NT_SUCCESS(Status)) { ERR("PsCreateSystemThread returned %08lx\n", Status); goto exit; } Status = create_calc_threads(NewDeviceObject); if (!NT_SUCCESS(Status)) { ERR("create_calc_threads returned %08lx\n", Status); goto exit; } Status = registry_mark_volume_mounted(&Vcb->superblock.uuid); if (!NT_SUCCESS(Status)) WARN("registry_mark_volume_mounted returned %08lx\n", Status); Status = look_for_balance_item(Vcb); if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) WARN("look_for_balance_item returned %08lx\n", Status); Status = STATUS_SUCCESS; if (vde) vde->mounted_device = NewDeviceObject; Vcb->devobj = NewDeviceObject; ExInitializeResourceLite(&Vcb->send_load_lock); exit: if (Vcb) { ExReleaseResourceLite(&Vcb->tree_lock); ExReleaseResourceLite(&Vcb->load_lock); } if (!NT_SUCCESS(Status)) { if (Vcb) { if (init_lookaside) { ExDeletePagedLookasideList(&Vcb->tree_data_lookaside); ExDeletePagedLookasideList(&Vcb->traverse_ptr_lookaside); ExDeletePagedLookasideList(&Vcb->batch_item_lookaside); ExDeletePagedLookasideList(&Vcb->fileref_lookaside); ExDeletePagedLookasideList(&Vcb->fcb_lookaside); ExDeletePagedLookasideList(&Vcb->name_bit_lookaside); ExDeleteNPagedLookasideList(&Vcb->range_lock_lookaside); ExDeleteNPagedLookasideList(&Vcb->fcb_np_lookaside); } if (Vcb->root_file) ObDereferenceObject(Vcb->root_file); else if (Vcb->root_fileref) free_fileref(Vcb->root_fileref); else if (root_fcb) free_fcb(root_fcb); if (root_fcb && root_fcb->refcount == 0) reap_fcb(root_fcb); if (Vcb->volume_fcb) reap_fcb(Vcb->volume_fcb); ExDeleteResourceLite(&Vcb->tree_lock); ExDeleteResourceLite(&Vcb->load_lock); ExDeleteResourceLite(&Vcb->fcb_lock); ExDeleteResourceLite(&Vcb->fileref_lock); ExDeleteResourceLite(&Vcb->chunk_lock); ExDeleteResourceLite(&Vcb->dirty_fcbs_lock); ExDeleteResourceLite(&Vcb->dirty_filerefs_lock); ExDeleteResourceLite(&Vcb->dirty_subvols_lock); ExDeleteResourceLite(&Vcb->scrub.stats_lock); if (Vcb->devices.Flink) { while (!IsListEmpty(&Vcb->devices)) { device* dev2 = CONTAINING_RECORD(RemoveHeadList(&Vcb->devices), device, list_entry); ExFreePool(dev2); } } } if (NewDeviceObject) IoDeleteDevice(NewDeviceObject); } else { ExAcquireResourceExclusiveLite(&global_loading_lock, true); InsertTailList(&VcbList, &Vcb->list_entry); ExReleaseResourceLite(&global_loading_lock); FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_MOUNT); } TRACE("mount_vol done (status: %lx)\n", Status); return Status; } static NTSTATUS verify_device(_In_ device_extension* Vcb, _Inout_ device* dev) { NTSTATUS Status; superblock* sb; ULONG to_read, cc; if (!dev->devobj) return STATUS_WRONG_VOLUME; if (dev->removable) { IO_STATUS_BLOCK iosb; Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, &cc, sizeof(ULONG), true, &iosb); if (IoIsErrorUserInduced(Status)) { ERR("IOCTL_STORAGE_CHECK_VERIFY returned %08lx (user-induced)\n", Status); if (Vcb->vde) { pdo_device_extension* pdode = Vcb->vde->pdode; LIST_ENTRY* le2; bool changed = false; ExAcquireResourceExclusiveLite(&pdode->child_lock, true); le2 = pdode->children.Flink; while (le2 != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry); if (vc->devobj == dev->devobj) { TRACE("removing device\n"); remove_volume_child(Vcb->vde, vc, true); changed = true; break; } le2 = le2->Flink; } if (!changed) ExReleaseResourceLite(&pdode->child_lock); } } else if (!NT_SUCCESS(Status)) { ERR("IOCTL_STORAGE_CHECK_VERIFY returned %08lx\n", Status); return Status; } else if (iosb.Information < sizeof(ULONG)) { ERR("iosb.Information was too short\n"); return STATUS_INTERNAL_ERROR; } dev->change_count = cc; } to_read = dev->devobj->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), dev->devobj->SectorSize); sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG); if (!sb) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } Status = sync_read_phys(dev->devobj, dev->fileobj, superblock_addrs[0], to_read, (PUCHAR)sb, true); if (!NT_SUCCESS(Status)) { ERR("Failed to read superblock: %08lx\n", Status); ExFreePool(sb); return Status; } if (sb->magic != BTRFS_MAGIC) { ERR("not a BTRFS volume\n"); ExFreePool(sb); return STATUS_WRONG_VOLUME; } if (!check_superblock_checksum(sb)) { ExFreePool(sb); return STATUS_WRONG_VOLUME; } if (RtlCompareMemory(&sb->uuid, &Vcb->superblock.uuid, sizeof(BTRFS_UUID)) != sizeof(BTRFS_UUID)) { ERR("different UUIDs\n"); ExFreePool(sb); return STATUS_WRONG_VOLUME; } ExFreePool(sb); dev->devobj->Flags &= ~DO_VERIFY_VOLUME; return STATUS_SUCCESS; } static NTSTATUS verify_volume(_In_ PDEVICE_OBJECT devobj) { device_extension* Vcb = devobj->DeviceExtension; NTSTATUS Status; LIST_ENTRY* le; uint64_t failed_devices = 0; bool locked = false, remove = false; if (!(Vcb->Vpb->Flags & VPB_MOUNTED)) return STATUS_WRONG_VOLUME; if (!ExIsResourceAcquiredExclusive(&Vcb->tree_lock)) { ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); locked = true; } if (Vcb->removing) { if (locked) ExReleaseResourceLite(&Vcb->tree_lock); return STATUS_WRONG_VOLUME; } Status = STATUS_SUCCESS; InterlockedIncrement(&Vcb->open_files); // so pnp_surprise_removal doesn't uninit the device while we're still using it le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); Status = verify_device(Vcb, dev); if (!NT_SUCCESS(Status)) { failed_devices++; if (dev->devobj && Vcb->options.allow_degraded) dev->devobj = NULL; } le = le->Flink; } InterlockedDecrement(&Vcb->open_files); if (Vcb->removing && Vcb->open_files == 0) remove = true; if (locked) ExReleaseResourceLite(&Vcb->tree_lock); if (remove) { uninit(Vcb); return Status; } if (failed_devices == 0 || (Vcb->options.allow_degraded && failed_devices < Vcb->superblock.num_devices)) { Vcb->Vpb->RealDevice->Flags &= ~DO_VERIFY_VOLUME; return STATUS_SUCCESS; } return Status; } _Dispatch_type_(IRP_MJ_FILE_SYSTEM_CONTROL) _Function_class_(DRIVER_DISPATCH) static NTSTATUS __stdcall drv_file_system_control(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { PIO_STACK_LOCATION IrpSp; NTSTATUS Status; device_extension* Vcb = DeviceObject->DeviceExtension; bool top_level; FsRtlEnterFileSystem(); TRACE("file system control\n"); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = STATUS_INVALID_DEVICE_REQUEST; goto end; } else if (!Vcb || (Vcb->type != VCB_TYPE_FS && Vcb->type != VCB_TYPE_CONTROL)) { Status = STATUS_INVALID_PARAMETER; goto end; } Status = STATUS_NOT_IMPLEMENTED; IrpSp = IoGetCurrentIrpStackLocation( Irp ); Irp->IoStatus.Information = 0; switch (IrpSp->MinorFunction) { case IRP_MN_MOUNT_VOLUME: TRACE("IRP_MN_MOUNT_VOLUME\n"); Status = mount_vol(DeviceObject, Irp); break; case IRP_MN_KERNEL_CALL: TRACE("IRP_MN_KERNEL_CALL\n"); Status = fsctl_request(DeviceObject, &Irp, IrpSp->Parameters.FileSystemControl.FsControlCode); break; case IRP_MN_USER_FS_REQUEST: TRACE("IRP_MN_USER_FS_REQUEST\n"); Status = fsctl_request(DeviceObject, &Irp, IrpSp->Parameters.FileSystemControl.FsControlCode); break; case IRP_MN_VERIFY_VOLUME: TRACE("IRP_MN_VERIFY_VOLUME\n"); Status = verify_volume(DeviceObject); if (!NT_SUCCESS(Status) && Vcb->Vpb->Flags & VPB_MOUNTED) { ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); Vcb->removing = true; ExReleaseResourceLite(&Vcb->tree_lock); } break; default: break; } end: TRACE("returning %08lx\n", Status); if (Irp) { Irp->IoStatus.Status = Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); } if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } _Dispatch_type_(IRP_MJ_LOCK_CONTROL) _Function_class_(DRIVER_DISPATCH) static NTSTATUS __stdcall drv_lock_control(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { NTSTATUS Status; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); fcb* fcb = IrpSp->FileObject ? IrpSp->FileObject->FsContext : NULL; device_extension* Vcb = DeviceObject->DeviceExtension; bool top_level; FsRtlEnterFileSystem(); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = STATUS_INVALID_DEVICE_REQUEST; Irp->IoStatus.Status = Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); goto exit; } TRACE("lock control\n"); if (!fcb) { ERR("fcb was NULL\n"); Status = STATUS_INVALID_PARAMETER; goto exit; } FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL); Status = FsRtlProcessFileLock(&fcb->lock, Irp, NULL); fcb->Header.IsFastIoPossible = fast_io_possible(fcb); exit: TRACE("returning %08lx\n", Status); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } void do_shutdown(PIRP Irp) { LIST_ENTRY* le; bus_device_extension* bde; shutting_down = true; KeSetEvent(&mountmgr_thread_event, 0, false); le = VcbList.Flink; while (le != &VcbList) { LIST_ENTRY* le2 = le->Flink; device_extension* Vcb = CONTAINING_RECORD(le, device_extension, list_entry); volume_device_extension* vde = Vcb->vde; PDEVICE_OBJECT devobj = vde ? vde->device : NULL; TRACE("shutting down Vcb %p\n", Vcb); if (vde) InterlockedIncrement(&vde->open_count); if (devobj) ObReferenceObject(devobj); dismount_volume(Vcb, true, Irp); if (vde) { NTSTATUS Status; UNICODE_STRING mmdevpath; PDEVICE_OBJECT mountmgr; PFILE_OBJECT mountmgrfo; KIRQL irql; PVPB newvpb; RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME); Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &mountmgrfo, &mountmgr); if (!NT_SUCCESS(Status)) ERR("IoGetDeviceObjectPointer returned %08lx\n", Status); else { remove_drive_letter(mountmgr, &vde->name); ObDereferenceObject(mountmgrfo); } vde->removing = true; newvpb = ExAllocatePoolWithTag(NonPagedPool, sizeof(VPB), ALLOC_TAG); if (!newvpb) { ERR("out of memory\n"); return; } RtlZeroMemory(newvpb, sizeof(VPB)); newvpb->Type = IO_TYPE_VPB; newvpb->Size = sizeof(VPB); newvpb->RealDevice = newvpb->DeviceObject = vde->device; newvpb->Flags = VPB_DIRECT_WRITES_ALLOWED; IoAcquireVpbSpinLock(&irql); vde->device->Vpb = newvpb; IoReleaseVpbSpinLock(irql); if (InterlockedDecrement(&vde->open_count) == 0) free_vol(vde); } if (devobj) ObDereferenceObject(devobj); le = le2; } #ifdef _DEBUG if (comfo) { ObDereferenceObject(comfo); comdo = NULL; comfo = NULL; } #endif IoUnregisterFileSystem(master_devobj); if (notification_entry2) { if (fIoUnregisterPlugPlayNotificationEx) fIoUnregisterPlugPlayNotificationEx(notification_entry2); else IoUnregisterPlugPlayNotification(notification_entry2); notification_entry2 = NULL; } if (notification_entry3) { if (fIoUnregisterPlugPlayNotificationEx) fIoUnregisterPlugPlayNotificationEx(notification_entry3); else IoUnregisterPlugPlayNotification(notification_entry3); notification_entry3 = NULL; } if (notification_entry) { if (fIoUnregisterPlugPlayNotificationEx) fIoUnregisterPlugPlayNotificationEx(notification_entry); else IoUnregisterPlugPlayNotification(notification_entry); notification_entry = NULL; } bde = busobj->DeviceExtension; if (bde->attached_device) IoDetachDevice(bde->attached_device); IoDeleteDevice(busobj); IoDeleteDevice(master_devobj); } _Dispatch_type_(IRP_MJ_SHUTDOWN) _Function_class_(DRIVER_DISPATCH) static NTSTATUS __stdcall drv_shutdown(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { NTSTATUS Status; bool top_level; device_extension* Vcb = DeviceObject->DeviceExtension; FsRtlEnterFileSystem(); TRACE("shutdown\n"); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = STATUS_INVALID_DEVICE_REQUEST; goto end; } Status = STATUS_SUCCESS; do_shutdown(Irp); end: Irp->IoStatus.Status = Status; Irp->IoStatus.Information = 0; IoCompleteRequest( Irp, IO_NO_INCREMENT ); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } static bool device_still_valid(device* dev, uint64_t expected_generation) { NTSTATUS Status; unsigned int to_read; superblock* sb; to_read = (unsigned int)(dev->devobj->SectorSize == 0 ? sizeof(superblock) : sector_align(sizeof(superblock), dev->devobj->SectorSize)); sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG); if (!sb) { ERR("out of memory\n"); return false; } Status = sync_read_phys(dev->devobj, dev->fileobj, superblock_addrs[0], to_read, (PUCHAR)sb, false); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); ExFreePool(sb); return false; } if (sb->magic != BTRFS_MAGIC) { ERR("magic not found\n"); ExFreePool(sb); return false; } if (!check_superblock_checksum(sb)) { ExFreePool(sb); return false; } if (sb->generation > expected_generation) { ERR("generation was %I64x, expected %I64x\n", sb->generation, expected_generation); ExFreePool(sb); return false; } ExFreePool(sb); return true; } _Function_class_(IO_WORKITEM_ROUTINE) static void __stdcall check_after_wakeup(PDEVICE_OBJECT DeviceObject, PVOID con) { device_extension* Vcb = (device_extension*)con; LIST_ENTRY* le; UNUSED(DeviceObject); ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); le = Vcb->devices.Flink; // FIXME - do reads in parallel? while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (dev->devobj) { if (!device_still_valid(dev, Vcb->superblock.generation - 1)) { PDEVICE_OBJECT voldev = Vcb->Vpb->RealDevice; KIRQL irql; PVPB newvpb; WARN("forcing remount\n"); newvpb = ExAllocatePoolWithTag(NonPagedPool, sizeof(VPB), ALLOC_TAG); if (!newvpb) { ERR("out of memory\n"); return; } RtlZeroMemory(newvpb, sizeof(VPB)); newvpb->Type = IO_TYPE_VPB; newvpb->Size = sizeof(VPB); newvpb->RealDevice = voldev; newvpb->Flags = VPB_DIRECT_WRITES_ALLOWED; Vcb->removing = true; IoAcquireVpbSpinLock(&irql); voldev->Vpb = newvpb; IoReleaseVpbSpinLock(irql); Vcb->vde = NULL; ExReleaseResourceLite(&Vcb->tree_lock); if (Vcb->open_files == 0) uninit(Vcb); else { // remove from VcbList ExAcquireResourceExclusiveLite(&global_loading_lock, true); RemoveEntryList(&Vcb->list_entry); Vcb->list_entry.Flink = NULL; ExReleaseResourceLite(&global_loading_lock); } return; } } le = le->Flink; } ExReleaseResourceLite(&Vcb->tree_lock); } _Dispatch_type_(IRP_MJ_POWER) _Function_class_(DRIVER_DISPATCH) static NTSTATUS __stdcall drv_power(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { NTSTATUS Status; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); device_extension* Vcb = DeviceObject->DeviceExtension; bool top_level; // no need for FsRtlEnterFileSystem, as this only ever gets called in a system thread top_level = is_top_level(Irp); Irp->IoStatus.Information = 0; if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { volume_device_extension* vde = DeviceObject->DeviceExtension; if (IrpSp->MinorFunction == IRP_MN_QUERY_POWER && IrpSp->Parameters.Power.Type == SystemPowerState && IrpSp->Parameters.Power.State.SystemState != PowerSystemWorking && vde->mounted_device) { device_extension* Vcb2 = vde->mounted_device->DeviceExtension; /* If power state is about to go to sleep or hibernate, do a flush. We do this on IRP_MJ_QUERY_POWER * rather than IRP_MJ_SET_POWER because we know that the hard disks are still awake. */ if (Vcb2) { ExAcquireResourceExclusiveLite(&Vcb2->tree_lock, true); if (Vcb2->need_write && !Vcb2->readonly) { TRACE("doing protective flush on power state change\n"); Status = do_write(Vcb2, NULL); } else Status = STATUS_SUCCESS; free_trees(Vcb2); if (!NT_SUCCESS(Status)) ERR("do_write returned %08lx\n", Status); ExReleaseResourceLite(&Vcb2->tree_lock); } } else if (IrpSp->MinorFunction == IRP_MN_SET_POWER && IrpSp->Parameters.Power.Type == SystemPowerState && IrpSp->Parameters.Power.State.SystemState == PowerSystemWorking && vde->mounted_device) { device_extension* Vcb2 = vde->mounted_device->DeviceExtension; /* If waking up, make sure that the FS hasn't been changed while we've been out (e.g., by dual-boot Linux) */ if (Vcb2) { PIO_WORKITEM work_item; work_item = IoAllocateWorkItem(DeviceObject); if (!work_item) { ERR("out of memory\n"); } else IoQueueWorkItem(work_item, check_after_wakeup, DelayedWorkQueue, Vcb2); } } PoStartNextPowerIrp(Irp); IoSkipCurrentIrpStackLocation(Irp); Status = PoCallDriver(vde->attached_device, Irp); goto exit; } else if (Vcb && Vcb->type == VCB_TYPE_FS) { IoSkipCurrentIrpStackLocation(Irp); Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp); goto exit; } else if (Vcb && Vcb->type == VCB_TYPE_BUS) { bus_device_extension* bde = DeviceObject->DeviceExtension; PoStartNextPowerIrp(Irp); IoSkipCurrentIrpStackLocation(Irp); Status = PoCallDriver(bde->attached_device, Irp); goto exit; } if (IrpSp->MinorFunction == IRP_MN_SET_POWER || IrpSp->MinorFunction == IRP_MN_QUERY_POWER) Irp->IoStatus.Status = STATUS_SUCCESS; Status = Irp->IoStatus.Status; PoStartNextPowerIrp(Irp); IoCompleteRequest(Irp, IO_NO_INCREMENT); exit: if (top_level) IoSetTopLevelIrp(NULL); return Status; } _Dispatch_type_(IRP_MJ_SYSTEM_CONTROL) _Function_class_(DRIVER_DISPATCH) static NTSTATUS __stdcall drv_system_control(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { NTSTATUS Status; device_extension* Vcb = DeviceObject->DeviceExtension; bool top_level; FsRtlEnterFileSystem(); top_level = is_top_level(Irp); Irp->IoStatus.Information = 0; if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { volume_device_extension* vde = DeviceObject->DeviceExtension; IoSkipCurrentIrpStackLocation(Irp); Status = IoCallDriver(vde->attached_device, Irp); goto exit; } else if (Vcb && Vcb->type == VCB_TYPE_FS) { IoSkipCurrentIrpStackLocation(Irp); Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp); goto exit; } else if (Vcb && Vcb->type == VCB_TYPE_BUS) { bus_device_extension* bde = DeviceObject->DeviceExtension; IoSkipCurrentIrpStackLocation(Irp); Status = IoCallDriver(bde->attached_device, Irp); goto exit; } Status = Irp->IoStatus.Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); exit: if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } NTSTATUS check_file_name_valid(_In_ PUNICODE_STRING us, _In_ bool posix, _In_ bool stream) { ULONG i; if (us->Length < sizeof(WCHAR)) return STATUS_OBJECT_NAME_INVALID; if (us->Length > 255 * sizeof(WCHAR)) return STATUS_OBJECT_NAME_INVALID; for (i = 0; i < us->Length / sizeof(WCHAR); i++) { if (us->Buffer[i] == '/' || us->Buffer[i] == 0 || (!posix && (us->Buffer[i] == '/' || us->Buffer[i] == ':')) || (!posix && !stream && (us->Buffer[i] == '<' || us->Buffer[i] == '>' || us->Buffer[i] == '"' || us->Buffer[i] == '|' || us->Buffer[i] == '?' || us->Buffer[i] == '*' || (us->Buffer[i] >= 1 && us->Buffer[i] <= 31)))) return STATUS_OBJECT_NAME_INVALID; /* Don't allow unpaired surrogates ("WTF-16") */ if ((us->Buffer[i] & 0xfc00) == 0xdc00 && (i == 0 || ((us->Buffer[i-1] & 0xfc00) != 0xd800))) return STATUS_OBJECT_NAME_INVALID; if ((us->Buffer[i] & 0xfc00) == 0xd800 && (i == (us->Length / sizeof(WCHAR)) - 1 || ((us->Buffer[i+1] & 0xfc00) != 0xdc00))) return STATUS_OBJECT_NAME_INVALID; } if (us->Buffer[0] == '.' && (us->Length == sizeof(WCHAR) || (us->Length == 2 * sizeof(WCHAR) && us->Buffer[1] == '.'))) return STATUS_OBJECT_NAME_INVALID; /* The Linux driver expects filenames with a maximum length of 255 bytes - make sure * that our UTF-8 length won't be longer than that. */ if (us->Length >= 85 * sizeof(WCHAR)) { NTSTATUS Status; ULONG utf8len; Status = utf16_to_utf8(NULL, 0, &utf8len, us->Buffer, us->Length); if (!NT_SUCCESS(Status)) return Status; if (utf8len > 255) return STATUS_OBJECT_NAME_INVALID; else if (stream && utf8len > 250) // minus five bytes for "user." return STATUS_OBJECT_NAME_INVALID; } return STATUS_SUCCESS; } void chunk_lock_range(_In_ device_extension* Vcb, _In_ chunk* c, _In_ uint64_t start, _In_ uint64_t length) { LIST_ENTRY* le; bool locked; range_lock* rl; rl = ExAllocateFromNPagedLookasideList(&Vcb->range_lock_lookaside); if (!rl) { ERR("out of memory\n"); return; } rl->start = start; rl->length = length; rl->thread = PsGetCurrentThread(); while (true) { locked = false; ExAcquireResourceExclusiveLite(&c->range_locks_lock, true); le = c->range_locks.Flink; while (le != &c->range_locks) { range_lock* rl2 = CONTAINING_RECORD(le, range_lock, list_entry); if (rl2->start < start + length && rl2->start + rl2->length > start && rl2->thread != PsGetCurrentThread()) { locked = true; break; } le = le->Flink; } if (!locked) { InsertTailList(&c->range_locks, &rl->list_entry); ExReleaseResourceLite(&c->range_locks_lock); return; } KeClearEvent(&c->range_locks_event); ExReleaseResourceLite(&c->range_locks_lock); KeWaitForSingleObject(&c->range_locks_event, UserRequest, KernelMode, false, NULL); } } void chunk_unlock_range(_In_ device_extension* Vcb, _In_ chunk* c, _In_ uint64_t start, _In_ uint64_t length) { LIST_ENTRY* le; ExAcquireResourceExclusiveLite(&c->range_locks_lock, true); le = c->range_locks.Flink; while (le != &c->range_locks) { range_lock* rl = CONTAINING_RECORD(le, range_lock, list_entry); if (rl->start == start && rl->length == length) { RemoveEntryList(&rl->list_entry); ExFreeToNPagedLookasideList(&Vcb->range_lock_lookaside, rl); break; } le = le->Flink; } KeSetEvent(&c->range_locks_event, 0, false); ExReleaseResourceLite(&c->range_locks_lock); } void log_device_error(_In_ device_extension* Vcb, _Inout_ device* dev, _In_ int error) { dev->stats[error]++; dev->stats_changed = true; Vcb->stats_changed = true; } #ifdef _DEBUG _Function_class_(KSTART_ROUTINE) static void __stdcall serial_thread(void* context) { LARGE_INTEGER due_time; KTIMER timer; UNUSED(context); KeInitializeTimer(&timer); due_time.QuadPart = (uint64_t)-10000000; KeSetTimer(&timer, due_time, NULL); while (true) { KeWaitForSingleObject(&timer, Executive, KernelMode, false, NULL); init_serial(false); if (comdo) break; KeSetTimer(&timer, due_time, NULL); } KeCancelTimer(&timer); PsTerminateSystemThread(STATUS_SUCCESS); serial_thread_handle = NULL; } static void init_serial(bool first_time) { NTSTATUS Status; Status = IoGetDeviceObjectPointer(&log_device, FILE_WRITE_DATA, &comfo, &comdo); if (!NT_SUCCESS(Status)) { ERR("IoGetDeviceObjectPointer returned %08lx\n", Status); if (first_time) { OBJECT_ATTRIBUTES oa; InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); Status = PsCreateSystemThread(&serial_thread_handle, 0, &oa, NULL, NULL, serial_thread, NULL); if (!NT_SUCCESS(Status)) { ERR("PsCreateSystemThread returned %08lx\n", Status); return; } } } } #endif #if defined(_X86_) || defined(_AMD64_) static void check_cpu() { bool have_sse2 = false, have_sse42 = false, have_avx2 = false; int cpu_info[4]; __cpuid(cpu_info, 1); have_sse42 = cpu_info[2] & (1 << 20); have_sse2 = cpu_info[3] & (1 << 26); __cpuidex(cpu_info, 7, 0); have_avx2 = cpu_info[1] & (1 << 5); if (have_avx2) { // check Windows has enabled AVX2 - Windows 10 doesn't immediately if (__readcr4() & (1 << 18)) { uint32_t xcr0; #ifdef _MSC_VER xcr0 = (uint32_t)_xgetbv(0); #else __asm__("xgetbv" : "=a" (xcr0) : "c" (0) : "edx"); #endif if ((xcr0 & 6) != 6) have_avx2 = false; } else have_avx2 = false; } if (have_sse42) { TRACE("SSE4.2 is supported\n"); calc_crc32c = calc_crc32c_hw; } else TRACE("SSE4.2 not supported\n"); if (have_sse2) { TRACE("SSE2 is supported\n"); if (!have_avx2) do_xor = do_xor_sse2; } else TRACE("SSE2 is not supported\n"); if (have_avx2) { TRACE("AVX2 is supported\n"); do_xor = do_xor_avx2; } else TRACE("AVX2 is not supported\n"); } #elif defined(_ARM64_) static void check_cpu() { uint64_t reg = _ReadStatusReg(ARM64_ID_AA64ISAR0_EL1); if ((reg & 0xf0000) >> 16 == 1) calc_crc32c = calc_crc32c_hw; } #endif #ifdef _DEBUG static void init_logging() { ExAcquireResourceExclusiveLite(&log_lock, true); if (log_device.Length > 0) init_serial(true); else if (log_file.Length > 0) { NTSTATUS Status; OBJECT_ATTRIBUTES oa; IO_STATUS_BLOCK iosb; char* dateline; LARGE_INTEGER time; TIME_FIELDS tf; InitializeObjectAttributes(&oa, &log_file, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateFile(&log_handle, FILE_WRITE_DATA, &oa, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_WRITE_THROUGH | FILE_SYNCHRONOUS_IO_ALERT, NULL, 0); if (!NT_SUCCESS(Status)) { ERR("ZwCreateFile returned %08lx\n", Status); goto end; } if (iosb.Information == FILE_OPENED) { // already exists FILE_STANDARD_INFORMATION fsi; FILE_POSITION_INFORMATION fpi; static const char delim[] = "\n---\n"; // move to end of file Status = ZwQueryInformationFile(log_handle, &iosb, &fsi, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation); if (!NT_SUCCESS(Status)) { ERR("ZwQueryInformationFile returned %08lx\n", Status); goto end; } fpi.CurrentByteOffset = fsi.EndOfFile; Status = ZwSetInformationFile(log_handle, &iosb, &fpi, sizeof(FILE_POSITION_INFORMATION), FilePositionInformation); if (!NT_SUCCESS(Status)) { ERR("ZwSetInformationFile returned %08lx\n", Status); goto end; } Status = ZwWriteFile(log_handle, NULL, NULL, NULL, &iosb, (void*)delim, sizeof(delim) - 1, NULL, NULL); if (!NT_SUCCESS(Status)) { ERR("ZwWriteFile returned %08lx\n", Status); goto end; } } dateline = ExAllocatePoolWithTag(PagedPool, 256, ALLOC_TAG); if (!dateline) { ERR("out of memory\n"); goto end; } KeQuerySystemTime(&time); RtlTimeToTimeFields(&time, &tf); sprintf(dateline, "Starting logging at %04i-%02i-%02i %02i:%02i:%02i\n", tf.Year, tf.Month, tf.Day, tf.Hour, tf.Minute, tf.Second); Status = ZwWriteFile(log_handle, NULL, NULL, NULL, &iosb, dateline, (ULONG)strlen(dateline), NULL, NULL); ExFreePool(dateline); if (!NT_SUCCESS(Status)) { ERR("ZwWriteFile returned %08lx\n", Status); goto end; } } end: ExReleaseResourceLite(&log_lock); } #endif _Function_class_(KSTART_ROUTINE) static void __stdcall degraded_wait_thread(_In_ void* context) { KTIMER timer; LARGE_INTEGER delay; UNUSED(context); KeInitializeTimer(&timer); delay.QuadPart = -30000000; // wait three seconds KeSetTimer(&timer, delay, NULL); KeWaitForSingleObject(&timer, Executive, KernelMode, false, NULL); TRACE("timer expired\n"); degraded_wait = false; ZwClose(degraded_wait_handle); degraded_wait_handle = NULL; PsTerminateSystemThread(STATUS_SUCCESS); } _Function_class_(DRIVER_ADD_DEVICE) NTSTATUS __stdcall AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT PhysicalDeviceObject) { LIST_ENTRY* le; NTSTATUS Status; UNICODE_STRING volname; ULONG i; WCHAR* s; pdo_device_extension* pdode = NULL; PDEVICE_OBJECT voldev; volume_device_extension* vde; UNICODE_STRING arc_name_us; WCHAR* anp; static const WCHAR arc_name_prefix[] = L"\\ArcName\\btrfs("; WCHAR arc_name[(sizeof(arc_name_prefix) / sizeof(WCHAR)) - 1 + 37]; TRACE("(%p, %p)\n", DriverObject, PhysicalDeviceObject); UNUSED(DriverObject); ExAcquireResourceSharedLite(&pdo_list_lock, true); le = pdo_list.Flink; while (le != &pdo_list) { pdo_device_extension* pdode2 = CONTAINING_RECORD(le, pdo_device_extension, list_entry); if (pdode2->pdo == PhysicalDeviceObject) { pdode = pdode2; break; } le = le->Flink; } if (!pdode) { WARN("unrecognized PDO %p\n", PhysicalDeviceObject); Status = STATUS_NOT_SUPPORTED; goto end; } ExAcquireResourceExclusiveLite(&pdode->child_lock, true); if (pdode->vde) { // if already done, return success Status = STATUS_SUCCESS; goto end2; } volname.Length = volname.MaximumLength = (sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)) + ((36 + 1) * sizeof(WCHAR)); volname.Buffer = ExAllocatePoolWithTag(PagedPool, volname.MaximumLength, ALLOC_TAG); // FIXME - when do we free this? if (!volname.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end2; } RtlCopyMemory(volname.Buffer, BTRFS_VOLUME_PREFIX, sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)); RtlCopyMemory(arc_name, arc_name_prefix, sizeof(arc_name_prefix) - sizeof(WCHAR)); anp = &arc_name[(sizeof(arc_name_prefix) / sizeof(WCHAR)) - 1]; s = &volname.Buffer[(sizeof(BTRFS_VOLUME_PREFIX) / sizeof(WCHAR)) - 1]; for (i = 0; i < 16; i++) { *s = *anp = hex_digit(pdode->uuid.uuid[i] >> 4); s++; anp++; *s = *anp = hex_digit(pdode->uuid.uuid[i] & 0xf); s++; anp++; if (i == 3 || i == 5 || i == 7 || i == 9) { *s = *anp = '-'; s++; anp++; } } *s = '}'; *anp = ')'; Status = IoCreateDevice(drvobj, sizeof(volume_device_extension), &volname, FILE_DEVICE_DISK, is_windows_8 ? FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL : 0, false, &voldev); if (!NT_SUCCESS(Status)) { ERR("IoCreateDevice returned %08lx\n", Status); goto end2; } arc_name_us.Buffer = arc_name; arc_name_us.Length = arc_name_us.MaximumLength = sizeof(arc_name); Status = IoCreateSymbolicLink(&arc_name_us, &volname); if (!NT_SUCCESS(Status)) WARN("IoCreateSymbolicLink returned %08lx\n", Status); voldev->SectorSize = PhysicalDeviceObject->SectorSize; voldev->Flags |= DO_DIRECT_IO; vde = voldev->DeviceExtension; vde->type = VCB_TYPE_VOLUME; vde->name = volname; vde->device = voldev; vde->mounted_device = NULL; vde->pdo = PhysicalDeviceObject; vde->pdode = pdode; vde->removing = false; vde->dead = false; vde->open_count = 0; Status = IoRegisterDeviceInterface(PhysicalDeviceObject, &GUID_DEVINTERFACE_VOLUME, NULL, &vde->bus_name); if (!NT_SUCCESS(Status)) WARN("IoRegisterDeviceInterface returned %08lx\n", Status); vde->attached_device = IoAttachDeviceToDeviceStack(voldev, PhysicalDeviceObject); pdode->vde = vde; if (pdode->removable) voldev->Characteristics |= FILE_REMOVABLE_MEDIA; if (RtlCompareMemory(&boot_uuid, &pdode->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { voldev->Flags |= DO_SYSTEM_BOOT_PARTITION; PhysicalDeviceObject->Flags |= DO_SYSTEM_BOOT_PARTITION; } voldev->Flags &= ~DO_DEVICE_INITIALIZING; Status = IoSetDeviceInterfaceState(&vde->bus_name, true); if (!NT_SUCCESS(Status)) WARN("IoSetDeviceInterfaceState returned %08lx\n", Status); Status = STATUS_SUCCESS; end2: ExReleaseResourceLite(&pdode->child_lock); end: ExReleaseResourceLite(&pdo_list_lock); return Status; } _Function_class_(DRIVER_INITIALIZE) NTSTATUS __stdcall DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) { NTSTATUS Status; PDEVICE_OBJECT DeviceObject; UNICODE_STRING device_nameW; UNICODE_STRING dosdevice_nameW; control_device_extension* cde; bus_device_extension* bde; HANDLE regh; OBJECT_ATTRIBUTES oa, system_thread_attributes; ULONG dispos; RTL_OSVERSIONINFOW ver; ver.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOW); Status = RtlGetVersion(&ver); if (!NT_SUCCESS(Status)) { ERR("RtlGetVersion returned %08lx\n", Status); return Status; } is_windows_8 = ver.dwMajorVersion > 6 || (ver.dwMajorVersion == 6 && ver.dwMinorVersion >= 2); KeInitializeSpinLock(&fve_data_lock); InitializeListHead(&uid_map_list); InitializeListHead(&gid_map_list); #ifdef _DEBUG ExInitializeResourceLite(&log_lock); #endif ExInitializeResourceLite(&mapping_lock); log_device.Buffer = NULL; log_device.Length = log_device.MaximumLength = 0; log_file.Buffer = NULL; log_file.Length = log_file.MaximumLength = 0; registry_path.Length = registry_path.MaximumLength = RegistryPath->Length; registry_path.Buffer = ExAllocatePoolWithTag(PagedPool, registry_path.Length, ALLOC_TAG); if (!registry_path.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(registry_path.Buffer, RegistryPath->Buffer, registry_path.Length); read_registry(®istry_path, false); #ifdef _DEBUG if (debug_log_level > 0) init_logging(); log_started = true; #endif TRACE("DriverEntry\n"); #if defined(_X86_) || defined(_AMD64_) || defined(_ARM64_) check_cpu(); #endif if (ver.dwMajorVersion > 6 || (ver.dwMajorVersion == 6 && ver.dwMinorVersion >= 2)) { // Windows 8 or above UNICODE_STRING name; tPsIsDiskCountersEnabled fPsIsDiskCountersEnabled; RtlInitUnicodeString(&name, L"PsIsDiskCountersEnabled"); fPsIsDiskCountersEnabled = (tPsIsDiskCountersEnabled)MmGetSystemRoutineAddress(&name); if (fPsIsDiskCountersEnabled) { diskacc = fPsIsDiskCountersEnabled(); RtlInitUnicodeString(&name, L"PsUpdateDiskCounters"); fPsUpdateDiskCounters = (tPsUpdateDiskCounters)MmGetSystemRoutineAddress(&name); if (!fPsUpdateDiskCounters) diskacc = false; RtlInitUnicodeString(&name, L"FsRtlUpdateDiskCounters"); fFsRtlUpdateDiskCounters = (tFsRtlUpdateDiskCounters)MmGetSystemRoutineAddress(&name); } RtlInitUnicodeString(&name, L"CcCopyReadEx"); fCcCopyReadEx = (tCcCopyReadEx)MmGetSystemRoutineAddress(&name); RtlInitUnicodeString(&name, L"CcCopyWriteEx"); fCcCopyWriteEx = (tCcCopyWriteEx)MmGetSystemRoutineAddress(&name); RtlInitUnicodeString(&name, L"CcSetAdditionalCacheAttributesEx"); fCcSetAdditionalCacheAttributesEx = (tCcSetAdditionalCacheAttributesEx)MmGetSystemRoutineAddress(&name); RtlInitUnicodeString(&name, L"FsRtlCheckLockForOplockRequest"); fFsRtlCheckLockForOplockRequest = (tFsRtlCheckLockForOplockRequest)MmGetSystemRoutineAddress(&name); } else { fPsUpdateDiskCounters = NULL; fCcCopyReadEx = NULL; fCcCopyWriteEx = NULL; fCcSetAdditionalCacheAttributesEx = NULL; fFsRtlUpdateDiskCounters = NULL; fFsRtlCheckLockForOplockRequest = NULL; } if (ver.dwMajorVersion > 6 || (ver.dwMajorVersion == 6 && ver.dwMinorVersion >= 1)) { // Windows 7 or above UNICODE_STRING name; RtlInitUnicodeString(&name, L"IoUnregisterPlugPlayNotificationEx"); fIoUnregisterPlugPlayNotificationEx = (tIoUnregisterPlugPlayNotificationEx)MmGetSystemRoutineAddress(&name); RtlInitUnicodeString(&name, L"FsRtlAreThereCurrentOrInProgressFileLocks"); fFsRtlAreThereCurrentOrInProgressFileLocks = (tFsRtlAreThereCurrentOrInProgressFileLocks)MmGetSystemRoutineAddress(&name); } else { fIoUnregisterPlugPlayNotificationEx = NULL; fFsRtlAreThereCurrentOrInProgressFileLocks = NULL; } if (ver.dwMajorVersion >= 6) { // Windows Vista or above UNICODE_STRING name; RtlInitUnicodeString(&name, L"FsRtlGetEcpListFromIrp"); fFsRtlGetEcpListFromIrp = (tFsRtlGetEcpListFromIrp)MmGetSystemRoutineAddress(&name); RtlInitUnicodeString(&name, L"FsRtlGetNextExtraCreateParameter"); fFsRtlGetNextExtraCreateParameter = (tFsRtlGetNextExtraCreateParameter)MmGetSystemRoutineAddress(&name); RtlInitUnicodeString(&name, L"FsRtlValidateReparsePointBuffer"); fFsRtlValidateReparsePointBuffer = (tFsRtlValidateReparsePointBuffer)MmGetSystemRoutineAddress(&name); } else { fFsRtlGetEcpListFromIrp = NULL; fFsRtlGetNextExtraCreateParameter = NULL; fFsRtlValidateReparsePointBuffer = compat_FsRtlValidateReparsePointBuffer; } drvobj = DriverObject; DriverObject->DriverUnload = DriverUnload; DriverObject->DriverExtension->AddDevice = AddDevice; DriverObject->MajorFunction[IRP_MJ_CREATE] = drv_create; DriverObject->MajorFunction[IRP_MJ_CLOSE] = drv_close; DriverObject->MajorFunction[IRP_MJ_READ] = drv_read; DriverObject->MajorFunction[IRP_MJ_WRITE] = drv_write; DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] = drv_query_information; DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = drv_set_information; DriverObject->MajorFunction[IRP_MJ_QUERY_EA] = drv_query_ea; DriverObject->MajorFunction[IRP_MJ_SET_EA] = drv_set_ea; DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS] = drv_flush_buffers; DriverObject->MajorFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = drv_query_volume_information; DriverObject->MajorFunction[IRP_MJ_SET_VOLUME_INFORMATION] = drv_set_volume_information; DriverObject->MajorFunction[IRP_MJ_DIRECTORY_CONTROL] = drv_directory_control; DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = drv_file_system_control; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = drv_device_control; DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = drv_shutdown; DriverObject->MajorFunction[IRP_MJ_LOCK_CONTROL] = drv_lock_control; DriverObject->MajorFunction[IRP_MJ_CLEANUP] = drv_cleanup; DriverObject->MajorFunction[IRP_MJ_QUERY_SECURITY] = drv_query_security; DriverObject->MajorFunction[IRP_MJ_SET_SECURITY] = drv_set_security; DriverObject->MajorFunction[IRP_MJ_POWER] = drv_power; DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = drv_system_control; DriverObject->MajorFunction[IRP_MJ_PNP] = drv_pnp; init_fast_io_dispatch(&DriverObject->FastIoDispatch); device_nameW.Buffer = (WCHAR*)device_name; device_nameW.Length = device_nameW.MaximumLength = sizeof(device_name) - sizeof(WCHAR); dosdevice_nameW.Buffer = (WCHAR*)dosdevice_name; dosdevice_nameW.Length = dosdevice_nameW.MaximumLength = sizeof(dosdevice_name) - sizeof(WCHAR); Status = IoCreateDevice(DriverObject, sizeof(control_device_extension), &device_nameW, FILE_DEVICE_DISK_FILE_SYSTEM, FILE_DEVICE_SECURE_OPEN, false, &DeviceObject); if (!NT_SUCCESS(Status)) { ERR("IoCreateDevice returned %08lx\n", Status); return Status; } master_devobj = DeviceObject; cde = (control_device_extension*)master_devobj->DeviceExtension; RtlZeroMemory(cde, sizeof(control_device_extension)); cde->type = VCB_TYPE_CONTROL; DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; Status = IoCreateSymbolicLink(&dosdevice_nameW, &device_nameW); if (!NT_SUCCESS(Status)) { ERR("IoCreateSymbolicLink returned %08lx\n", Status); return Status; } init_cache(); InitializeListHead(&VcbList); ExInitializeResourceLite(&global_loading_lock); ExInitializeResourceLite(&pdo_list_lock); InitializeListHead(&pdo_list); InitializeObjectAttributes(&oa, RegistryPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateKey(®h, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos); if (!NT_SUCCESS(Status)) { ERR("ZwCreateKey returned %08lx\n", Status); return Status; } watch_registry(regh); Status = IoCreateDevice(DriverObject, sizeof(bus_device_extension), NULL, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, false, &busobj); if (!NT_SUCCESS(Status)) { ERR("IoCreateDevice returned %08lx\n", Status); return Status; } bde = (bus_device_extension*)busobj->DeviceExtension; RtlZeroMemory(bde, sizeof(bus_device_extension)); bde->type = VCB_TYPE_BUS; Status = IoReportDetectedDevice(drvobj, InterfaceTypeUndefined, 0xFFFFFFFF, 0xFFFFFFFF, NULL, NULL, 0, &bde->buspdo); if (!NT_SUCCESS(Status)) { ERR("IoReportDetectedDevice returned %08lx\n", Status); return Status; } Status = IoRegisterDeviceInterface(bde->buspdo, &BtrfsBusInterface, NULL, &bde->bus_name); if (!NT_SUCCESS(Status)) WARN("IoRegisterDeviceInterface returned %08lx\n", Status); bde->attached_device = IoAttachDeviceToDeviceStack(busobj, bde->buspdo); busobj->Flags &= ~DO_DEVICE_INITIALIZING; Status = IoSetDeviceInterfaceState(&bde->bus_name, true); if (!NT_SUCCESS(Status)) WARN("IoSetDeviceInterfaceState returned %08lx\n", Status); IoInvalidateDeviceRelations(bde->buspdo, BusRelations); InitializeObjectAttributes(&system_thread_attributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); Status = PsCreateSystemThread(°raded_wait_handle, 0, &system_thread_attributes, NULL, NULL, degraded_wait_thread, NULL); if (!NT_SUCCESS(Status)) WARN("PsCreateSystemThread returned %08lx\n", Status); ExInitializeResourceLite(&boot_lock); Status = IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange, PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES, (PVOID)&GUID_DEVINTERFACE_VOLUME, DriverObject, volume_notification, NULL, ¬ification_entry2); if (!NT_SUCCESS(Status)) ERR("IoRegisterPlugPlayNotification returned %08lx\n", Status); Status = IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange, PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES, (PVOID)&GUID_DEVINTERFACE_HIDDEN_VOLUME, DriverObject, volume_notification, NULL, ¬ification_entry3); if (!NT_SUCCESS(Status)) ERR("IoRegisterPlugPlayNotification returned %08lx\n", Status); Status = IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange, PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES, (PVOID)&GUID_DEVINTERFACE_DISK, DriverObject, pnp_notification, DriverObject, ¬ification_entry); if (!NT_SUCCESS(Status)) ERR("IoRegisterPlugPlayNotification returned %08lx\n", Status); finished_probing = true; KeInitializeEvent(&mountmgr_thread_event, NotificationEvent, false); Status = PsCreateSystemThread(&mountmgr_thread_handle, 0, &system_thread_attributes, NULL, NULL, mountmgr_thread, NULL); if (!NT_SUCCESS(Status)) WARN("PsCreateSystemThread returned %08lx\n", Status); IoRegisterFileSystem(DeviceObject); check_system_root(); return STATUS_SUCCESS; } ================================================ FILE: src/btrfs.cdf ================================================ [CatalogHeader] Name=btrfs.cat CatalogVersion=1 HashAlgorithms=SHA1 PageHashes=true EncodingType=0x00010001 CATATTR1=0x10010001:HWID1:btrfsvolume CATATTR2=0x10010001:HWID2:root\btrfs CATATTR3=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 [CatalogFiles] btrfs.inf=btrfs.inf btrfs.infATTR1=0x10010001:File:btrfs.inf 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 btrfs-vol.inf=btrfs-vol.inf btrfs-vol.infATTR1=0x10010001:File:btrfs-vol.inf 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 aarch64_btrfs.sys=aarch64\btrfs.sys aarch64_btrfs.sysATTR1=0x10010001:File:btrfs.sys 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 aarch64_mkbtrfs.exe=aarch64\mkbtrfs.exe aarch64_mkbtrfs.exeATTR1=0x10010001:File:mkbtrfs.exe aarch64_mkbtrfs.exeATTR2=0x10010001:OSAttr:2:10.0 aarch64_shellbtrfs.dll=aarch64\shellbtrfs.dll aarch64_shellbtrfs.dllATTR1=0x10010001:File:shellbtrfs.dll aarch64_shellbtrfs.dllATTR2=0x10010001:OSAttr:2:10.0 aarch64_ubtrfs.dll=aarch64\ubtrfs.dll aarch64_ubtrfs.dllATTR1=0x10010001:File:ubtrfs.dll aarch64_ubtrfs.dllATTR2=0x10010001:OSAttr:2:10.0 amd64_btrfs.sys=amd64\btrfs.sys amd64_btrfs.sysATTR1=0x10010001:File:btrfs.sys 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 amd64_mkbtrfs.exe=amd64\mkbtrfs.exe amd64_mkbtrfs.exeATTR1=0x10010001:File:mkbtrfs.exe 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 amd64_shellbtrfs.dll=amd64\shellbtrfs.dll amd64_shellbtrfs.dllATTR1=0x10010001:File:shellbtrfs.dll 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 amd64_ubtrfs.dll=amd64\ubtrfs.dll amd64_ubtrfs.dllATTR1=0x10010001:File:ubtrfs.dll 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 arm_btrfs.sys=arm\btrfs.sys arm_btrfs.sysATTR1=0x10010001:File:btrfs.sys 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 arm_mkbtrfs.exe=arm\mkbtrfs.exe arm_mkbtrfs.exeATTR1=0x10010001:File:mkbtrfs.exe arm_mkbtrfs.exeATTR2=0x10010001:OSAttr:2:6.2,2:6.3 arm_shellbtrfs.dll=arm\shellbtrfs.dll arm_shellbtrfs.dllATTR1=0x10010001:File:shellbtrfs.dll arm_shellbtrfs.dllATTR2=0x10010001:OSAttr:2:6.2,2:6.3 arm_ubtrfs.dll=arm\ubtrfs.dll arm_ubtrfs.dllATTR1=0x10010001:File:ubtrfs.dll arm_ubtrfs.dllATTR2=0x10010001:OSAttr:2:6.2,2:6.3 x86_btrfs.sys=x86\btrfs.sys x86_btrfs.sysATTR1=0x10010001:File:btrfs.sys 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 x86_mkbtrfs.exe=x86\mkbtrfs.exe x86_mkbtrfs.exeATTR1=0x10010001:File:mkbtrfs.exe x86_mkbtrfs.exeATTR2=0x10010001:OSAttr:2:5.1,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0 x86_shellbtrfs.dll=x86\shellbtrfs.dll x86_shellbtrfs.dllATTR1=0x10010001:File:shellbtrfs.dll x86_shellbtrfs.dllATTR2=0x10010001:OSAttr:2:5.1,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0 x86_ubtrfs.dll=x86\ubtrfs.dll x86_ubtrfs.dllATTR1=0x10010001:File:ubtrfs.dll x86_ubtrfs.dllATTR2=0x10010001:OSAttr:2:5.1,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0 ================================================ FILE: src/btrfs.h ================================================ /* btrfs.h * Generic btrfs header file. Thanks to whoever it was who wrote * https://btrfs.wiki.kernel.org/index.php/On-disk_Format - you saved me a lot of time! * * I release this file, and this file only, into the public domain - do whatever * you want with it. You don't have to, but I'd appreciate if you let me know if you * use it anything cool - mark@harmstone.com. */ #pragma once #include #include static const uint64_t superblock_addrs[] = { 0x10000, 0x4000000, 0x4000000000, 0x4000000000000, 0 }; #define BTRFS_MAGIC 0x4d5f53665248425f #define MAX_LABEL_SIZE 0x100 #define SUBVOL_ROOT_INODE 0x100 #define BTRFS_LAST_FREE_OBJECTID 0xffffffffffffff00 #define TYPE_INODE_ITEM 0x01 #define TYPE_INODE_REF 0x0C #define TYPE_INODE_EXTREF 0x0D #define TYPE_XATTR_ITEM 0x18 #define TYPE_ORPHAN_INODE 0x30 #define TYPE_DIR_ITEM 0x54 #define TYPE_DIR_INDEX 0x60 #define TYPE_EXTENT_DATA 0x6C #define TYPE_EXTENT_CSUM 0x80 #define TYPE_ROOT_ITEM 0x84 #define TYPE_ROOT_BACKREF 0x90 #define TYPE_ROOT_REF 0x9C #define TYPE_EXTENT_ITEM 0xA8 #define TYPE_METADATA_ITEM 0xA9 #define TYPE_TREE_BLOCK_REF 0xB0 #define TYPE_EXTENT_DATA_REF 0xB2 #define TYPE_EXTENT_REF_V0 0xB4 #define TYPE_SHARED_BLOCK_REF 0xB6 #define TYPE_SHARED_DATA_REF 0xB8 #define TYPE_BLOCK_GROUP_ITEM 0xC0 #define TYPE_FREE_SPACE_INFO 0xC6 #define TYPE_FREE_SPACE_EXTENT 0xC7 #define TYPE_FREE_SPACE_BITMAP 0xC8 #define TYPE_DEV_EXTENT 0xCC #define TYPE_DEV_ITEM 0xD8 #define TYPE_CHUNK_ITEM 0xE4 #define TYPE_TEMP_ITEM 0xF8 #define TYPE_DEV_STATS 0xF9 #define TYPE_SUBVOL_UUID 0xFB #define TYPE_SUBVOL_REC_UUID 0xFC #define BTRFS_ROOT_ROOT 1 #define BTRFS_ROOT_EXTENT 2 #define BTRFS_ROOT_CHUNK 3 #define BTRFS_ROOT_DEVTREE 4 #define BTRFS_ROOT_FSTREE 5 #define BTRFS_ROOT_TREEDIR 6 #define BTRFS_ROOT_CHECKSUM 7 #define BTRFS_ROOT_UUID 9 #define BTRFS_ROOT_FREE_SPACE 0xa #define BTRFS_ROOT_BLOCK_GROUP 0xb #define BTRFS_ROOT_RAID_STRIPE 0xc #define BTRFS_ROOT_DATA_RELOC 0xFFFFFFFFFFFFFFF7 #define BTRFS_COMPRESSION_NONE 0 #define BTRFS_COMPRESSION_ZLIB 1 #define BTRFS_COMPRESSION_LZO 2 #define BTRFS_COMPRESSION_ZSTD 3 #define BTRFS_ENCRYPTION_NONE 0 #define BTRFS_ENCODING_NONE 0 #define EXTENT_TYPE_INLINE 0 #define EXTENT_TYPE_REGULAR 1 #define EXTENT_TYPE_PREALLOC 2 #define BLOCK_FLAG_DATA 0x001 #define BLOCK_FLAG_SYSTEM 0x002 #define BLOCK_FLAG_METADATA 0x004 #define BLOCK_FLAG_RAID0 0x008 #define BLOCK_FLAG_RAID1 0x010 #define BLOCK_FLAG_DUPLICATE 0x020 #define BLOCK_FLAG_RAID10 0x040 #define BLOCK_FLAG_RAID5 0x080 #define BLOCK_FLAG_RAID6 0x100 #define BLOCK_FLAG_RAID1C3 0x200 #define BLOCK_FLAG_RAID1C4 0x400 #define FREE_SPACE_CACHE_ID 0xFFFFFFFFFFFFFFF5 #define EXTENT_CSUM_ID 0xFFFFFFFFFFFFFFF6 #define BALANCE_ITEM_ID 0xFFFFFFFFFFFFFFFC #define BTRFS_INODE_NODATASUM 0x001 #define BTRFS_INODE_NODATACOW 0x002 #define BTRFS_INODE_READONLY 0x004 #define BTRFS_INODE_NOCOMPRESS 0x008 #define BTRFS_INODE_PREALLOC 0x010 #define BTRFS_INODE_SYNC 0x020 #define BTRFS_INODE_IMMUTABLE 0x040 #define BTRFS_INODE_APPEND 0x080 #define BTRFS_INODE_NODUMP 0x100 #define BTRFS_INODE_NOATIME 0x200 #define BTRFS_INODE_DIRSYNC 0x400 #define BTRFS_INODE_COMPRESS 0x800 #define BTRFS_INODE_RO_VERITY 0x1 #define BTRFS_SUBVOL_READONLY 0x1 #define BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE 0x1 #define BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID 0x2 #define BTRFS_COMPAT_RO_FLAGS_VERITY 0x4 #define BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE 0x8 #define BTRFS_INCOMPAT_FLAGS_MIXED_BACKREF 0x0001 #define BTRFS_INCOMPAT_FLAGS_DEFAULT_SUBVOL 0x0002 #define BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS 0x0004 #define BTRFS_INCOMPAT_FLAGS_COMPRESS_LZO 0x0008 #define BTRFS_INCOMPAT_FLAGS_COMPRESS_ZSTD 0x0010 #define BTRFS_INCOMPAT_FLAGS_BIG_METADATA 0x0020 #define BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF 0x0040 #define BTRFS_INCOMPAT_FLAGS_RAID56 0x0080 #define BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA 0x0100 #define BTRFS_INCOMPAT_FLAGS_NO_HOLES 0x0200 #define BTRFS_INCOMPAT_FLAGS_METADATA_UUID 0x0400 #define BTRFS_INCOMPAT_FLAGS_RAID1C34 0x0800 #define BTRFS_INCOMPAT_FLAGS_ZONED 0x1000 #define BTRFS_INCOMPAT_FLAGS_EXTENT_TREE_V2 0x2000 #define BTRFS_INCOMPAT_FLAGS_RAID_STRIPE_TREE 0x4000 #define BTRFS_INCOMPAT_FLAGS_SIMPLE_QUOTA 0x10000 #define BTRFS_SUPERBLOCK_FLAGS_SEEDING 0x100000000 #define BTRFS_ORPHAN_INODE_OBJID 0xFFFFFFFFFFFFFFFB #define CSUM_TYPE_CRC32C 0 #define CSUM_TYPE_XXHASH 1 #define CSUM_TYPE_SHA256 2 #define CSUM_TYPE_BLAKE2 3 #pragma pack(push, 1) typedef struct { uint8_t uuid[16]; } BTRFS_UUID; typedef struct { uint64_t obj_id; uint8_t obj_type; uint64_t offset; } KEY; #define HEADER_FLAG_WRITTEN 0x000000000000001 #define HEADER_FLAG_SHARED_BACKREF 0x000000000000002 #define HEADER_FLAG_MIXED_BACKREF 0x100000000000000 typedef struct { uint8_t csum[32]; BTRFS_UUID fs_uuid; uint64_t address; uint64_t flags; BTRFS_UUID chunk_tree_uuid; uint64_t generation; uint64_t tree_id; uint32_t num_items; uint8_t level; } tree_header; typedef struct { KEY key; uint32_t offset; uint32_t size; } leaf_node; typedef struct { KEY key; uint64_t address; uint64_t generation; } internal_node; typedef struct { uint64_t dev_id; uint64_t num_bytes; uint64_t bytes_used; uint32_t optimal_io_align; uint32_t optimal_io_width; uint32_t minimal_io_size; uint64_t type; uint64_t generation; uint64_t start_offset; uint32_t dev_group; uint8_t seek_speed; uint8_t bandwidth; BTRFS_UUID device_uuid; BTRFS_UUID fs_uuid; } DEV_ITEM; #define SYS_CHUNK_ARRAY_SIZE 0x800 #define BTRFS_NUM_BACKUP_ROOTS 4 typedef struct { uint64_t root_tree_addr; uint64_t root_tree_generation; uint64_t chunk_tree_addr; uint64_t chunk_tree_generation; uint64_t extent_tree_addr; uint64_t extent_tree_generation; uint64_t fs_tree_addr; uint64_t fs_tree_generation; uint64_t dev_root_addr; uint64_t dev_root_generation; uint64_t csum_root_addr; uint64_t csum_root_generation; uint64_t total_bytes; uint64_t bytes_used; uint64_t num_devices; uint64_t reserved[4]; uint8_t root_level; uint8_t chunk_root_level; uint8_t extent_root_level; uint8_t fs_root_level; uint8_t dev_root_level; uint8_t csum_root_level; uint8_t reserved2[10]; } superblock_backup; typedef struct { uint8_t checksum[32]; BTRFS_UUID uuid; uint64_t sb_phys_addr; uint64_t flags; uint64_t magic; uint64_t generation; uint64_t root_tree_addr; uint64_t chunk_tree_addr; uint64_t log_tree_addr; uint64_t log_root_transid; uint64_t total_bytes; uint64_t bytes_used; uint64_t root_dir_objectid; uint64_t num_devices; uint32_t sector_size; uint32_t node_size; uint32_t leaf_size; uint32_t stripe_size; uint32_t n; uint64_t chunk_root_generation; uint64_t compat_flags; uint64_t compat_ro_flags; uint64_t incompat_flags; uint16_t csum_type; uint8_t root_level; uint8_t chunk_root_level; uint8_t log_root_level; DEV_ITEM dev_item; char label[MAX_LABEL_SIZE]; uint64_t cache_generation; uint64_t uuid_tree_generation; BTRFS_UUID metadata_uuid; uint64_t reserved[28]; uint8_t sys_chunk_array[SYS_CHUNK_ARRAY_SIZE]; superblock_backup backup[BTRFS_NUM_BACKUP_ROOTS]; uint8_t reserved2[565]; } superblock; #define BTRFS_TYPE_UNKNOWN 0 #define BTRFS_TYPE_FILE 1 #define BTRFS_TYPE_DIRECTORY 2 #define BTRFS_TYPE_CHARDEV 3 #define BTRFS_TYPE_BLOCKDEV 4 #define BTRFS_TYPE_FIFO 5 #define BTRFS_TYPE_SOCKET 6 #define BTRFS_TYPE_SYMLINK 7 #define BTRFS_TYPE_EA 8 typedef struct { KEY key; uint64_t transid; uint16_t m; uint16_t n; uint8_t type; char name[1]; } DIR_ITEM; typedef struct { uint64_t seconds; uint32_t nanoseconds; } BTRFS_TIME; typedef struct { uint64_t generation; uint64_t transid; uint64_t st_size; uint64_t st_blocks; uint64_t block_group; uint32_t st_nlink; uint32_t st_uid; uint32_t st_gid; uint32_t st_mode; uint64_t st_rdev; uint32_t flags; uint32_t flags_ro; uint64_t sequence; uint8_t reserved[32]; BTRFS_TIME st_atime; BTRFS_TIME st_ctime; BTRFS_TIME st_mtime; BTRFS_TIME otime; } INODE_ITEM; static_assert(sizeof(INODE_ITEM) == 0xa0, "INODE_ITEM has wrong size"); typedef struct { INODE_ITEM inode; uint64_t generation; uint64_t objid; uint64_t block_number; uint64_t byte_limit; uint64_t bytes_used; uint64_t last_snapshot_generation; uint64_t flags; uint32_t num_references; KEY drop_progress; uint8_t drop_level; uint8_t root_level; uint64_t generation2; BTRFS_UUID uuid; BTRFS_UUID parent_uuid; BTRFS_UUID received_uuid; uint64_t ctransid; uint64_t otransid; uint64_t stransid; uint64_t rtransid; BTRFS_TIME ctime; BTRFS_TIME otime; BTRFS_TIME stime; BTRFS_TIME rtime; uint64_t reserved[8]; } ROOT_ITEM; typedef struct { uint64_t size; uint64_t root_id; uint64_t stripe_length; uint64_t type; uint32_t opt_io_alignment; uint32_t opt_io_width; uint32_t sector_size; uint16_t num_stripes; uint16_t sub_stripes; } CHUNK_ITEM; typedef struct { uint64_t dev_id; uint64_t offset; BTRFS_UUID dev_uuid; } CHUNK_ITEM_STRIPE; typedef struct { uint64_t generation; uint64_t decoded_size; uint8_t compression; uint8_t encryption; uint16_t encoding; uint8_t type; uint8_t data[1]; } EXTENT_DATA; typedef struct { uint64_t address; uint64_t size; uint64_t offset; uint64_t num_bytes; } EXTENT_DATA2; typedef struct { uint64_t index; uint16_t n; char name[1]; } INODE_REF; typedef struct { uint64_t dir; uint64_t index; uint16_t n; char name[1]; } INODE_EXTREF; #define EXTENT_ITEM_DATA 0x001 #define EXTENT_ITEM_TREE_BLOCK 0x002 #define EXTENT_ITEM_SHARED_BACKREFS 0x100 typedef struct { uint64_t refcount; uint64_t generation; uint64_t flags; } EXTENT_ITEM; typedef struct { KEY firstitem; uint8_t level; } EXTENT_ITEM2; typedef struct { uint32_t refcount; } EXTENT_ITEM_V0; typedef struct { EXTENT_ITEM extent_item; KEY firstitem; uint8_t level; } EXTENT_ITEM_TREE; typedef struct { uint64_t offset; } TREE_BLOCK_REF; typedef struct { uint64_t root; uint64_t objid; uint64_t offset; uint32_t count; } EXTENT_DATA_REF; typedef struct { uint64_t used; uint64_t chunk_tree; uint64_t flags; } BLOCK_GROUP_ITEM; typedef struct { uint64_t root; uint64_t gen; uint64_t objid; uint32_t count; } EXTENT_REF_V0; typedef struct { uint64_t offset; } SHARED_BLOCK_REF; typedef struct { uint64_t offset; uint32_t count; } SHARED_DATA_REF; #define FREE_SPACE_EXTENT 1 #define FREE_SPACE_BITMAP 2 typedef struct { uint64_t offset; uint64_t size; uint8_t type; } FREE_SPACE_ENTRY; typedef struct { KEY key; uint64_t generation; uint64_t num_entries; uint64_t num_bitmaps; } FREE_SPACE_ITEM; typedef struct { uint64_t dir; uint64_t index; uint16_t n; char name[1]; } ROOT_REF; typedef struct { uint64_t chunktree; uint64_t objid; uint64_t address; uint64_t length; BTRFS_UUID chunktree_uuid; } DEV_EXTENT; #define BALANCE_FLAGS_DATA 0x1 #define BALANCE_FLAGS_SYSTEM 0x2 #define BALANCE_FLAGS_METADATA 0x4 #define BALANCE_ARGS_FLAGS_PROFILES 0x001 #define BALANCE_ARGS_FLAGS_USAGE 0x002 #define BALANCE_ARGS_FLAGS_DEVID 0x004 #define BALANCE_ARGS_FLAGS_DRANGE 0x008 #define BALANCE_ARGS_FLAGS_VRANGE 0x010 #define BALANCE_ARGS_FLAGS_LIMIT 0x020 #define BALANCE_ARGS_FLAGS_LIMIT_RANGE 0x040 #define BALANCE_ARGS_FLAGS_STRIPES_RANGE 0x080 #define BALANCE_ARGS_FLAGS_CONVERT 0x100 #define BALANCE_ARGS_FLAGS_SOFT 0x200 #define BALANCE_ARGS_FLAGS_USAGE_RANGE 0x400 typedef struct { uint64_t profiles; union { uint64_t usage; struct { uint32_t usage_start; uint32_t usage_end; }; }; uint64_t devid; uint64_t drange_start; uint64_t drange_end; uint64_t vrange_start; uint64_t vrange_end; uint64_t convert; uint64_t flags; union { uint64_t limit; struct { uint32_t limit_start; uint32_t limit_end; }; }; uint32_t stripes_start; uint32_t stripes_end; uint8_t reserved[48]; } BALANCE_ARGS; typedef struct { uint64_t flags; BALANCE_ARGS data; BALANCE_ARGS metadata; BALANCE_ARGS system; uint8_t reserved[32]; } BALANCE_ITEM; #define BTRFS_FREE_SPACE_USING_BITMAPS 1 typedef struct { uint32_t count; uint32_t flags; } FREE_SPACE_INFO; #define BTRFS_DEV_STAT_WRITE_ERRORS 0 #define BTRFS_DEV_STAT_READ_ERRORS 1 #define BTRFS_DEV_STAT_FLUSH_ERRORS 2 #define BTRFS_DEV_STAT_CORRUPTION_ERRORS 3 #define BTRFS_DEV_STAT_GENERATION_ERRORS 4 #define BTRFS_SEND_CMD_SUBVOL 1 #define BTRFS_SEND_CMD_SNAPSHOT 2 #define BTRFS_SEND_CMD_MKFILE 3 #define BTRFS_SEND_CMD_MKDIR 4 #define BTRFS_SEND_CMD_MKNOD 5 #define BTRFS_SEND_CMD_MKFIFO 6 #define BTRFS_SEND_CMD_MKSOCK 7 #define BTRFS_SEND_CMD_SYMLINK 8 #define BTRFS_SEND_CMD_RENAME 9 #define BTRFS_SEND_CMD_LINK 10 #define BTRFS_SEND_CMD_UNLINK 11 #define BTRFS_SEND_CMD_RMDIR 12 #define BTRFS_SEND_CMD_SET_XATTR 13 #define BTRFS_SEND_CMD_REMOVE_XATTR 14 #define BTRFS_SEND_CMD_WRITE 15 #define BTRFS_SEND_CMD_CLONE 16 #define BTRFS_SEND_CMD_TRUNCATE 17 #define BTRFS_SEND_CMD_CHMOD 18 #define BTRFS_SEND_CMD_CHOWN 19 #define BTRFS_SEND_CMD_UTIMES 20 #define BTRFS_SEND_CMD_END 21 #define BTRFS_SEND_CMD_UPDATE_EXTENT 22 #define BTRFS_SEND_TLV_UUID 1 #define BTRFS_SEND_TLV_TRANSID 2 #define BTRFS_SEND_TLV_INODE 3 #define BTRFS_SEND_TLV_SIZE 4 #define BTRFS_SEND_TLV_MODE 5 #define BTRFS_SEND_TLV_UID 6 #define BTRFS_SEND_TLV_GID 7 #define BTRFS_SEND_TLV_RDEV 8 #define BTRFS_SEND_TLV_CTIME 9 #define BTRFS_SEND_TLV_MTIME 10 #define BTRFS_SEND_TLV_ATIME 11 #define BTRFS_SEND_TLV_OTIME 12 #define BTRFS_SEND_TLV_XATTR_NAME 13 #define BTRFS_SEND_TLV_XATTR_DATA 14 #define BTRFS_SEND_TLV_PATH 15 #define BTRFS_SEND_TLV_PATH_TO 16 #define BTRFS_SEND_TLV_PATH_LINK 17 #define BTRFS_SEND_TLV_OFFSET 18 #define BTRFS_SEND_TLV_DATA 19 #define BTRFS_SEND_TLV_CLONE_UUID 20 #define BTRFS_SEND_TLV_CLONE_CTRANSID 21 #define BTRFS_SEND_TLV_CLONE_PATH 22 #define BTRFS_SEND_TLV_CLONE_OFFSET 23 #define BTRFS_SEND_TLV_CLONE_LENGTH 24 #define BTRFS_SEND_MAGIC "btrfs-stream" typedef struct { uint8_t magic[13]; uint32_t version; } btrfs_send_header; typedef struct { uint32_t length; uint16_t cmd; uint32_t csum; } btrfs_send_command; typedef struct { uint16_t type; uint16_t length; } btrfs_send_tlv; #pragma pack(pop) ================================================ FILE: src/btrfs.inf ================================================ ;;; ;;; WinBtrfs ;;; ;;; ;;; Copyright (c) 2016-24 Mark Harmstone ;;; [Version] Signature = "$Windows NT$" Class = Volume ClassGuid = {71a27cdd-812a-11d0-bec7-08002be2092f} Provider = %Me% DriverVer = 03/15/2024,1.9.0.0 CatalogFile = btrfs.cat [DestinationDirs] Btrfs.DriverFiles = 12 ;%windir%\system32\drivers Btrfs.DllFiles = 11 ;%windir%\system32 ;; ;; Default install sections ;; [DefaultInstall.NTamd64] OptionDesc = %ServiceDescription% CopyFiles = Btrfs.DriverFiles,Btrfs.DllFiles AddReg = shellbtrfs_AddReg CopyINF = btrfs-vol.inf [DefaultInstall.NTx86] OptionDesc = %ServiceDescription% CopyFiles = Btrfs.DriverFiles,Btrfs.DllFiles AddReg = shellbtrfs_AddReg CopyINF = btrfs-vol.inf [DefaultInstall.NTarm] OptionDesc = %ServiceDescription% CopyFiles = Btrfs.DriverFiles,Btrfs.DllFiles AddReg = shellbtrfs_AddReg CopyINF = btrfs-vol.inf [DefaultInstall.NTarm64] OptionDesc = %ServiceDescription% CopyFiles = Btrfs.DriverFiles,Btrfs.DllFiles AddReg = shellbtrfs_AddReg CopyINF = btrfs-vol.inf [DefaultInstall.NTamd64.Services] AddService = %ServiceName%,0x802,Btrfs.Service [DefaultInstall.NTx86.Services] AddService = %ServiceName%,0x802,Btrfs.Service [DefaultInstall.NTarm.Services] AddService = %ServiceName%,0x802,Btrfs.Service [DefaultInstall.NTarm64.Services] AddService = %ServiceName%,0x802,Btrfs.Service ; ; Services Section ; [Btrfs.Service] DisplayName = %ServiceName% Description = %ServiceDescription% ServiceBinary = %12%\%DriverName%.sys ;%windir%\system32\drivers\ ServiceType = 1 StartType = 1 ;SERVICE_SYSTEM_START ErrorControl = 1 LoadOrderGroup = "File System" ; ; Copy Files ; [Btrfs.DriverFiles] %DriverName%.sys [Btrfs.DllFiles] shellbtrfs.dll ubtrfs.dll mkbtrfs.exe [SourceDisksFiles] btrfs.sys = 1,, shellbtrfs.dll = 1,, ubtrfs.dll = 1,, mkbtrfs.exe = 1,, [SourceDisksNames.x86] 1 = %DiskId1%,,,\x86 [SourceDisksNames.amd64] 1 = %DiskId1%,,,\amd64 [SourceDisksNames.arm] 1 = %DiskId1%,,,\arm [SourceDisksNames.arm64] 1 = %DiskId1%,,,\aarch64 [shellbtrfs_AddReg] HKCR,*\ShellEx\PropertySheetHandlers\WinBtrfs,,,"{2690B74F-F353-422D-BB12-401581EEF8F2}" HKCR,CLSID\{2690B74F-F353-422D-BB12-401581EEF8F0},,,"WinBtrfs shell extension (icon handler)" HKCR,CLSID\{2690B74F-F353-422D-BB12-401581EEF8F0}\InprocServer32,,%REG_EXPAND_SZ%,"%%SystemRoot%%\System32\shellbtrfs.dll" HKCR,CLSID\{2690B74F-F353-422D-BB12-401581EEF8F0}\InprocServer32,ThreadingModel,,"Apartment" HKCR,CLSID\{2690B74F-F353-422D-BB12-401581EEF8F1},,,"WinBtrfs shell extension (context menu)" HKCR,CLSID\{2690B74F-F353-422D-BB12-401581EEF8F1}\InprocServer32,,%REG_EXPAND_SZ%,"%%SystemRoot%%\System32\shellbtrfs.dll" HKCR,CLSID\{2690B74F-F353-422D-BB12-401581EEF8F1}\InprocServer32,ThreadingModel,,"Apartment" HKCR,CLSID\{2690B74F-F353-422D-BB12-401581EEF8F2},,,"WinBtrfs shell extension (property sheet)" HKCR,CLSID\{2690B74F-F353-422D-BB12-401581EEF8F2}\InprocServer32,,%REG_EXPAND_SZ%,"%%SystemRoot%%\System32\shellbtrfs.dll" HKCR,CLSID\{2690B74F-F353-422D-BB12-401581EEF8F2}\InprocServer32,ThreadingModel,,"Apartment" HKCR,CLSID\{2690B74F-F353-422D-BB12-401581EEF8F3},,,"WinBtrfs shell extension (volume property sheet)" HKCR,CLSID\{2690B74F-F353-422D-BB12-401581EEF8F3}\InprocServer32,,%REG_EXPAND_SZ%,"%%SystemRoot%%\System32\shellbtrfs.dll" HKCR,CLSID\{2690B74F-F353-422D-BB12-401581EEF8F3}\InprocServer32,ThreadingModel,,"Apartment" HKCR,Directory\Background\ShellEx\ContextMenuHandlers\WinBtrfs,,,"{2690B74F-F353-422D-BB12-401581EEF8F1}" HKCR,Drive\ShellEx\PropertySheetHandlers\WinBtrfs,,,"{2690B74F-F353-422D-BB12-401581EEF8F3}" HKCR,Folder\ShellEx\ContextMenuHandlers\WinBtrfs,,,"{2690B74F-F353-422D-BB12-401581EEF8F1}" HKCR,Folder\ShellEx\PropertySheetHandlers\WinBtrfs,,,"{2690B74F-F353-422D-BB12-401581EEF8F2}" ;HKLM,Software\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\WinBtrfs,,,"{2690B74F-F353-422D-BB12-401581EEF8F0}" ;; ;; String Section ;; [Strings] Me = "Mark Harmstone" ServiceDescription = "Btrfs driver" ServiceName = "btrfs" DriverName = "btrfs" DiskId1 = "Btrfs Device Installation Disk" VolumeName = "Btrfs volume" ControllerName = "Btrfs controller" REG_EXPAND_SZ = 0x00020000 ================================================ FILE: src/btrfs.rc.in ================================================ // Microsoft Visual C++ generated resource script. // #include "@CMAKE_CURRENT_SOURCE_DIR@/src/resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United Kingdom) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK #pragma code_page(1252) #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "080904b0" BEGIN VALUE "FileDescription", "WinBtrfs" VALUE "FileVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" VALUE "InternalName", "btrfs" VALUE "LegalCopyright", "Copyright (c) Mark Harmstone 2016-24" VALUE "OriginalFilename", "btrfs.sys" VALUE "ProductName", "WinBtrfs" VALUE "ProductVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x809, 1200 END END #endif // English (United Kingdom) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: src/btrfs_drv.h ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #pragma once #undef _WIN32_WINNT #undef NTDDI_VERSION #define _WIN32_WINNT 0x0601 #define NTDDI_VERSION 0x06020000 // Win 8 #define _CRT_SECURE_NO_WARNINGS #define _NO_CRT_STDIO_INLINE #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4163) #pragma warning(disable:4311) #pragma warning(disable:4312) #else #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-compare" #pragma GCC diagnostic ignored "-Wsign-conversion" #endif #include #include #include #include #include #ifdef _MSC_VER #pragma warning(pop) #else #pragma GCC diagnostic pop #endif #include #include #include #include #include #include "btrfs.h" #include "btrfsioctl.h" #ifdef _DEBUG // #define DEBUG_FCB_REFCOUNTS // #define DEBUG_LONG_MESSAGES // #define DEBUG_FLUSH_TIMES // #define DEBUG_CHUNK_LOCKS // #define DEBUG_TRIM_EMULATION #define DEBUG_PARANOID #endif #define UNUSED(x) (void)(x) #define BTRFS_NODE_TYPE_CCB 0x2295 #define BTRFS_NODE_TYPE_FCB 0x2296 #define ALLOC_TAG 0x7442484D //'MHBt' #define ALLOC_TAG_ZLIB 0x7A42484D //'MHBz' #define UID_NOBODY 65534 #define GID_NOBODY 65534 #define EA_NTACL "security.NTACL" #define EA_NTACL_HASH 0x45922146 #define EA_DOSATTRIB "user.DOSATTRIB" #define EA_DOSATTRIB_HASH 0x914f9939 #define EA_REPARSE "user.reparse" #define EA_REPARSE_HASH 0xfabad1fe #define EA_EA "user.EA" #define EA_EA_HASH 0x8270dd43 #define EA_CASE_SENSITIVE "user.casesensitive" #define EA_CASE_SENSITIVE_HASH 0x1a9d97d4 #define EA_PROP_COMPRESSION "btrfs.compression" #define EA_PROP_COMPRESSION_HASH 0x20ccdf69 #define MAX_EXTENT_SIZE 0x8000000 // 128 MB #define COMPRESSED_EXTENT_SIZE 0x20000 // 128 KB #define READ_AHEAD_GRANULARITY COMPRESSED_EXTENT_SIZE // really ought to be a multiple of COMPRESSED_EXTENT_SIZE #ifndef IO_REPARSE_TAG_LX_SYMLINK #define IO_REPARSE_TAG_LX_SYMLINK 0xa000001d #define IO_REPARSE_TAG_AF_UNIX 0x80000023 #define IO_REPARSE_TAG_LX_FIFO 0x80000024 #define IO_REPARSE_TAG_LX_CHR 0x80000025 #define IO_REPARSE_TAG_LX_BLK 0x80000026 #endif #define BTRFS_VOLUME_PREFIX L"\\Device\\Btrfs{" #if defined(_MSC_VER) || defined(__clang__) #define try __try #define except __except #define finally __finally #define leave __leave #else #define try if (1) #define except(x) if (0 && (x)) #define finally if (1) #define leave #endif #ifndef InterlockedIncrement64 #define InterlockedIncrement64(a) __sync_add_and_fetch(a, 1) #endif #ifndef FILE_SUPPORTS_BLOCK_REFCOUNTING #define FILE_SUPPORTS_BLOCK_REFCOUNTING 0x08000000 #endif #ifndef FILE_SUPPORTS_POSIX_UNLINK_RENAME #define FILE_SUPPORTS_POSIX_UNLINK_RENAME 0x00000400 #endif #ifndef FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL #define FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL 0x00020000 #endif #ifndef _MSC_VER typedef struct _FILE_ID_128 { UCHAR Identifier[16]; } FILE_ID_128, *PFILE_ID_128; #define FILE_CS_FLAG_CASE_SENSITIVE_DIR 1 #endif typedef struct _DUPLICATE_EXTENTS_DATA { HANDLE FileHandle; LARGE_INTEGER SourceFileOffset; LARGE_INTEGER TargetFileOffset; LARGE_INTEGER ByteCount; } DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA; #define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_ACCESS) typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER { WORD ChecksumAlgorithm; WORD Reserved; DWORD Flags; DWORD ChecksumChunkSizeInBytes; DWORD ClusterSizeInBytes; } FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER; typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER { WORD ChecksumAlgorithm; WORD Reserved; DWORD Flags; } FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER; #define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) #ifndef _MSC_VER #define __drv_aliasesMem #define _Dispatch_type_(a) #define _Lock_level_order_(a,b) #endif _Create_lock_level_(tree_lock) _Create_lock_level_(fcb_lock) _Lock_level_order_(tree_lock, fcb_lock) #define MAX_HASH_SIZE 32 struct _device_extension; typedef struct _fcb_nonpaged { FAST_MUTEX HeaderMutex; SECTION_OBJECT_POINTERS segment_object; ERESOURCE resource; ERESOURCE paging_resource; ERESOURCE dir_children_lock; } fcb_nonpaged; struct _root; typedef struct { uint64_t offset; uint16_t datalen; bool unique; bool ignore; bool inserted; void* csum; LIST_ENTRY list_entry; EXTENT_DATA extent_data; } extent; typedef struct { uint64_t parent; uint64_t index; UNICODE_STRING name; ANSI_STRING utf8; LIST_ENTRY list_entry; } hardlink; struct _file_ref; typedef struct { KEY key; uint64_t index; uint8_t type; ANSI_STRING utf8; uint32_t hash; UNICODE_STRING name; uint32_t hash_uc; UNICODE_STRING name_uc; ULONG size; struct _file_ref* fileref; bool root_dir; LIST_ENTRY list_entry_index; LIST_ENTRY list_entry_hash; LIST_ENTRY list_entry_hash_uc; } dir_child; enum prop_compression_type { PropCompression_None, PropCompression_Zlib, PropCompression_LZO, PropCompression_ZSTD }; typedef struct { LIST_ENTRY list_entry; USHORT namelen; USHORT valuelen; bool dirty; char data[1]; } xattr; typedef struct _fcb { FSRTL_ADVANCED_FCB_HEADER Header; struct _fcb_nonpaged* nonpaged; LONG refcount; POOL_TYPE pool_type; struct _device_extension* Vcb; struct _root* subvol; uint64_t inode; uint32_t hash; uint8_t type; INODE_ITEM inode_item; SECURITY_DESCRIPTOR* sd; FILE_LOCK lock; bool deleted; PKTHREAD lazy_writer_thread; ULONG atts; SHARE_ACCESS share_access; bool csum_loaded; LIST_ENTRY extents; ANSI_STRING reparse_xattr; ANSI_STRING ea_xattr; ULONG ealen; LIST_ENTRY hardlinks; struct _file_ref* fileref; bool inode_item_changed; enum prop_compression_type prop_compression; LIST_ENTRY xattrs; bool marked_as_orphan; bool case_sensitive; bool case_sensitive_set; OPLOCK oplock; LIST_ENTRY dir_children_index; LIST_ENTRY dir_children_hash; LIST_ENTRY dir_children_hash_uc; LIST_ENTRY** hash_ptrs; LIST_ENTRY** hash_ptrs_uc; bool dirty; bool sd_dirty, sd_deleted; bool atts_changed, atts_deleted; bool extents_changed; bool reparse_xattr_changed; bool ea_changed; bool prop_compression_changed; bool xattrs_changed; bool created; bool ads; uint32_t adshash; ULONG adsmaxlen; ANSI_STRING adsxattr; ANSI_STRING adsdata; LIST_ENTRY list_entry; LIST_ENTRY list_entry_all; LIST_ENTRY list_entry_dirty; } fcb; typedef struct _file_ref { fcb* fcb; ANSI_STRING oldutf8; uint64_t oldindex; bool delete_on_close; bool posix_delete; bool deleted; bool created; LIST_ENTRY children; LONG refcount; LONG open_count; struct _file_ref* parent; dir_child* dc; bool dirty; LIST_ENTRY list_entry; LIST_ENTRY list_entry_dirty; } file_ref; typedef struct { HANDLE thread; struct _ccb* ccb; void* context; KEVENT cleared_event; bool cancelling; LIST_ENTRY list_entry; } send_info; typedef struct _ccb { USHORT NodeType; CSHORT NodeSize; ULONG disposition; ULONG options; uint64_t query_dir_offset; UNICODE_STRING query_string; bool has_wildcard; bool specific_file; bool manage_volume_privilege; bool allow_extended_dasd_io; bool reserving; ACCESS_MASK access; file_ref* fileref; UNICODE_STRING filename; ULONG ea_index; bool case_sensitive; bool user_set_creation_time; bool user_set_access_time; bool user_set_write_time; bool user_set_change_time; bool lxss; send_info* send; NTSTATUS send_status; } ccb; struct _device_extension; typedef struct { uint64_t address; uint64_t generation; struct _tree* tree; } tree_holder; typedef struct _tree_data { KEY key; LIST_ENTRY list_entry; bool ignore; bool inserted; union { tree_holder treeholder; struct { uint16_t size; uint8_t* data; }; }; } tree_data; typedef struct { FAST_MUTEX mutex; } tree_nonpaged; typedef struct _tree { tree_nonpaged* nonpaged; tree_header header; uint32_t hash; bool has_address; uint32_t size; struct _device_extension* Vcb; struct _tree* parent; tree_data* paritem; struct _root* root; LIST_ENTRY itemlist; LIST_ENTRY list_entry; LIST_ENTRY list_entry_hash; uint64_t new_address; bool has_new_address; bool updated_extents; bool write; bool is_unique; bool uniqueness_determined; uint8_t* buf; } tree; typedef struct { ERESOURCE load_tree_lock; } root_nonpaged; typedef struct _root { uint64_t id; LONGLONG lastinode; // signed so we can use InterlockedIncrement64 tree_holder treeholder; root_nonpaged* nonpaged; ROOT_ITEM root_item; bool dirty; bool received; PEPROCESS reserved; uint64_t parent; LONG send_ops; uint64_t fcbs_version; bool checked_for_orphans; bool dropped; LIST_ENTRY fcbs; LIST_ENTRY* fcbs_ptrs[256]; LIST_ENTRY list_entry; LIST_ENTRY list_entry_dirty; } root; enum batch_operation { Batch_Delete, Batch_DeleteInode, Batch_DeleteDirItem, Batch_DeleteInodeRef, Batch_DeleteInodeExtRef, Batch_DeleteXattr, Batch_DeleteExtentData, Batch_DeleteFreeSpace, Batch_Insert, Batch_SetXattr, Batch_DirItem, Batch_InodeRef, Batch_InodeExtRef, }; typedef struct { KEY key; void* data; uint16_t datalen; enum batch_operation operation; LIST_ENTRY list_entry; } batch_item; typedef struct { KEY key; LIST_ENTRY items; unsigned int num_items; LIST_ENTRY list_entry; } batch_item_ind; typedef struct { root* r; LIST_ENTRY items_ind; LIST_ENTRY list_entry; } batch_root; typedef struct { tree* tree; tree_data* item; } traverse_ptr; typedef struct _root_cache { root* root; struct _root_cache* next; } root_cache; typedef struct { uint64_t address; uint64_t size; LIST_ENTRY list_entry; LIST_ENTRY list_entry_size; } space; typedef struct { PDEVICE_OBJECT devobj; PFILE_OBJECT fileobj; DEV_ITEM devitem; bool removable; bool seeding; bool readonly; bool reloc; bool trim; bool can_flush; ULONG change_count; ULONG disk_num; ULONG part_num; uint64_t stats[5]; bool stats_changed; LIST_ENTRY space; LIST_ENTRY list_entry; ULONG num_trim_entries; LIST_ENTRY trim_list; } device; typedef struct { uint64_t start; uint64_t length; PETHREAD thread; LIST_ENTRY list_entry; } range_lock; typedef struct { uint64_t address; ULONG* bmparr; ULONG bmplen; RTL_BITMAP bmp; LIST_ENTRY list_entry; uint8_t data[1]; } partial_stripe; typedef struct { CHUNK_ITEM* chunk_item; uint16_t size; uint64_t offset; uint64_t used; uint64_t oldused; device** devices; fcb* cache; fcb* old_cache; LIST_ENTRY space; LIST_ENTRY space_size; LIST_ENTRY deleting; LIST_ENTRY changed_extents; LIST_ENTRY range_locks; ERESOURCE range_locks_lock; KEVENT range_locks_event; ERESOURCE lock; ERESOURCE changed_extents_lock; bool created; bool readonly; bool reloc; bool last_alloc_set; bool cache_loaded; bool changed; bool space_changed; uint64_t last_alloc; uint16_t last_stripe; LIST_ENTRY partial_stripes; ERESOURCE partial_stripes_lock; ULONG balance_num; LIST_ENTRY list_entry; LIST_ENTRY list_entry_balance; } chunk; typedef struct { uint64_t address; uint64_t size; uint64_t old_size; uint64_t count; uint64_t old_count; bool no_csum; bool superseded; LIST_ENTRY refs; LIST_ENTRY old_refs; LIST_ENTRY list_entry; } changed_extent; typedef struct { uint8_t type; union { EXTENT_DATA_REF edr; SHARED_DATA_REF sdr; }; LIST_ENTRY list_entry; } changed_extent_ref; typedef struct { KEY key; void* data; USHORT size; LIST_ENTRY list_entry; } sys_chunk; enum calc_thread_type { calc_thread_crc32c, calc_thread_xxhash, calc_thread_sha256, calc_thread_blake2, calc_thread_decomp_zlib, calc_thread_decomp_lzo, calc_thread_decomp_zstd, calc_thread_comp_zlib, calc_thread_comp_lzo, calc_thread_comp_zstd, }; typedef struct { LIST_ENTRY list_entry; void* in; void* out; unsigned int inlen, outlen, off, space_left; LONG left, not_started; KEVENT event; enum calc_thread_type type; NTSTATUS Status; } calc_job; typedef struct { PDEVICE_OBJECT DeviceObject; HANDLE handle; KEVENT finished; unsigned int number; bool quit; } drv_calc_thread; typedef struct { ULONG num_threads; LIST_ENTRY job_list; KSPIN_LOCK spinlock; drv_calc_thread* threads; KEVENT event; } drv_calc_threads; typedef struct { bool ignore; bool compress; bool compress_force; uint8_t compress_type; bool readonly; uint32_t zlib_level; uint32_t zstd_level; uint32_t flush_interval; uint32_t max_inline; uint64_t subvol_id; bool skip_balance; bool no_barrier; bool no_trim; bool clear_cache; bool allow_degraded; bool no_root_dir; bool nodatacow; } mount_options; #define VCB_TYPE_FS 1 #define VCB_TYPE_CONTROL 2 #define VCB_TYPE_VOLUME 3 #define VCB_TYPE_PDO 4 #define VCB_TYPE_BUS 5 #define BALANCE_OPTS_DATA 0 #define BALANCE_OPTS_METADATA 1 #define BALANCE_OPTS_SYSTEM 2 typedef struct { HANDLE thread; uint64_t total_chunks; uint64_t chunks_left; btrfs_balance_opts opts[3]; bool paused; bool stopping; bool removing; bool shrinking; bool dev_readonly; ULONG balance_num; NTSTATUS status; KEVENT event; KEVENT finished; } balance_info; typedef struct { uint64_t address; uint64_t device; bool recovered; bool is_metadata; bool parity; LIST_ENTRY list_entry; union { struct { uint64_t subvol; uint64_t offset; uint16_t filename_length; WCHAR filename[1]; } data; struct { uint64_t root; uint8_t level; KEY firstitem; } metadata; }; } scrub_error; typedef struct { HANDLE thread; ERESOURCE stats_lock; KEVENT event; KEVENT finished; bool stopping; bool paused; LARGE_INTEGER start_time; LARGE_INTEGER finish_time; LARGE_INTEGER resume_time; LARGE_INTEGER duration; uint64_t total_chunks; uint64_t chunks_left; uint64_t data_scrubbed; NTSTATUS error; ULONG num_errors; LIST_ENTRY errors; } scrub_info; struct _volume_device_extension; typedef struct _device_extension { uint32_t type; mount_options options; PVPB Vpb; PDEVICE_OBJECT devobj; struct _volume_device_extension* vde; LIST_ENTRY devices; #ifdef DEBUG_CHUNK_LOCKS LONG chunk_locks_held; #endif uint64_t devices_loaded; superblock superblock; unsigned int sector_shift; unsigned int csum_size; bool readonly; bool removing; bool locked; bool lock_paused_balance; bool disallow_dismount; LONG page_file_count; bool trim; PFILE_OBJECT locked_fileobj; fcb* volume_fcb; fcb* dummy_fcb; file_ref* root_fileref; LONG open_files; _Has_lock_level_(fcb_lock) ERESOURCE fcb_lock; ERESOURCE fileref_lock; ERESOURCE load_lock; _Has_lock_level_(tree_lock) ERESOURCE tree_lock; PNOTIFY_SYNC NotifySync; LIST_ENTRY DirNotifyList; bool need_write; bool stats_changed; uint64_t data_flags; uint64_t metadata_flags; uint64_t system_flags; LIST_ENTRY roots; LIST_ENTRY drop_roots; root* chunk_root; root* root_root; root* extent_root; root* checksum_root; root* dev_root; root* uuid_root; root* data_reloc_root; root* space_root; root* block_group_root; bool log_to_phys_loaded; bool chunk_usage_found; LIST_ENTRY sys_chunks; LIST_ENTRY chunks; LIST_ENTRY trees; LIST_ENTRY trees_hash; LIST_ENTRY* trees_ptrs[256]; FAST_MUTEX trees_list_mutex; LIST_ENTRY all_fcbs; LIST_ENTRY dirty_fcbs; ERESOURCE dirty_fcbs_lock; LIST_ENTRY dirty_filerefs; ERESOURCE dirty_filerefs_lock; LIST_ENTRY dirty_subvols; ERESOURCE dirty_subvols_lock; ERESOURCE chunk_lock; HANDLE flush_thread_handle; KTIMER flush_thread_timer; KEVENT flush_thread_finished; drv_calc_threads calcthreads; balance_info balance; scrub_info scrub; ERESOURCE send_load_lock; LONG running_sends; LIST_ENTRY send_ops; PFILE_OBJECT root_file; PAGED_LOOKASIDE_LIST tree_data_lookaside; PAGED_LOOKASIDE_LIST traverse_ptr_lookaside; PAGED_LOOKASIDE_LIST batch_item_lookaside; PAGED_LOOKASIDE_LIST fileref_lookaside; PAGED_LOOKASIDE_LIST fcb_lookaside; PAGED_LOOKASIDE_LIST name_bit_lookaside; NPAGED_LOOKASIDE_LIST range_lock_lookaside; NPAGED_LOOKASIDE_LIST fcb_np_lookaside; LIST_ENTRY list_entry; } device_extension; typedef struct { uint32_t type; } control_device_extension; typedef struct { uint32_t type; PDEVICE_OBJECT buspdo; PDEVICE_OBJECT attached_device; UNICODE_STRING bus_name; } bus_device_extension; typedef struct { BTRFS_UUID uuid; uint64_t devid; uint64_t generation; PDEVICE_OBJECT devobj; PFILE_OBJECT fileobj; UNICODE_STRING pnp_name; uint64_t size; bool seeding; bool had_drive_letter; void* notification_entry; ULONG disk_num; ULONG part_num; bool boot_volume; LIST_ENTRY list_entry; } volume_child; struct pdo_device_extension; typedef struct _volume_device_extension { uint32_t type; UNICODE_STRING name; PDEVICE_OBJECT device; PDEVICE_OBJECT mounted_device; PDEVICE_OBJECT pdo; struct pdo_device_extension* pdode; UNICODE_STRING bus_name; PDEVICE_OBJECT attached_device; bool removing; bool dead; LONG open_count; } volume_device_extension; typedef struct pdo_device_extension { uint32_t type; BTRFS_UUID uuid; volume_device_extension* vde; PDEVICE_OBJECT pdo; bool removable; bool dont_report; uint64_t num_children; uint64_t children_loaded; ERESOURCE child_lock; LIST_ENTRY children; LIST_ENTRY list_entry; } pdo_device_extension; typedef struct { LIST_ENTRY listentry; PSID sid; uint32_t uid; } uid_map; typedef struct { LIST_ENTRY listentry; PSID sid; uint32_t gid; } gid_map; enum write_data_status { WriteDataStatus_Pending, WriteDataStatus_Success, WriteDataStatus_Error, WriteDataStatus_Cancelling, WriteDataStatus_Cancelled, WriteDataStatus_Ignore }; struct _write_data_context; typedef struct { struct _write_data_context* context; uint8_t* buf; PMDL mdl; device* device; PIRP Irp; IO_STATUS_BLOCK iosb; enum write_data_status status; LIST_ENTRY list_entry; } write_data_stripe; typedef struct _write_data_context { KEVENT Event; LIST_ENTRY stripes; LONG stripes_left; bool need_wait; uint8_t *parity1, *parity2, *scratch; PMDL mdl, parity1_mdl, parity2_mdl; } write_data_context; typedef struct { uint64_t address; uint32_t length; uint8_t* data; chunk* c; bool allocated; LIST_ENTRY list_entry; } tree_write; typedef struct { UNICODE_STRING us; LIST_ENTRY list_entry; } name_bit; _Requires_lock_not_held_(Vcb->fcb_lock) _Acquires_shared_lock_(Vcb->fcb_lock) static __inline void acquire_fcb_lock_shared(device_extension* Vcb) { ExAcquireResourceSharedLite(&Vcb->fcb_lock, true); } _Requires_lock_not_held_(Vcb->fcb_lock) _Acquires_exclusive_lock_(Vcb->fcb_lock) static __inline void acquire_fcb_lock_exclusive(device_extension* Vcb) { ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, true); } _Requires_lock_held_(Vcb->fcb_lock) _Releases_lock_(Vcb->fcb_lock) static __inline void release_fcb_lock(device_extension* Vcb) { ExReleaseResourceLite(&Vcb->fcb_lock); } static __inline void* map_user_buffer(PIRP Irp, ULONG priority) { if (!Irp->MdlAddress) { return Irp->UserBuffer; } else { return MmGetSystemAddressForMdlSafe(Irp->MdlAddress, priority); } } static __inline uint64_t unix_time_to_win(BTRFS_TIME* t) { return (t->seconds * 10000000) + (t->nanoseconds / 100) + 116444736000000000; } static __inline void win_time_to_unix(LARGE_INTEGER t, BTRFS_TIME* out) { ULONGLONG l = (ULONGLONG)t.QuadPart - 116444736000000000; out->seconds = l / 10000000; out->nanoseconds = (uint32_t)((l % 10000000) * 100); } _Post_satisfies_(*stripe>=0&&*stripeid << 40) | (inode & 0xffffffffff); } #define keycmp(key1, key2)\ ((key1.obj_id < key2.obj_id) ? -1 :\ ((key1.obj_id > key2.obj_id) ? 1 :\ ((key1.obj_type < key2.obj_type) ? -1 :\ ((key1.obj_type > key2.obj_type) ? 1 :\ ((key1.offset < key2.offset) ? -1 :\ ((key1.offset > key2.offset) ? 1 :\ 0)))))) _Post_satisfies_(return>=n) __inline static uint64_t sector_align(_In_ uint64_t n, _In_ uint64_t a) { if (n & (a - 1)) n = (n + a) & ~(a - 1); return n; } __inline static bool is_subvol_readonly(root* r, PIRP Irp) { if (!(r->root_item.flags & BTRFS_SUBVOL_READONLY)) return false; if (!r->reserved) return true; return (!Irp || Irp->RequestorMode == UserMode) && PsGetCurrentProcess() != r->reserved ? true : false; } __inline static uint16_t get_extent_data_len(uint8_t type) { switch (type) { case TYPE_TREE_BLOCK_REF: return sizeof(TREE_BLOCK_REF); case TYPE_EXTENT_DATA_REF: return sizeof(EXTENT_DATA_REF); case TYPE_EXTENT_REF_V0: return sizeof(EXTENT_REF_V0); case TYPE_SHARED_BLOCK_REF: return sizeof(SHARED_BLOCK_REF); case TYPE_SHARED_DATA_REF: return sizeof(SHARED_DATA_REF); default: return 0; } } __inline static uint32_t get_extent_data_refcount(uint8_t type, void* data) { switch (type) { case TYPE_TREE_BLOCK_REF: return 1; case TYPE_EXTENT_DATA_REF: { EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data; return edr->count; } case TYPE_EXTENT_REF_V0: { EXTENT_REF_V0* erv0 = (EXTENT_REF_V0*)data; return erv0->count; } case TYPE_SHARED_BLOCK_REF: return 1; case TYPE_SHARED_DATA_REF: { SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data; return sdr->count; } default: return 0; } } // in xor-gas.S #if defined(_X86_) || defined(_AMD64_) void __stdcall do_xor_sse2(uint8_t* buf1, uint8_t* buf2, uint32_t len); void __stdcall do_xor_avx2(uint8_t* buf1, uint8_t* buf2, uint32_t len); #endif // in btrfs.c _Ret_maybenull_ device* find_device_from_uuid(_In_ device_extension* Vcb, _In_ BTRFS_UUID* uuid); _Success_(return) bool get_file_attributes_from_xattr(_In_reads_bytes_(len) char* val, _In_ uint16_t len, _Out_ ULONG* atts); ULONG get_file_attributes(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _In_ uint64_t inode, _In_ uint8_t type, _In_ bool dotfile, _In_ bool ignore_xa, _In_opt_ PIRP Irp); _Success_(return) bool 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, _Out_ uint8_t** data, _Out_ uint16_t* datalen, _In_opt_ PIRP Irp); #ifndef DEBUG_FCB_REFCOUNTS void free_fcb(_Inout_ fcb* fcb); #endif void free_fileref(_Inout_ file_ref* fr); void protect_superblocks(_Inout_ chunk* c); bool is_top_level(_In_ PIRP Irp); NTSTATUS create_root(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ uint64_t id, _Out_ root** rootptr, _In_ bool no_tree, _In_ uint64_t offset, _In_opt_ PIRP Irp); void uninit(_In_ device_extension* Vcb); NTSTATUS dev_ioctl(_In_ PDEVICE_OBJECT DeviceObject, _In_ ULONG ControlCode, _In_reads_bytes_opt_(InputBufferSize) PVOID InputBuffer, _In_ ULONG InputBufferSize, _Out_writes_bytes_opt_(OutputBufferSize) PVOID OutputBuffer, _In_ ULONG OutputBufferSize, _In_ bool Override, _Out_opt_ IO_STATUS_BLOCK* iosb); NTSTATUS check_file_name_valid(_In_ PUNICODE_STRING us, _In_ bool posix, _In_ bool stream); void send_notification_fileref(_In_ file_ref* fileref, _In_ ULONG filter_match, _In_ ULONG action, _In_opt_ PUNICODE_STRING stream); void queue_notification_fcb(_In_ file_ref* fileref, _In_ ULONG filter_match, _In_ ULONG action, _In_opt_ PUNICODE_STRING stream); typedef void (__stdcall *xor_func)(uint8_t* buf1, uint8_t* buf2, uint32_t len); extern xor_func do_xor; #ifdef DEBUG_CHUNK_LOCKS #define acquire_chunk_lock(c, Vcb) { ExAcquireResourceExclusiveLite(&c->lock, true); InterlockedIncrement(&Vcb->chunk_locks_held); } #define release_chunk_lock(c, Vcb) { InterlockedDecrement(&Vcb->chunk_locks_held); ExReleaseResourceLite(&c->lock); } #else #define acquire_chunk_lock(c, Vcb) ExAcquireResourceExclusiveLite(&(c)->lock, true) #define release_chunk_lock(c, Vcb) ExReleaseResourceLite(&(c)->lock) #endif void mark_fcb_dirty(_In_ fcb* fcb); void mark_fileref_dirty(_In_ file_ref* fileref); NTSTATUS delete_fileref(_In_ file_ref* fileref, _In_opt_ PFILE_OBJECT FileObject, _In_ bool make_orphan, _In_opt_ PIRP Irp, _In_ LIST_ENTRY* rollback); void chunk_lock_range(_In_ device_extension* Vcb, _In_ chunk* c, _In_ uint64_t start, _In_ uint64_t length); void chunk_unlock_range(_In_ device_extension* Vcb, _In_ chunk* c, _In_ uint64_t start, _In_ uint64_t length); void init_device(_In_ device_extension* Vcb, _Inout_ device* dev, _In_ bool get_nums); void init_file_cache(_In_ PFILE_OBJECT FileObject, _In_ CC_FILE_SIZES* ccfs); NTSTATUS sync_read_phys(_In_ PDEVICE_OBJECT DeviceObject, _In_ PFILE_OBJECT FileObject, _In_ uint64_t StartingOffset, _In_ ULONG Length, _Out_writes_bytes_(Length) PUCHAR Buffer, _In_ bool override); NTSTATUS get_device_pnp_name(_In_ PDEVICE_OBJECT DeviceObject, _Out_ PUNICODE_STRING pnp_name, _Out_ const GUID** guid); void log_device_error(_In_ device_extension* Vcb, _Inout_ device* dev, _In_ int error); NTSTATUS find_chunk_usage(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp); _Function_class_(DRIVER_ADD_DEVICE) NTSTATUS __stdcall AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT PhysicalDeviceObject); void reap_fcb(fcb* fcb); void reap_fcbs(device_extension* Vcb); void reap_fileref(device_extension* Vcb, file_ref* fr); void reap_filerefs(device_extension* Vcb, file_ref* fr); NTSTATUS utf8_to_utf16(WCHAR* dest, ULONG dest_max, ULONG* dest_len, char* src, ULONG src_len); NTSTATUS utf16_to_utf8(char* dest, ULONG dest_max, ULONG* dest_len, WCHAR* src, ULONG src_len); uint32_t get_num_of_processors(); _Ret_maybenull_ root* find_default_subvol(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp); void do_shutdown(PIRP Irp); bool check_superblock_checksum(superblock* sb); #ifdef _MSC_VER #define funcname __FUNCTION__ #else #define funcname __func__ #endif extern uint32_t mount_compress; extern uint32_t mount_compress_force; extern uint32_t mount_compress_type; extern uint32_t mount_zlib_level; extern uint32_t mount_zstd_level; extern uint32_t mount_flush_interval; extern uint32_t mount_max_inline; extern uint32_t mount_skip_balance; extern uint32_t mount_no_barrier; extern uint32_t mount_no_trim; extern uint32_t mount_clear_cache; extern uint32_t mount_allow_degraded; extern uint32_t mount_readonly; extern uint32_t mount_no_root_dir; extern uint32_t mount_nodatacow; extern uint32_t no_pnp; #ifndef __GNUC__ #define __attribute__(x) #endif #ifdef _DEBUG extern bool log_started; extern uint32_t debug_log_level; #ifdef DEBUG_LONG_MESSAGES #define MSG(fn, file, line, s, level, ...) (!log_started || level <= debug_log_level) ? _debug_message(fn, file, line, s, ##__VA_ARGS__) : (void)0 #define TRACE(s, ...) MSG(funcname, __FILE__, __LINE__, s, 3, ##__VA_ARGS__) #define WARN(s, ...) MSG(funcname, __FILE__, __LINE__, s, 2, ##__VA_ARGS__) #define FIXME(s, ...) MSG(funcname, __FILE__, __LINE__, s, 1, ##__VA_ARGS__) #define ERR(s, ...) MSG(funcname, __FILE__, __LINE__, s, 1, ##__VA_ARGS__) void _debug_message(_In_ const char* func, _In_ const char* file, _In_ unsigned int line, _In_ char* s, ...) __attribute__((format(printf, 4, 5))); #else #define MSG(fn, s, level, ...) (!log_started || level <= debug_log_level) ? _debug_message(fn, s, ##__VA_ARGS__) : (void)0 #define TRACE(s, ...) MSG(funcname, s, 3, ##__VA_ARGS__) #define WARN(s, ...) MSG(funcname, s, 2, ##__VA_ARGS__) #define FIXME(s, ...) MSG(funcname, s, 1, ##__VA_ARGS__) #define ERR(s, ...) MSG(funcname, s, 1, ##__VA_ARGS__) void _debug_message(_In_ const char* func, _In_ char* s, ...) __attribute__((format(printf, 2, 3))); #endif #else #define TRACE(s, ...) do { } while(0) #define WARN(s, ...) do { } while(0) #define FIXME(s, ...) DbgPrint("Btrfs FIXME : %s : " s, funcname, ##__VA_ARGS__) #define ERR(s, ...) DbgPrint("Btrfs ERR : %s : " s, funcname, ##__VA_ARGS__) #endif #ifdef DEBUG_FCB_REFCOUNTS void _free_fcb(_Inout_ fcb* fcb, _In_ const char* func); #define free_fcb(fcb) _free_fcb(fcb, funcname) #endif // in fastio.c void init_fast_io_dispatch(FAST_IO_DISPATCH** fiod); // in sha256.c void calc_sha256(uint8_t* hash, const void* input, size_t len); #define SHA256_HASH_SIZE 32 // in blake2b-ref.c void blake2b(void *out, size_t outlen, const void* in, size_t inlen); #define BLAKE2_HASH_SIZE 32 typedef struct { LIST_ENTRY* list; LIST_ENTRY* list_size; uint64_t address; uint64_t length; chunk* chunk; } rollback_space; typedef struct { fcb* fcb; extent* ext; } rollback_extent; enum rollback_type { ROLLBACK_INSERT_EXTENT, ROLLBACK_DELETE_EXTENT, ROLLBACK_ADD_SPACE, ROLLBACK_SUBTRACT_SPACE }; typedef struct { enum rollback_type type; void* ptr; LIST_ENTRY list_entry; } rollback_item; typedef struct { ANSI_STRING name; ANSI_STRING value; UCHAR flags; LIST_ENTRY list_entry; } ea_item; static const char lxuid[] = "$LXUID"; static const char lxgid[] = "$LXGID"; static const char lxmod[] = "$LXMOD"; static const char lxdev[] = "$LXDEV"; // in treefuncs.c NTSTATUS find_item(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _Out_ traverse_ptr* tp, _In_ const KEY* searchkey, _In_ bool ignore, _In_opt_ PIRP Irp) __attribute__((nonnull(1,2,3,4))); NTSTATUS find_item_to_level(device_extension* Vcb, root* r, traverse_ptr* tp, const KEY* searchkey, bool ignore, uint8_t level, PIRP Irp) __attribute__((nonnull(1,2,3,4))); bool find_next_item(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, const traverse_ptr* tp, traverse_ptr* next_tp, bool ignore, PIRP Irp) __attribute__((nonnull(1,2,3))); bool find_prev_item(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, const traverse_ptr* tp, traverse_ptr* prev_tp, PIRP Irp) __attribute__((nonnull(1,2,3))); void free_trees(device_extension* Vcb) __attribute__((nonnull(1))); NTSTATUS insert_tree_item(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _In_ uint64_t obj_id, _In_ uint8_t obj_type, _In_ uint64_t offset, _In_reads_bytes_opt_(size) _When_(return >= 0, __drv_aliasesMem) void* data, _In_ uint16_t size, _Out_opt_ traverse_ptr* ptp, _In_opt_ PIRP Irp) __attribute__((nonnull(1,2))); NTSTATUS delete_tree_item(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _Inout_ traverse_ptr* tp) __attribute__((nonnull(1,2))); void free_tree(tree* t) __attribute__((nonnull(1))); NTSTATUS load_tree(device_extension* Vcb, uint64_t addr, uint8_t* buf, root* r, tree** pt) __attribute__((nonnull(1,3,4,5))); NTSTATUS do_load_tree(device_extension* Vcb, tree_holder* th, root* r, tree* t, tree_data* td, PIRP Irp) __attribute__((nonnull(1,2,3))); void clear_rollback(LIST_ENTRY* rollback) __attribute__((nonnull(1))); void do_rollback(device_extension* Vcb, LIST_ENTRY* rollback) __attribute__((nonnull(1,2))); void free_trees_root(device_extension* Vcb, root* r) __attribute__((nonnull(1,2))); void add_rollback(_In_ LIST_ENTRY* rollback, _In_ enum rollback_type type, _In_ __drv_aliasesMem void* ptr) __attribute__((nonnull(1,3))); NTSTATUS commit_batch_list(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, LIST_ENTRY* batchlist, PIRP Irp) __attribute__((nonnull(1,2))); void clear_batch_list(device_extension* Vcb, LIST_ENTRY* batchlist) __attribute__((nonnull(1,2))); NTSTATUS skip_to_difference(device_extension* Vcb, traverse_ptr* tp, traverse_ptr* tp2, bool* ended1, bool* ended2) __attribute__((nonnull(1,2,3,4,5))); // in search.c NTSTATUS remove_drive_letter(PDEVICE_OBJECT mountmgr, PUNICODE_STRING devpath); _Function_class_(KSTART_ROUTINE) void __stdcall mountmgr_thread(_In_ void* context); _Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE) NTSTATUS __stdcall pnp_notification(PVOID NotificationStructure, PVOID Context); void disk_arrival(PUNICODE_STRING devpath); bool volume_arrival(PUNICODE_STRING devpath, bool fve_callback); void volume_removal(PUNICODE_STRING devpath); _Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE) NTSTATUS __stdcall volume_notification(PVOID NotificationStructure, PVOID Context); void remove_volume_child(_Inout_ _Requires_exclusive_lock_held_(_Curr_->child_lock) _Releases_exclusive_lock_(_Curr_->child_lock) _In_ volume_device_extension* vde, _In_ volume_child* vc, _In_ bool skip_dev); extern KSPIN_LOCK fve_data_lock; // in cache.c void init_cache(); extern CACHE_MANAGER_CALLBACKS cache_callbacks; // in write.c NTSTATUS write_file(device_extension* Vcb, PIRP Irp, bool wait, bool deferred_write) __attribute__((nonnull(1,2))); NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void* buf, ULONG* length, bool paging_io, bool no_cache, bool wait, bool deferred_write, bool write_irp, LIST_ENTRY* rollback) __attribute__((nonnull(1,2,4,5,11))); NTSTATUS truncate_file(fcb* fcb, uint64_t end, PIRP Irp, LIST_ENTRY* rollback) __attribute__((nonnull(1,4))); NTSTATUS extend_file(fcb* fcb, file_ref* fileref, uint64_t end, bool prealloc, PIRP Irp, LIST_ENTRY* rollback) __attribute__((nonnull(1,6))); NTSTATUS 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))); chunk* get_chunk_from_address(device_extension* Vcb, uint64_t address) __attribute__((nonnull(1))); NTSTATUS alloc_chunk(device_extension* Vcb, uint64_t flags, chunk** pc, bool full_size) __attribute__((nonnull(1,3))); NTSTATUS 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, _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))); NTSTATUS 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) __attribute__((nonnull(1,3))); void free_write_data_stripes(write_data_context* wtc) __attribute__((nonnull(1))); _Dispatch_type_(IRP_MJ_WRITE) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) __attribute__((nonnull(1,2))); _Requires_lock_held_(c->lock) _When_(return != 0, _Releases_lock_(c->lock)) bool 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, _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) __attribute__((nonnull(1,2,3,9))); NTSTATUS 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))); bool find_data_address_in_chunk(device_extension* Vcb, chunk* c, uint64_t length, uint64_t* address) __attribute__((nonnull(1, 2, 4))); void get_raid56_lock_range(chunk* c, uint64_t address, uint64_t length, uint64_t* lockaddr, uint64_t* locklen) __attribute__((nonnull(1,4,5))); NTSTATUS add_extent_to_fcb(_In_ fcb* fcb, _In_ uint64_t offset, _In_reads_bytes_(edsize) EXTENT_DATA* ed, _In_ uint16_t edsize, _In_ bool unique, _In_opt_ _When_(return >= 0, __drv_aliasesMem) void* csum, _In_ LIST_ENTRY* rollback) __attribute__((nonnull(1,3,7))); void add_extent(_In_ fcb* fcb, _In_ LIST_ENTRY* prevextle, _In_ __drv_aliasesMem extent* newext) __attribute__((nonnull(1,2,3))); // in dirctrl.c _Dispatch_type_(IRP_MJ_DIRECTORY_CONTROL) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_directory_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); ULONG get_reparse_tag(device_extension* Vcb, root* subvol, uint64_t inode, uint8_t type, ULONG atts, bool lxss, PIRP Irp); ULONG get_reparse_tag_fcb(fcb* fcb); // in security.c _Dispatch_type_(IRP_MJ_QUERY_SECURITY) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_query_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); _Dispatch_type_(IRP_MJ_SET_SECURITY) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_set_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); void fcb_get_sd(fcb* fcb, struct _fcb* parent, bool look_for_xattr, PIRP Irp); void add_user_mapping(WCHAR* sidstring, ULONG sidstringlength, uint32_t uid); void add_group_mapping(WCHAR* sidstring, ULONG sidstringlength, uint32_t gid); uint32_t sid_to_uid(PSID sid); NTSTATUS uid_to_sid(uint32_t uid, PSID* sid); NTSTATUS fcb_get_new_sd(fcb* fcb, file_ref* parfileref, ACCESS_STATE* as); void find_gid(struct _fcb* fcb, struct _fcb* parfcb, PSECURITY_SUBJECT_CONTEXT subjcont); // in fileinfo.c _Dispatch_type_(IRP_MJ_SET_INFORMATION) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_set_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); _Dispatch_type_(IRP_MJ_QUERY_INFORMATION) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_query_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); _Dispatch_type_(IRP_MJ_QUERY_EA) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_query_ea(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); _Dispatch_type_(IRP_MJ_SET_EA) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_set_ea(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); bool has_open_children(file_ref* fileref); NTSTATUS stream_set_end_of_file_information(device_extension* Vcb, uint16_t end, fcb* fcb, file_ref* fileref, bool advance_only); NTSTATUS fileref_get_filename(file_ref* fileref, PUNICODE_STRING fn, USHORT* name_offset, ULONG* preqlen); void insert_dir_child_into_hash_lists(fcb* fcb, dir_child* dc); void remove_dir_child_from_hash_lists(fcb* fcb, dir_child* dc); void add_fcb_to_subvol(_In_ _Requires_exclusive_lock_held_(_Curr_->Vcb->fcb_lock) fcb* fcb); void remove_fcb_from_subvol(_In_ _Requires_exclusive_lock_held_(_Curr_->Vcb->fcb_lock) fcb* fcb); // in reparse.c NTSTATUS get_reparse_point(PFILE_OBJECT FileObject, void* buffer, DWORD buflen, ULONG_PTR* retlen); NTSTATUS set_reparse_point2(fcb* fcb, REPARSE_DATA_BUFFER* rdb, ULONG buflen, ccb* ccb, file_ref* fileref, PIRP Irp, LIST_ENTRY* rollback); NTSTATUS set_reparse_point(PIRP Irp); NTSTATUS delete_reparse_point(PIRP Irp); // in create.c _Dispatch_type_(IRP_MJ_CREATE) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); NTSTATUS open_fileref(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) _In_ device_extension* Vcb, _Out_ file_ref** pfr, _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, _In_ bool case_sensitive, _In_opt_ PIRP Irp); NTSTATUS open_fcb(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb, root* subvol, uint64_t inode, uint8_t type, PANSI_STRING utf8, bool always_add_hl, fcb* parent, fcb** pfcb, POOL_TYPE pooltype, PIRP Irp); NTSTATUS load_csum(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, void* csum, uint64_t start, uint64_t length, PIRP Irp); NTSTATUS load_dir_children(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, fcb* fcb, bool ignore_size, PIRP Irp); NTSTATUS add_dir_child(fcb* fcb, uint64_t inode, bool subvol, PANSI_STRING utf8, PUNICODE_STRING name, uint8_t type, dir_child** pdc); NTSTATUS open_fileref_child(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) _In_ device_extension* Vcb, _In_ file_ref* sf, _In_ PUNICODE_STRING name, _In_ bool case_sensitive, _In_ bool lastpart, _In_ bool streampart, _In_ POOL_TYPE pooltype, _Out_ file_ref** psf2, _In_opt_ PIRP Irp); fcb* create_fcb(device_extension* Vcb, POOL_TYPE pool_type); NTSTATUS find_file_in_dir(PUNICODE_STRING filename, fcb* fcb, root** subvol, uint64_t* inode, dir_child** pdc, bool case_sensitive); uint32_t inherit_mode(fcb* parfcb, bool is_dir); file_ref* create_fileref(device_extension* Vcb); NTSTATUS open_fileref_by_inode(_Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb, root* subvol, uint64_t inode, file_ref** pfr, PIRP Irp); // in fsctl.c NTSTATUS fsctl_request(PDEVICE_OBJECT DeviceObject, PIRP* Pirp, uint32_t type); void do_unlock_volume(device_extension* Vcb); void trim_whole_device(device* dev); void flush_subvol_fcbs(root* subvol); bool fcb_is_inline(fcb* fcb); NTSTATUS dismount_volume(device_extension* Vcb, bool shutdown, PIRP Irp); // in flushthread.c _Function_class_(KSTART_ROUTINE) void __stdcall flush_thread(void* context); NTSTATUS do_write(device_extension* Vcb, PIRP Irp); NTSTATUS get_tree_new_address(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback); NTSTATUS flush_fcb(fcb* fcb, bool cache, LIST_ENTRY* batchlist, PIRP Irp); NTSTATUS write_data_phys(_In_ PDEVICE_OBJECT device, _In_ PFILE_OBJECT fileobj, _In_ uint64_t address, _In_reads_bytes_(length) void* data, _In_ uint32_t length); bool is_tree_unique(device_extension* Vcb, tree* t, PIRP Irp); NTSTATUS do_tree_writes(device_extension* Vcb, LIST_ENTRY* tree_writes, bool no_free); void add_checksum_entry(device_extension* Vcb, uint64_t address, ULONG length, void* csum, PIRP Irp); bool find_metadata_address_in_chunk(device_extension* Vcb, chunk* c, uint64_t* address); void add_trim_entry_avoid_sb(device_extension* Vcb, device* dev, uint64_t address, uint64_t size); NTSTATUS flush_partial_stripe(device_extension* Vcb, chunk* c, partial_stripe* ps); NTSTATUS update_dev_item(device_extension* Vcb, device* device, PIRP Irp); void calc_tree_checksum(device_extension* Vcb, tree_header* th); // in read.c _Dispatch_type_(IRP_MJ_READ) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_read(PDEVICE_OBJECT DeviceObject, PIRP Irp); NTSTATUS 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, _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, _In_ ULONG priority); NTSTATUS read_file(fcb* fcb, uint8_t* data, uint64_t start, uint64_t length, ULONG* pbr, PIRP Irp) __attribute__((nonnull(1, 2))); NTSTATUS read_stream(fcb* fcb, uint8_t* data, uint64_t start, ULONG length, ULONG* pbr) __attribute__((nonnull(1, 2))); NTSTATUS do_read(PIRP Irp, bool wait, ULONG* bytes_read); NTSTATUS check_csum(device_extension* Vcb, uint8_t* data, uint32_t sectors, void* csum); void raid6_recover2(uint8_t* sectors, uint16_t num_stripes, ULONG sector_size, uint16_t missing1, uint16_t missing2, uint8_t* out); void get_tree_checksum(device_extension* Vcb, tree_header* th, void* csum); bool check_tree_checksum(device_extension* Vcb, tree_header* th); void get_sector_csum(device_extension* Vcb, void* buf, void* csum); bool check_sector_csum(device_extension* Vcb, void* buf, void* csum); // in pnp.c _Dispatch_type_(IRP_MJ_PNP) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_pnp(PDEVICE_OBJECT DeviceObject, PIRP Irp); NTSTATUS pnp_surprise_removal(PDEVICE_OBJECT DeviceObject, PIRP Irp); NTSTATUS pnp_query_remove_device(PDEVICE_OBJECT DeviceObject, PIRP Irp); // in free-space.c NTSTATUS load_cache_chunk(device_extension* Vcb, chunk* c, PIRP Irp); NTSTATUS clear_free_space_cache(device_extension* Vcb, LIST_ENTRY* batchlist, PIRP Irp); NTSTATUS allocate_cache(device_extension* Vcb, bool* changed, PIRP Irp, LIST_ENTRY* rollback); NTSTATUS update_chunk_caches(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback); NTSTATUS update_chunk_caches_tree(device_extension* Vcb, PIRP Irp); NTSTATUS add_space_entry(LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t offset, uint64_t size); void space_list_add(chunk* c, uint64_t address, uint64_t length, LIST_ENTRY* rollback); void space_list_add2(LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t address, uint64_t length, chunk* c, LIST_ENTRY* rollback); void space_list_subtract(chunk* c, uint64_t address, uint64_t length, LIST_ENTRY* rollback); void space_list_subtract2(LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t address, uint64_t length, chunk* c, LIST_ENTRY* rollback); void space_list_merge(LIST_ENTRY* spacelist, LIST_ENTRY* spacelist_size, LIST_ENTRY* deleting); NTSTATUS load_stored_free_space_cache(device_extension* Vcb, chunk* c, bool load_only, PIRP Irp); // in extent-tree.c NTSTATUS 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); NTSTATUS decrease_extent_refcount_data(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root, uint64_t inode, uint64_t offset, uint32_t refcount, bool superseded, PIRP Irp); NTSTATUS decrease_extent_refcount_tree(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root, uint8_t level, PIRP Irp); uint64_t get_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, PIRP Irp); bool is_extent_unique(device_extension* Vcb, uint64_t address, uint64_t size, PIRP Irp); NTSTATUS increase_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, uint8_t type, void* data, KEY* firstitem, uint8_t level, PIRP Irp); uint64_t get_extent_flags(device_extension* Vcb, uint64_t address, PIRP Irp); void update_extent_flags(device_extension* Vcb, uint64_t address, uint64_t flags, PIRP Irp); NTSTATUS 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, bool no_csum, bool superseded, PIRP Irp); void 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); uint64_t find_extent_shared_tree_refcount(device_extension* Vcb, uint64_t address, uint64_t parent, PIRP Irp); uint32_t find_extent_shared_data_refcount(device_extension* Vcb, uint64_t address, uint64_t parent, PIRP Irp); NTSTATUS decrease_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, uint8_t type, void* data, KEY* firstitem, uint8_t level, uint64_t parent, bool superseded, PIRP Irp); uint64_t get_extent_data_ref_hash2(uint64_t root, uint64_t objid, uint64_t offset); // in worker-thread.c NTSTATUS do_read_job(PIRP Irp); NTSTATUS do_write_job(device_extension* Vcb, PIRP Irp); bool add_thread_job(device_extension* Vcb, PIRP Irp); // in registry.c void read_registry(PUNICODE_STRING regpath, bool refresh); NTSTATUS registry_mark_volume_mounted(BTRFS_UUID* uuid); NTSTATUS registry_mark_volume_unmounted(BTRFS_UUID* uuid); NTSTATUS registry_load_volume_options(device_extension* Vcb); void watch_registry(HANDLE regh); // in compress.c NTSTATUS zlib_decompress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen); NTSTATUS lzo_decompress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, uint32_t inpageoff); NTSTATUS zstd_decompress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen); NTSTATUS write_compressed(fcb* fcb, uint64_t start_data, uint64_t end_data, void* data, PIRP Irp, LIST_ENTRY* rollback); NTSTATUS zlib_compress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, unsigned int level, unsigned int* space_left); NTSTATUS lzo_compress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, unsigned int* space_left); NTSTATUS zstd_compress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, uint32_t level, unsigned int* space_left); // in galois.c void galois_double(uint8_t* data, uint32_t len); void galois_divpower(uint8_t* data, uint8_t div, uint32_t readlen); uint8_t gpow2(uint8_t e); uint8_t gmul(uint8_t a, uint8_t b); uint8_t gdiv(uint8_t a, uint8_t b); // in devctrl.c _Dispatch_type_(IRP_MJ_DEVICE_CONTROL) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_device_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); // in calcthread.c _Function_class_(KSTART_ROUTINE) void __stdcall calc_thread(void* context); void do_calc_job(device_extension* Vcb, uint8_t* data, uint32_t sectors, void* csum); NTSTATUS add_calc_job_decomp(device_extension* Vcb, uint8_t compression, void* in, unsigned int inlen, void* out, unsigned int outlen, unsigned int off, calc_job** pcj); NTSTATUS add_calc_job_comp(device_extension* Vcb, uint8_t compression, void* in, unsigned int inlen, void* out, unsigned int outlen, calc_job** pcj); void calc_thread_main(device_extension* Vcb, calc_job* cj); // in balance.c NTSTATUS start_balance(device_extension* Vcb, void* data, ULONG length, KPROCESSOR_MODE processor_mode); NTSTATUS query_balance(device_extension* Vcb, void* data, ULONG length); NTSTATUS pause_balance(device_extension* Vcb, KPROCESSOR_MODE processor_mode); NTSTATUS resume_balance(device_extension* Vcb, KPROCESSOR_MODE processor_mode); NTSTATUS stop_balance(device_extension* Vcb, KPROCESSOR_MODE processor_mode); NTSTATUS look_for_balance_item(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb); NTSTATUS remove_device(device_extension* Vcb, void* data, ULONG length, KPROCESSOR_MODE processor_mode); _Function_class_(KSTART_ROUTINE) void __stdcall balance_thread(void* context); // in volume.c NTSTATUS vol_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); NTSTATUS vol_close(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); NTSTATUS vol_read(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); NTSTATUS vol_write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); NTSTATUS vol_device_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); void add_volume_device(superblock* sb, PUNICODE_STRING devpath, uint64_t length, ULONG disk_num, ULONG part_num); NTSTATUS mountmgr_add_drive_letter(PDEVICE_OBJECT mountmgr, PUNICODE_STRING devpath); _Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE) NTSTATUS __stdcall pnp_removal(PVOID NotificationStructure, PVOID Context); void free_vol(volume_device_extension* vde); // in scrub.c NTSTATUS start_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode); NTSTATUS query_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode, void* data, ULONG length); NTSTATUS pause_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode); NTSTATUS resume_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode); NTSTATUS stop_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode); // in send.c NTSTATUS send_subvol(device_extension* Vcb, void* data, ULONG datalen, PFILE_OBJECT FileObject, PIRP Irp); NTSTATUS read_send_buffer(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, ULONG_PTR* retlen, KPROCESSOR_MODE processor_mode); // in fsrtl.c NTSTATUS __stdcall compat_FsRtlValidateReparsePointBuffer(IN ULONG BufferLength, IN PREPARSE_DATA_BUFFER ReparseBuffer); // in boot.c void check_system_root(); void boot_add_device(DEVICE_OBJECT* pdo); extern BTRFS_UUID boot_uuid; // based on function in sys/sysmacros.h #define makedev(major, minor) (((minor) & 0xFF) | (((major) & 0xFFF) << 8) | (((uint64_t)((minor) & ~0xFF)) << 12) | (((uint64_t)((major) & ~0xFFF)) << 32)) // not in mingw yet #ifndef _MSC_VER typedef struct { FSRTL_COMMON_FCB_HEADER Header; PFAST_MUTEX FastMutex; LIST_ENTRY FilterContexts; EX_PUSH_LOCK PushLock; PVOID* FileContextSupportPointer; union { OPLOCK Oplock; PVOID ReservedForRemote; }; PVOID ReservedContext; } FSRTL_ADVANCED_FCB_HEADER_NEW; #define FSRTL_FCB_HEADER_V2 2 #else #define FSRTL_ADVANCED_FCB_HEADER_NEW FSRTL_ADVANCED_FCB_HEADER #endif static __inline POPLOCK fcb_oplock(fcb* fcb) { if (fcb->Header.Version >= FSRTL_FCB_HEADER_V2) return &((FSRTL_ADVANCED_FCB_HEADER_NEW*)&fcb->Header)->Oplock; else return &fcb->oplock; } static __inline FAST_IO_POSSIBLE fast_io_possible(fcb* fcb) { if (!FsRtlOplockIsFastIoPossible(fcb_oplock(fcb))) return FastIoIsNotPossible; if (!FsRtlAreThereCurrentFileLocks(&fcb->lock) && !fcb->Vcb->readonly) return FastIoIsPossible; return FastIoIsQuestionable; } static __inline void print_open_trees(device_extension* Vcb) { LIST_ENTRY* le = Vcb->trees.Flink; while (le != &Vcb->trees) { tree* t = CONTAINING_RECORD(le, tree, list_entry); tree_data* td = CONTAINING_RECORD(t->itemlist.Flink, tree_data, list_entry); ERR("tree %p: root %I64x, level %u, first key (%I64x,%x,%I64x)\n", t, t->root->id, t->header.level, td->key.obj_id, td->key.obj_type, td->key.offset); le = le->Flink; } } static __inline bool write_fcb_compressed(fcb* fcb) { if (fcb->inode_item.flags & BTRFS_INODE_NODATACOW) return false; // make sure we don't accidentally write the cache inodes or pagefile compressed if (fcb->subvol->id == BTRFS_ROOT_ROOT || fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE) return false; if (fcb->Vcb->options.compress_force) return true; if (fcb->inode_item.flags & BTRFS_INODE_NOCOMPRESS) return false; if (fcb->inode_item.flags & BTRFS_INODE_COMPRESS || fcb->Vcb->options.compress) return true; return false; } #ifdef DEBUG_FCB_REFCOUNTS #ifdef DEBUG_LONG_MESSAGES #define increase_fileref_refcount(fileref) {\ LONG rc = InterlockedIncrement(&fileref->refcount);\ MSG(funcname, __FILE__, __LINE__, "fileref %p: refcount now %i\n", 1, fileref, rc);\ } #else #define increase_fileref_refcount(fileref) {\ LONG rc = InterlockedIncrement(&fileref->refcount);\ MSG(funcname, "fileref %p: refcount now %i\n", 1, fileref, rc);\ } #endif #else #define increase_fileref_refcount(fileref) InterlockedIncrement(&fileref->refcount) #endif #ifdef _MSC_VER #define int3 __debugbreak() #else #define int3 asm("int3;") #endif #define hex_digit(c) ((c) <= 9) ? ((c) + '0') : ((c) - 10 + 'a') // FIXME - find a way to catch unfreed trees again // from sys/stat.h #define __S_IFMT 0170000 /* These bits determine file type. */ #define __S_IFDIR 0040000 /* Directory. */ #define __S_IFCHR 0020000 /* Character device. */ #define __S_IFBLK 0060000 /* Block device. */ #define __S_IFREG 0100000 /* Regular file. */ #define __S_IFIFO 0010000 /* FIFO. */ #define __S_IFLNK 0120000 /* Symbolic link. */ #define __S_IFSOCK 0140000 /* Socket. */ #define __S_ISTYPE(mode, mask) (((mode) & __S_IFMT) == (mask)) #ifndef S_ISDIR #define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR) #endif #ifndef S_IRUSR #define S_IRUSR 0000400 #endif #ifndef S_IWUSR #define S_IWUSR 0000200 #endif #ifndef S_IXUSR #define S_IXUSR 0000100 #endif #ifndef S_IRGRP #define S_IRGRP (S_IRUSR >> 3) #endif #ifndef S_IWGRP #define S_IWGRP (S_IWUSR >> 3) #endif #ifndef S_IXGRP #define S_IXGRP (S_IXUSR >> 3) #endif #ifndef S_IROTH #define S_IROTH (S_IRGRP >> 3) #endif #ifndef S_IWOTH #define S_IWOTH (S_IWGRP >> 3) #endif #ifndef S_IXOTH #define S_IXOTH (S_IXGRP >> 3) #endif #ifndef S_ISUID #define S_ISUID 0004000 #endif #ifndef S_ISGID #define S_ISGID 0002000 #endif #ifndef S_ISVTX #define S_ISVTX 0001000 #endif // based on functions in sys/sysmacros.h #define major(rdev) ((((rdev) >> 8) & 0xFFF) | ((uint32_t)((rdev) >> 32) & ~0xFFF)) #define minor(rdev) (((rdev) & 0xFF) | ((uint32_t)((rdev) >> 12) & ~0xFF)) static __inline uint64_t fcb_alloc_size(fcb* fcb) { if (S_ISDIR(fcb->inode_item.st_mode)) return 0; else if (fcb->atts & FILE_ATTRIBUTE_SPARSE_FILE) return fcb->inode_item.st_blocks; else return sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size); } typedef BOOLEAN (__stdcall *tPsIsDiskCountersEnabled)(); typedef VOID (__stdcall *tPsUpdateDiskCounters)(PEPROCESS Process, ULONG64 BytesRead, ULONG64 BytesWritten, ULONG ReadOperationCount, ULONG WriteOperationCount, ULONG FlushOperationCount); typedef BOOLEAN (__stdcall *tCcCopyWriteEx)(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, ULONG Length, BOOLEAN Wait, PVOID Buffer, PETHREAD IoIssuerThread); typedef BOOLEAN (__stdcall *tCcCopyReadEx)(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, ULONG Length, BOOLEAN Wait, PVOID Buffer, PIO_STATUS_BLOCK IoStatus, PETHREAD IoIssuerThread); #ifndef CC_ENABLE_DISK_IO_ACCOUNTING #define CC_ENABLE_DISK_IO_ACCOUNTING 0x00000010 #endif typedef VOID (__stdcall *tCcSetAdditionalCacheAttributesEx)(PFILE_OBJECT FileObject, ULONG Flags); typedef VOID (__stdcall *tFsRtlUpdateDiskCounters)(ULONG64 BytesRead, ULONG64 BytesWritten); typedef NTSTATUS (__stdcall *tIoUnregisterPlugPlayNotificationEx)(PVOID NotificationEntry); typedef NTSTATUS (__stdcall *tFsRtlGetEcpListFromIrp)(PIRP Irp, PECP_LIST* EcpList); typedef NTSTATUS (__stdcall *tFsRtlGetNextExtraCreateParameter)(PECP_LIST EcpList, PVOID CurrentEcpContext, LPGUID NextEcpType, PVOID* NextEcpContext, ULONG* NextEcpContextSize); typedef NTSTATUS (__stdcall *tFsRtlValidateReparsePointBuffer)(ULONG BufferLength, PREPARSE_DATA_BUFFER ReparseBuffer); typedef BOOLEAN (__stdcall *tFsRtlCheckLockForOplockRequest)(PFILE_LOCK FileLock, PLARGE_INTEGER AllocationSize); typedef BOOLEAN (__stdcall *tFsRtlAreThereCurrentOrInProgressFileLocks)(PFILE_LOCK FileLock); #ifndef _MSC_VER PEPROCESS __stdcall PsGetThreadProcess(_In_ PETHREAD Thread); // not in mingw #endif // not in DDK headers - taken from winternl.h typedef struct _LDR_DATA_TABLE_ENTRY { PVOID Reserved1[2]; LIST_ENTRY InMemoryOrderLinks; PVOID Reserved2[2]; PVOID DllBase; PVOID Reserved3[2]; UNICODE_STRING FullDllName; BYTE Reserved4[8]; PVOID Reserved5[3]; union { ULONG CheckSum; PVOID Reserved6; }; ULONG TimeDateStamp; } LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY; typedef struct _PEB_LDR_DATA { BYTE Reserved1[8]; PVOID Reserved2[3]; LIST_ENTRY InMemoryOrderModuleList; } PEB_LDR_DATA,*PPEB_LDR_DATA; typedef struct _RTL_USER_PROCESS_PARAMETERS { BYTE Reserved1[16]; PVOID Reserved2[10]; UNICODE_STRING ImagePathName; UNICODE_STRING CommandLine; } RTL_USER_PROCESS_PARAMETERS,*PRTL_USER_PROCESS_PARAMETERS; typedef VOID (NTAPI *PPS_POST_PROCESS_INIT_ROUTINE)(VOID); typedef struct _PEB { BYTE Reserved1[2]; BYTE BeingDebugged; BYTE Reserved2[1]; PVOID Reserved3[2]; PPEB_LDR_DATA Ldr; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; BYTE Reserved4[104]; PVOID Reserved5[52]; PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; BYTE Reserved6[128]; PVOID Reserved7[1]; ULONG SessionId; } PEB,*PPEB; #ifdef _MSC_VER __kernel_entry NTSTATUS NTAPI ZwQueryInformationProcess( IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength OPTIONAL ); #endif ================================================ FILE: src/btrfsioctl.h ================================================ // No copyright claimed in this file - do what you want with it. #pragma once #include "btrfs.h" #define FSCTL_BTRFS_GET_FILE_IDS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x829, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_CREATE_SUBVOL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82a, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_CREATE_SNAPSHOT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82b, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_GET_INODE_INFO CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82c, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_SET_INODE_INFO CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82d, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_GET_DEVICES CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82e, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_GET_USAGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82f, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_START_BALANCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x830, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_QUERY_BALANCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x831, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_PAUSE_BALANCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x832, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_RESUME_BALANCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x833, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_STOP_BALANCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x834, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_ADD_DEVICE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x835, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_REMOVE_DEVICE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x836, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define IOCTL_BTRFS_QUERY_FILESYSTEMS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x837, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_GET_UUID CTL_CODE(FILE_DEVICE_UNKNOWN, 0x838, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_START_SCRUB CTL_CODE(FILE_DEVICE_UNKNOWN, 0x839, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_QUERY_SCRUB CTL_CODE(FILE_DEVICE_UNKNOWN, 0x83a, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_PAUSE_SCRUB CTL_CODE(FILE_DEVICE_UNKNOWN, 0x83b, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_RESUME_SCRUB CTL_CODE(FILE_DEVICE_UNKNOWN, 0x83c, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_STOP_SCRUB CTL_CODE(FILE_DEVICE_UNKNOWN, 0x83d, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define IOCTL_BTRFS_PROBE_VOLUME CTL_CODE(FILE_DEVICE_UNKNOWN, 0x83e, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_RESET_STATS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x83f, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_MKNOD CTL_CODE(FILE_DEVICE_UNKNOWN, 0x840, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_RECEIVED_SUBVOL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x841, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_GET_XATTRS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x842, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_BTRFS_SET_XATTR CTL_CODE(FILE_DEVICE_UNKNOWN, 0x843, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_RESERVE_SUBVOL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x844, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_FIND_SUBVOL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x845, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_BTRFS_SEND_SUBVOL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x846, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_BTRFS_READ_SEND_BUFFER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x847, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define FSCTL_BTRFS_RESIZE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x848, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define IOCTL_BTRFS_UNLOAD CTL_CODE(FILE_DEVICE_UNKNOWN, 0x849, METHOD_NEITHER, FILE_ANY_ACCESS) #define FSCTL_BTRFS_GET_CSUM_INFO CTL_CODE(FILE_DEVICE_UNKNOWN, 0x84a, METHOD_BUFFERED, FILE_READ_ACCESS) typedef struct { uint64_t subvol; uint64_t inode; BOOL top; } btrfs_get_file_ids; typedef struct { HANDLE subvol; BOOL readonly; BOOL posix; uint16_t namelen; WCHAR name[1]; } btrfs_create_snapshot; typedef struct { void* POINTER_32 subvol; BOOL readonly; BOOL posix; uint16_t namelen; WCHAR name[1]; } btrfs_create_snapshot32; #define BTRFS_COMPRESSION_ANY 0 #define BTRFS_COMPRESSION_ZLIB 1 #define BTRFS_COMPRESSION_LZO 2 #define BTRFS_COMPRESSION_ZSTD 3 typedef struct { uint64_t subvol; uint64_t inode; BOOL top; uint8_t type; uint32_t st_uid; uint32_t st_gid; uint32_t st_mode; uint64_t st_rdev; uint64_t flags; uint32_t inline_length; uint64_t disk_size_uncompressed; uint64_t disk_size_zlib; uint64_t disk_size_lzo; uint8_t compression_type; uint64_t disk_size_zstd; uint64_t sparse_size; uint32_t num_extents; } btrfs_inode_info; typedef struct { uint64_t flags; BOOL flags_changed; uint32_t st_uid; BOOL uid_changed; uint32_t st_gid; BOOL gid_changed; uint32_t st_mode; BOOL mode_changed; uint8_t compression_type; BOOL compression_type_changed; } btrfs_set_inode_info; typedef struct { uint32_t next_entry; uint64_t dev_id; uint64_t size; uint64_t max_size; BOOL readonly; BOOL missing; ULONG device_number; ULONG partition_number; uint64_t stats[5]; USHORT namelen; WCHAR name[1]; } btrfs_device; typedef struct { uint64_t dev_id; uint64_t alloc; } btrfs_usage_device; typedef struct { uint32_t next_entry; uint64_t type; uint64_t size; uint64_t used; uint64_t num_devices; btrfs_usage_device devices[1]; } btrfs_usage; #define BTRFS_BALANCE_OPTS_ENABLED 0x001 #define BTRFS_BALANCE_OPTS_PROFILES 0x002 #define BTRFS_BALANCE_OPTS_DEVID 0x004 #define BTRFS_BALANCE_OPTS_DRANGE 0x008 #define BTRFS_BALANCE_OPTS_VRANGE 0x010 #define BTRFS_BALANCE_OPTS_LIMIT 0x020 #define BTRFS_BALANCE_OPTS_STRIPES 0x040 #define BTRFS_BALANCE_OPTS_USAGE 0x080 #define BTRFS_BALANCE_OPTS_CONVERT 0x100 #define BTRFS_BALANCE_OPTS_SOFT 0x200 #define BLOCK_FLAG_SINGLE 0x1000000000000 // only used in balance typedef struct { uint64_t flags; uint64_t profiles; uint64_t devid; uint64_t drange_start; uint64_t drange_end; uint64_t vrange_start; uint64_t vrange_end; uint64_t limit_start; uint64_t limit_end; uint16_t stripes_start; uint16_t stripes_end; uint8_t usage_start; uint8_t usage_end; uint64_t convert; } btrfs_balance_opts; #define BTRFS_BALANCE_STOPPED 0 #define BTRFS_BALANCE_RUNNING 1 #define BTRFS_BALANCE_PAUSED 2 #define BTRFS_BALANCE_REMOVAL 4 #define BTRFS_BALANCE_ERROR 8 #define BTRFS_BALANCE_SHRINKING 16 typedef struct { uint32_t status; uint64_t chunks_left; uint64_t total_chunks; NTSTATUS error; btrfs_balance_opts data_opts; btrfs_balance_opts metadata_opts; btrfs_balance_opts system_opts; } btrfs_query_balance; typedef struct { btrfs_balance_opts opts[3]; } btrfs_start_balance; typedef struct { uint8_t uuid[16]; BOOL missing; USHORT name_length; WCHAR name[1]; } btrfs_filesystem_device; typedef struct { uint32_t next_entry; uint8_t uuid[16]; uint32_t num_devices; btrfs_filesystem_device device; } btrfs_filesystem; #define BTRFS_SCRUB_STOPPED 0 #define BTRFS_SCRUB_RUNNING 1 #define BTRFS_SCRUB_PAUSED 2 typedef struct { uint32_t next_entry; uint64_t address; uint64_t device; BOOL recovered; BOOL is_metadata; BOOL parity; union { struct { uint64_t subvol; uint64_t offset; uint16_t filename_length; WCHAR filename[1]; } data; struct { uint64_t root; uint8_t level; KEY firstitem; } metadata; }; } btrfs_scrub_error; typedef struct { uint32_t status; LARGE_INTEGER start_time; LARGE_INTEGER finish_time; uint64_t chunks_left; uint64_t total_chunks; uint64_t data_scrubbed; uint64_t duration; NTSTATUS error; uint32_t num_errors; btrfs_scrub_error errors; } btrfs_query_scrub; typedef struct { uint64_t inode; uint8_t type; uint64_t st_rdev; uint16_t namelen; WCHAR name[1]; } btrfs_mknod; typedef struct { uint64_t generation; BTRFS_UUID uuid; } btrfs_received_subvol; typedef struct { USHORT namelen; USHORT valuelen; char data[1]; } btrfs_set_xattr; typedef struct { BOOL readonly; BOOL posix; USHORT namelen; WCHAR name[1]; } btrfs_create_subvol; typedef struct { BTRFS_UUID uuid; uint64_t ctransid; } btrfs_find_subvol; typedef struct { HANDLE parent; ULONG num_clones; HANDLE clones[1]; } btrfs_send_subvol; typedef struct { void* POINTER_32 parent; ULONG num_clones; void* POINTER_32 clones[1]; } btrfs_send_subvol32; typedef struct { uint64_t device; uint64_t size; } btrfs_resize; typedef struct { uint8_t csum_type; uint8_t csum_length; uint64_t num_sectors; uint8_t data[1]; } btrfs_csum_info; ================================================ FILE: src/cache.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" CACHE_MANAGER_CALLBACKS cache_callbacks; static BOOLEAN __stdcall acquire_for_lazy_write(PVOID Context, BOOLEAN Wait) { PFILE_OBJECT FileObject = Context; fcb* fcb = FileObject->FsContext; TRACE("(%p, %u)\n", Context, Wait); if (!ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, Wait)) return false; if (!ExAcquireResourceExclusiveLite(fcb->Header.Resource, Wait)) { ExReleaseResourceLite(&fcb->Vcb->tree_lock); return false; } fcb->lazy_writer_thread = KeGetCurrentThread(); IoSetTopLevelIrp((PIRP)FSRTL_CACHE_TOP_LEVEL_IRP); return true; } static void __stdcall release_from_lazy_write(PVOID Context) { PFILE_OBJECT FileObject = Context; fcb* fcb = FileObject->FsContext; TRACE("(%p)\n", Context); fcb->lazy_writer_thread = NULL; ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&fcb->Vcb->tree_lock); if (IoGetTopLevelIrp() == (PIRP)FSRTL_CACHE_TOP_LEVEL_IRP) IoSetTopLevelIrp(NULL); } static BOOLEAN __stdcall acquire_for_read_ahead(PVOID Context, BOOLEAN Wait) { PFILE_OBJECT FileObject = Context; fcb* fcb = FileObject->FsContext; TRACE("(%p, %u)\n", Context, Wait); if (!ExAcquireResourceSharedLite(fcb->Header.Resource, Wait)) return false; IoSetTopLevelIrp((PIRP)FSRTL_CACHE_TOP_LEVEL_IRP); return true; } static void __stdcall release_from_read_ahead(PVOID Context) { PFILE_OBJECT FileObject = Context; fcb* fcb = FileObject->FsContext; TRACE("(%p)\n", Context); ExReleaseResourceLite(fcb->Header.Resource); if (IoGetTopLevelIrp() == (PIRP)FSRTL_CACHE_TOP_LEVEL_IRP) IoSetTopLevelIrp(NULL); } void init_cache() { cache_callbacks.AcquireForLazyWrite = acquire_for_lazy_write; cache_callbacks.ReleaseFromLazyWrite = release_from_lazy_write; cache_callbacks.AcquireForReadAhead = acquire_for_read_ahead; cache_callbacks.ReleaseFromReadAhead = release_from_read_ahead; } ================================================ FILE: src/calcthread.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include "zstd/lib/common/xxhash.h" #include "crc32c.h" void calc_thread_main(device_extension* Vcb, calc_job* cj) { while (true) { KIRQL irql; calc_job* cj2; uint8_t* src; void* dest; bool last_one = false; KeAcquireSpinLock(&Vcb->calcthreads.spinlock, &irql); if (cj && cj->not_started == 0) { KeReleaseSpinLock(&Vcb->calcthreads.spinlock, irql); break; } if (cj) cj2 = cj; else { if (IsListEmpty(&Vcb->calcthreads.job_list)) { KeReleaseSpinLock(&Vcb->calcthreads.spinlock, irql); break; } cj2 = CONTAINING_RECORD(Vcb->calcthreads.job_list.Flink, calc_job, list_entry); } src = cj2->in; dest = cj2->out; switch (cj2->type) { case calc_thread_crc32c: case calc_thread_xxhash: case calc_thread_sha256: case calc_thread_blake2: cj2->in = (uint8_t*)cj2->in + Vcb->superblock.sector_size; cj2->out = (uint8_t*)cj2->out + Vcb->csum_size; break; default: break; } cj2->not_started--; if (cj2->not_started == 0) { RemoveEntryList(&cj2->list_entry); last_one = true; } KeReleaseSpinLock(&Vcb->calcthreads.spinlock, irql); switch (cj2->type) { case calc_thread_crc32c: *(uint32_t*)dest = ~calc_crc32c(0xffffffff, src, Vcb->superblock.sector_size); break; case calc_thread_xxhash: *(uint64_t*)dest = XXH64(src, Vcb->superblock.sector_size, 0); break; case calc_thread_sha256: calc_sha256(dest, src, Vcb->superblock.sector_size); break; case calc_thread_blake2: blake2b(dest, BLAKE2_HASH_SIZE, src, Vcb->superblock.sector_size); break; case calc_thread_decomp_zlib: cj2->Status = zlib_decompress(src, cj2->inlen, dest, cj2->outlen); if (!NT_SUCCESS(cj2->Status)) ERR("zlib_decompress returned %08lx\n", cj2->Status); break; case calc_thread_decomp_lzo: cj2->Status = lzo_decompress(src, cj2->inlen, dest, cj2->outlen, cj2->off); if (!NT_SUCCESS(cj2->Status)) ERR("lzo_decompress returned %08lx\n", cj2->Status); break; case calc_thread_decomp_zstd: cj2->Status = zstd_decompress(src, cj2->inlen, dest, cj2->outlen); if (!NT_SUCCESS(cj2->Status)) ERR("zstd_decompress returned %08lx\n", cj2->Status); break; case calc_thread_comp_zlib: cj2->Status = zlib_compress(src, cj2->inlen, dest, cj2->outlen, Vcb->options.zlib_level, &cj2->space_left); if (!NT_SUCCESS(cj2->Status)) ERR("zlib_compress returned %08lx\n", cj2->Status); break; case calc_thread_comp_lzo: cj2->Status = lzo_compress(src, cj2->inlen, dest, cj2->outlen, &cj2->space_left); if (!NT_SUCCESS(cj2->Status)) ERR("lzo_compress returned %08lx\n", cj2->Status); break; case calc_thread_comp_zstd: cj2->Status = zstd_compress(src, cj2->inlen, dest, cj2->outlen, Vcb->options.zstd_level, &cj2->space_left); if (!NT_SUCCESS(cj2->Status)) ERR("zstd_compress returned %08lx\n", cj2->Status); break; } if (InterlockedDecrement(&cj2->left) == 0) KeSetEvent(&cj2->event, 0, false); if (last_one) break; } } void do_calc_job(device_extension* Vcb, uint8_t* data, uint32_t sectors, void* csum) { KIRQL irql; calc_job cj; cj.in = data; cj.out = csum; cj.left = cj.not_started = sectors; switch (Vcb->superblock.csum_type) { case CSUM_TYPE_CRC32C: cj.type = calc_thread_crc32c; break; case CSUM_TYPE_XXHASH: cj.type = calc_thread_xxhash; break; case CSUM_TYPE_SHA256: cj.type = calc_thread_sha256; break; case CSUM_TYPE_BLAKE2: cj.type = calc_thread_blake2; break; } KeInitializeEvent(&cj.event, NotificationEvent, false); KeAcquireSpinLock(&Vcb->calcthreads.spinlock, &irql); InsertTailList(&Vcb->calcthreads.job_list, &cj.list_entry); KeSetEvent(&Vcb->calcthreads.event, 0, false); KeClearEvent(&Vcb->calcthreads.event); KeReleaseSpinLock(&Vcb->calcthreads.spinlock, irql); calc_thread_main(Vcb, &cj); KeWaitForSingleObject(&cj.event, Executive, KernelMode, false, NULL); } NTSTATUS add_calc_job_decomp(device_extension* Vcb, uint8_t compression, void* in, unsigned int inlen, void* out, unsigned int outlen, unsigned int off, calc_job** pcj) { calc_job* cj; KIRQL irql; cj = ExAllocatePoolWithTag(NonPagedPool, sizeof(calc_job), ALLOC_TAG); if (!cj) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } cj->in = in; cj->inlen = inlen; cj->out = out; cj->outlen = outlen; cj->off = off; cj->left = cj->not_started = 1; cj->Status = STATUS_SUCCESS; switch (compression) { case BTRFS_COMPRESSION_ZLIB: cj->type = calc_thread_decomp_zlib; break; case BTRFS_COMPRESSION_LZO: cj->type = calc_thread_decomp_lzo; break; case BTRFS_COMPRESSION_ZSTD: cj->type = calc_thread_decomp_zstd; break; default: ERR("unexpected compression type %x\n", compression); ExFreePool(cj); return STATUS_NOT_SUPPORTED; } KeInitializeEvent(&cj->event, NotificationEvent, false); KeAcquireSpinLock(&Vcb->calcthreads.spinlock, &irql); InsertTailList(&Vcb->calcthreads.job_list, &cj->list_entry); KeSetEvent(&Vcb->calcthreads.event, 0, false); KeClearEvent(&Vcb->calcthreads.event); KeReleaseSpinLock(&Vcb->calcthreads.spinlock, irql); *pcj = cj; return STATUS_SUCCESS; } NTSTATUS add_calc_job_comp(device_extension* Vcb, uint8_t compression, void* in, unsigned int inlen, void* out, unsigned int outlen, calc_job** pcj) { calc_job* cj; KIRQL irql; cj = ExAllocatePoolWithTag(NonPagedPool, sizeof(calc_job), ALLOC_TAG); if (!cj) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } cj->in = in; cj->inlen = inlen; cj->out = out; cj->outlen = outlen; cj->left = cj->not_started = 1; cj->Status = STATUS_SUCCESS; switch (compression) { case BTRFS_COMPRESSION_ZLIB: cj->type = calc_thread_comp_zlib; break; case BTRFS_COMPRESSION_LZO: cj->type = calc_thread_comp_lzo; break; case BTRFS_COMPRESSION_ZSTD: cj->type = calc_thread_comp_zstd; break; default: ERR("unexpected compression type %x\n", compression); ExFreePool(cj); return STATUS_NOT_SUPPORTED; } KeInitializeEvent(&cj->event, NotificationEvent, false); KeAcquireSpinLock(&Vcb->calcthreads.spinlock, &irql); InsertTailList(&Vcb->calcthreads.job_list, &cj->list_entry); KeSetEvent(&Vcb->calcthreads.event, 0, false); KeClearEvent(&Vcb->calcthreads.event); KeReleaseSpinLock(&Vcb->calcthreads.spinlock, irql); *pcj = cj; return STATUS_SUCCESS; } _Function_class_(KSTART_ROUTINE) void __stdcall calc_thread(void* context) { drv_calc_thread* thread = context; device_extension* Vcb = thread->DeviceObject->DeviceExtension; ObReferenceObject(thread->DeviceObject); KeSetSystemAffinityThread((KAFFINITY)1 << thread->number); while (true) { KeWaitForSingleObject(&Vcb->calcthreads.event, Executive, KernelMode, false, NULL); calc_thread_main(Vcb, NULL); if (thread->quit) break; } ObDereferenceObject(thread->DeviceObject); KeSetEvent(&thread->finished, 0, false); PsTerminateSystemThread(STATUS_SUCCESS); } ================================================ FILE: src/compress.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * Copyright (c) Reimar Doeffinger 2006 * Copyright (c) Markus Oberhumer 1996 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ // Portions of the LZO decompression code here were cribbed from code in // libavcodec, also under the LGPL. Thank you, Reimar Doeffinger. // The LZO compression code comes from v0.22 of lzo, written way back in // 1996, and available here: // https://www.ibiblio.org/pub/historic-linux/ftp-archives/sunsite.unc.edu/Sep-29-1996/libs/lzo-0.22.tar.gz // Modern versions of lzo are licensed under the GPL, but the very oldest // versions are under the LGPL and hence okay to use here. #include "btrfs_drv.h" #include "zlib/zlib.h" #define ZSTD_STATIC_LINKING_ONLY #include "zstd/lib/zstd.h" #define LZO_PAGE_SIZE 4096 typedef struct { uint8_t* in; uint32_t inlen; uint32_t inpos; uint8_t* out; uint32_t outlen; uint32_t outpos; bool error; void* wrkmem; } lzo_stream; #define LZO1X_MEM_COMPRESS ((uint32_t) (16384L * sizeof(uint8_t*))) #define M1_MAX_OFFSET 0x0400 #define M2_MAX_OFFSET 0x0800 #define M3_MAX_OFFSET 0x4000 #define M4_MAX_OFFSET 0xbfff #define MX_MAX_OFFSET (M1_MAX_OFFSET + M2_MAX_OFFSET) #define M1_MARKER 0 #define M2_MARKER 64 #define M3_MARKER 32 #define M4_MARKER 16 #define _DV2(p, shift1, shift2) (((( (uint32_t)(p[2]) << shift1) ^ p[1]) << shift2) ^ p[0]) #define DVAL_NEXT(dv, p) dv ^= p[-1]; dv = (((dv) >> 5) ^ ((uint32_t)(p[2]) << (2*5))) #define _DV(p, shift) _DV2(p, shift, shift) #define DVAL_FIRST(dv, p) dv = _DV((p), 5) #define _DINDEX(dv, p) ((40799u * (dv)) >> 5) #define DINDEX(dv, p) (((_DINDEX(dv, p)) & 0x3fff) << 0) #define UPDATE_D(dict, cycle, dv, p) dict[DINDEX(dv, p)] = (p) #define UPDATE_I(dict, cycle, index, p) dict[index] = (p) #define LZO_CHECK_MPOS_NON_DET(m_pos, m_off, in, ip, max_offset) \ ((void*) m_pos < (void*) in || \ (m_off = (uint8_t*) ip - (uint8_t*) m_pos) <= 0 || \ m_off > max_offset) #define LZO_BYTE(x) ((unsigned char) (x)) #define ZSTD_ALLOC_TAG 0x6474737a // "zstd" // needs to be the same as Linux (fs/btrfs/zstd.c) #define ZSTD_BTRFS_MAX_WINDOWLOG 17 static void* zstd_malloc(void* opaque, size_t size); static void zstd_free(void* opaque, void* address); static const ZSTD_customMem zstd_mem = { .customAlloc = zstd_malloc, .customFree = zstd_free, .opaque = NULL }; static uint8_t lzo_nextbyte(lzo_stream* stream) { uint8_t c; if (stream->inpos >= stream->inlen) { stream->error = true; return 0; } c = stream->in[stream->inpos]; stream->inpos++; return c; } static int lzo_len(lzo_stream* stream, int byte, int mask) { int len = byte & mask; if (len == 0) { while (!(byte = lzo_nextbyte(stream))) { if (stream->error) return 0; len += 255; } len += mask + byte; } return len; } static void lzo_copy(lzo_stream* stream, int len) { if (stream->inpos + len > stream->inlen) { stream->error = true; return; } if (stream->outpos + len > stream->outlen) { stream->error = true; return; } do { stream->out[stream->outpos] = stream->in[stream->inpos]; stream->inpos++; stream->outpos++; len--; } while (len > 0); } static void lzo_copyback(lzo_stream* stream, uint32_t back, int len) { if (stream->outpos < back) { stream->error = true; return; } if (stream->outpos + len > stream->outlen) { stream->error = true; return; } do { stream->out[stream->outpos] = stream->out[stream->outpos - back]; stream->outpos++; len--; } while (len > 0); } static NTSTATUS do_lzo_decompress(lzo_stream* stream) { uint8_t byte; uint32_t len, back; bool backcopy = false; stream->error = false; byte = lzo_nextbyte(stream); if (stream->error) return STATUS_INTERNAL_ERROR; if (byte > 17) { lzo_copy(stream, min((uint8_t)(byte - 17), (uint32_t)(stream->outlen - stream->outpos))); if (stream->error) return STATUS_INTERNAL_ERROR; if (stream->outlen == stream->outpos) return STATUS_SUCCESS; byte = lzo_nextbyte(stream); if (stream->error) return STATUS_INTERNAL_ERROR; if (byte < 16) return STATUS_INTERNAL_ERROR; } while (1) { if (byte >> 4) { backcopy = true; if (byte >> 6) { len = (byte >> 5) - 1; back = (lzo_nextbyte(stream) << 3) + ((byte >> 2) & 7) + 1; if (stream->error) return STATUS_INTERNAL_ERROR; } else if (byte >> 5) { len = lzo_len(stream, byte, 31); if (stream->error) return STATUS_INTERNAL_ERROR; byte = lzo_nextbyte(stream); if (stream->error) return STATUS_INTERNAL_ERROR; back = (lzo_nextbyte(stream) << 6) + (byte >> 2) + 1; if (stream->error) return STATUS_INTERNAL_ERROR; } else { len = lzo_len(stream, byte, 7); if (stream->error) return STATUS_INTERNAL_ERROR; back = (1 << 14) + ((byte & 8) << 11); byte = lzo_nextbyte(stream); if (stream->error) return STATUS_INTERNAL_ERROR; back += (lzo_nextbyte(stream) << 6) + (byte >> 2); if (stream->error) return STATUS_INTERNAL_ERROR; if (back == (1 << 14)) { if (len != 1) return STATUS_INTERNAL_ERROR; break; } } } else if (backcopy) { len = 0; back = (lzo_nextbyte(stream) << 2) + (byte >> 2) + 1; if (stream->error) return STATUS_INTERNAL_ERROR; } else { len = lzo_len(stream, byte, 15); if (stream->error) return STATUS_INTERNAL_ERROR; lzo_copy(stream, min(len + 3, stream->outlen - stream->outpos)); if (stream->error) return STATUS_INTERNAL_ERROR; if (stream->outlen == stream->outpos) return STATUS_SUCCESS; byte = lzo_nextbyte(stream); if (stream->error) return STATUS_INTERNAL_ERROR; if (byte >> 4) continue; len = 1; back = (1 << 11) + (lzo_nextbyte(stream) << 2) + (byte >> 2) + 1; if (stream->error) return STATUS_INTERNAL_ERROR; break; } lzo_copyback(stream, back, min(len + 2, stream->outlen - stream->outpos)); if (stream->error) return STATUS_INTERNAL_ERROR; if (stream->outlen == stream->outpos) return STATUS_SUCCESS; len = byte & 3; if (len) { lzo_copy(stream, min(len, stream->outlen - stream->outpos)); if (stream->error) return STATUS_INTERNAL_ERROR; if (stream->outlen == stream->outpos) return STATUS_SUCCESS; } else backcopy = !backcopy; byte = lzo_nextbyte(stream); if (stream->error) return STATUS_INTERNAL_ERROR; } return STATUS_SUCCESS; } NTSTATUS lzo_decompress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, uint32_t inpageoff) { NTSTATUS Status; uint32_t partlen, inoff, outoff; lzo_stream stream; inoff = 0; outoff = 0; do { partlen = *(uint32_t*)&inbuf[inoff]; if (partlen + inoff > inlen) { ERR("overflow: %x + %x > %x\n", partlen, inoff, inlen); return STATUS_INTERNAL_ERROR; } inoff += sizeof(uint32_t); stream.in = &inbuf[inoff]; stream.inlen = partlen; stream.inpos = 0; stream.out = &outbuf[outoff]; stream.outlen = min(outlen, LZO_PAGE_SIZE); stream.outpos = 0; Status = do_lzo_decompress(&stream); if (!NT_SUCCESS(Status)) { ERR("do_lzo_decompress returned %08lx\n", Status); return Status; } if (stream.outpos < stream.outlen) RtlZeroMemory(&stream.out[stream.outpos], stream.outlen - stream.outpos); inoff += partlen; outoff += stream.outlen; if (LZO_PAGE_SIZE - ((inpageoff + inoff) % LZO_PAGE_SIZE) < sizeof(uint32_t)) inoff = ((((inpageoff + inoff) / LZO_PAGE_SIZE) + 1) * LZO_PAGE_SIZE) - inpageoff; outlen -= stream.outlen; } while (inoff < inlen && outlen > 0); return STATUS_SUCCESS; } static void* zlib_alloc(void* opaque, unsigned int items, unsigned int size) { UNUSED(opaque); return ExAllocatePoolWithTag(PagedPool, items * size, ALLOC_TAG_ZLIB); } static void zlib_free(void* opaque, void* ptr) { UNUSED(opaque); ExFreePool(ptr); } NTSTATUS zlib_compress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, unsigned int level, unsigned int* space_left) { z_stream c_stream; int ret; c_stream.zalloc = zlib_alloc; c_stream.zfree = zlib_free; c_stream.opaque = (voidpf)0; ret = deflateInit(&c_stream, level); if (ret != Z_OK) { ERR("deflateInit returned %i\n", ret); return STATUS_INTERNAL_ERROR; } c_stream.next_in = inbuf; c_stream.avail_in = inlen; c_stream.next_out = outbuf; c_stream.avail_out = outlen; do { ret = deflate(&c_stream, Z_FINISH); if (ret != Z_OK && ret != Z_STREAM_END) { ERR("deflate returned %i\n", ret); deflateEnd(&c_stream); return STATUS_INTERNAL_ERROR; } if (c_stream.avail_in == 0 || c_stream.avail_out == 0) break; } while (ret != Z_STREAM_END); deflateEnd(&c_stream); *space_left = c_stream.avail_in > 0 ? 0 : c_stream.avail_out; return STATUS_SUCCESS; } NTSTATUS zlib_decompress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen) { z_stream c_stream; int ret; c_stream.zalloc = zlib_alloc; c_stream.zfree = zlib_free; c_stream.opaque = (voidpf)0; ret = inflateInit(&c_stream); if (ret != Z_OK) { ERR("inflateInit returned %i\n", ret); return STATUS_INTERNAL_ERROR; } c_stream.next_in = inbuf; c_stream.avail_in = inlen; c_stream.next_out = outbuf; c_stream.avail_out = outlen; do { ret = inflate(&c_stream, Z_NO_FLUSH); if (ret != Z_OK && ret != Z_STREAM_END) { ERR("inflate returned %i\n", ret); inflateEnd(&c_stream); return STATUS_INTERNAL_ERROR; } if (c_stream.avail_out == 0) break; } while (ret != Z_STREAM_END); ret = inflateEnd(&c_stream); if (ret != Z_OK) { ERR("inflateEnd returned %i\n", ret); return STATUS_INTERNAL_ERROR; } // FIXME - if we're short, should we zero the end of outbuf so we don't leak information into userspace? return STATUS_SUCCESS; } static NTSTATUS lzo_do_compress(const uint8_t* in, uint32_t in_len, uint8_t* out, uint32_t* out_len, void* wrkmem) { const uint8_t* ip; uint32_t dv; uint8_t* op; const uint8_t* in_end = in + in_len; const uint8_t* ip_end = in + in_len - 9 - 4; const uint8_t* ii; const uint8_t** dict = (const uint8_t**)wrkmem; op = out; ip = in; ii = ip; DVAL_FIRST(dv, ip); UPDATE_D(dict, cycle, dv, ip); ip++; DVAL_NEXT(dv, ip); UPDATE_D(dict, cycle, dv, ip); ip++; DVAL_NEXT(dv, ip); UPDATE_D(dict, cycle, dv, ip); ip++; DVAL_NEXT(dv, ip); UPDATE_D(dict, cycle, dv, ip); ip++; while (1) { const uint8_t* m_pos; uint32_t m_len; ptrdiff_t m_off; uint32_t lit, dindex; dindex = DINDEX(dv, ip); m_pos = dict[dindex]; UPDATE_I(dict, cycle, dindex, ip); 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]) { lit = (uint32_t)(ip - ii); m_pos += 3; if (m_off <= M2_MAX_OFFSET) goto match; if (lit == 3) { /* better compression, but slower */ if (op - 2 <= out) return STATUS_INTERNAL_ERROR; op[-2] |= LZO_BYTE(3); *op++ = *ii++; *op++ = *ii++; *op++ = *ii++; goto code_match; } if (*m_pos == ip[3]) goto match; } /* a literal */ ++ip; if (ip >= ip_end) break; DVAL_NEXT(dv, ip); continue; /* a match */ match: /* store current literal run */ if (lit > 0) { uint32_t t = lit; if (t <= 3) { if (op - 2 <= out) return STATUS_INTERNAL_ERROR; op[-2] |= LZO_BYTE(t); } else if (t <= 18) *op++ = LZO_BYTE(t - 3); else { uint32_t tt = t - 18; *op++ = 0; while (tt > 255) { tt -= 255; *op++ = 0; } if (tt <= 0) return STATUS_INTERNAL_ERROR; *op++ = LZO_BYTE(tt); } do { *op++ = *ii++; } while (--t > 0); } /* code the match */ code_match: if (ii != ip) return STATUS_INTERNAL_ERROR; ip += 3; if (*m_pos++ != *ip++ || *m_pos++ != *ip++ || *m_pos++ != *ip++ || *m_pos++ != *ip++ || *m_pos++ != *ip++ || *m_pos++ != *ip++) { --ip; m_len = (uint32_t)(ip - ii); if (m_len < 3 || m_len > 8) return STATUS_INTERNAL_ERROR; if (m_off <= M2_MAX_OFFSET) { m_off -= 1; *op++ = LZO_BYTE(((m_len - 1) << 5) | ((m_off & 7) << 2)); *op++ = LZO_BYTE(m_off >> 3); } else if (m_off <= M3_MAX_OFFSET) { m_off -= 1; *op++ = LZO_BYTE(M3_MARKER | (m_len - 2)); goto m3_m4_offset; } else { m_off -= 0x4000; if (m_off <= 0 || m_off > 0x7fff) return STATUS_INTERNAL_ERROR; *op++ = LZO_BYTE(M4_MARKER | ((m_off & 0x4000) >> 11) | (m_len - 2)); goto m3_m4_offset; } } else { const uint8_t* end; end = in_end; while (ip < end && *m_pos == *ip) m_pos++, ip++; m_len = (uint32_t)(ip - ii); if (m_len < 3) return STATUS_INTERNAL_ERROR; if (m_off <= M3_MAX_OFFSET) { m_off -= 1; if (m_len <= 33) *op++ = LZO_BYTE(M3_MARKER | (m_len - 2)); else { m_len -= 33; *op++ = M3_MARKER | 0; goto m3_m4_len; } } else { m_off -= 0x4000; if (m_off <= 0 || m_off > 0x7fff) return STATUS_INTERNAL_ERROR; if (m_len <= 9) *op++ = LZO_BYTE(M4_MARKER | ((m_off & 0x4000) >> 11) | (m_len - 2)); else { m_len -= 9; *op++ = LZO_BYTE(M4_MARKER | ((m_off & 0x4000) >> 11)); m3_m4_len: while (m_len > 255) { m_len -= 255; *op++ = 0; } if (m_len <= 0) return STATUS_INTERNAL_ERROR; *op++ = LZO_BYTE(m_len); } } m3_m4_offset: *op++ = LZO_BYTE((m_off & 63) << 2); *op++ = LZO_BYTE(m_off >> 6); } ii = ip; if (ip >= ip_end) break; DVAL_FIRST(dv, ip); } /* store final literal run */ if (in_end - ii > 0) { uint32_t t = (uint32_t)(in_end - ii); if (op == out && t <= 238) *op++ = LZO_BYTE(17 + t); else if (t <= 3) op[-2] |= LZO_BYTE(t); else if (t <= 18) *op++ = LZO_BYTE(t - 3); else { uint32_t tt = t - 18; *op++ = 0; while (tt > 255) { tt -= 255; *op++ = 0; } if (tt <= 0) return STATUS_INTERNAL_ERROR; *op++ = LZO_BYTE(tt); } do { *op++ = *ii++; } while (--t > 0); } *out_len = (uint32_t)(op - out); return STATUS_SUCCESS; } static NTSTATUS lzo1x_1_compress(lzo_stream* stream) { uint8_t *op = stream->out; NTSTATUS Status = STATUS_SUCCESS; if (stream->inlen <= 0) stream->outlen = 0; else if (stream->inlen <= 9 + 4) { *op++ = LZO_BYTE(17 + stream->inlen); stream->inpos = 0; do { *op++ = stream->in[stream->inpos]; stream->inpos++; } while (stream->inlen < stream->inpos); stream->outlen = (uint32_t)(op - stream->out); } else Status = lzo_do_compress(stream->in, stream->inlen, stream->out, &stream->outlen, stream->wrkmem); if (Status == STATUS_SUCCESS) { op = stream->out + stream->outlen; *op++ = M4_MARKER | 1; *op++ = 0; *op++ = 0; stream->outlen += 3; } return Status; } static __inline uint32_t lzo_max_outlen(uint32_t inlen) { return inlen + (inlen / 16) + 64 + 3; // formula comes from LZO.FAQ } static void* zstd_malloc(void* opaque, size_t size) { UNUSED(opaque); return ExAllocatePoolWithTag(PagedPool, size, ZSTD_ALLOC_TAG); } static void zstd_free(void* opaque, void* address) { UNUSED(opaque); ExFreePool(address); } NTSTATUS zstd_decompress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen) { NTSTATUS Status; ZSTD_DStream* stream; size_t init_res, read; ZSTD_inBuffer input; ZSTD_outBuffer output; stream = ZSTD_createDStream_advanced(zstd_mem); if (!stream) { ERR("ZSTD_createDStream failed.\n"); return STATUS_INTERNAL_ERROR; } init_res = ZSTD_initDStream(stream); if (ZSTD_isError(init_res)) { ERR("ZSTD_initDStream failed: %s\n", ZSTD_getErrorName(init_res)); Status = STATUS_INTERNAL_ERROR; goto end; } input.src = inbuf; input.size = inlen; input.pos = 0; output.dst = outbuf; output.size = outlen; output.pos = 0; do { read = ZSTD_decompressStream(stream, &output, &input); if (ZSTD_isError(read)) { ERR("ZSTD_decompressStream failed: %s\n", ZSTD_getErrorName(read)); Status = STATUS_INTERNAL_ERROR; goto end; } if (output.pos == output.size) break; } while (read != 0); Status = STATUS_SUCCESS; end: ZSTD_freeDStream(stream); return Status; } NTSTATUS lzo_compress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, unsigned int* space_left) { NTSTATUS Status; unsigned int num_pages; unsigned int comp_data_len; uint8_t* comp_data; lzo_stream stream; uint32_t* out_size; num_pages = (unsigned int)sector_align(inlen, LZO_PAGE_SIZE) / LZO_PAGE_SIZE; // Four-byte overall header // Another four-byte header page // Each page has a maximum size of lzo_max_outlen(LZO_PAGE_SIZE) // Plus another four bytes for possible padding comp_data_len = sizeof(uint32_t) + ((lzo_max_outlen(LZO_PAGE_SIZE) + (2 * sizeof(uint32_t))) * num_pages); // FIXME - can we write this so comp_data isn't necessary? comp_data = ExAllocatePoolWithTag(PagedPool, comp_data_len, ALLOC_TAG); if (!comp_data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } stream.wrkmem = ExAllocatePoolWithTag(PagedPool, LZO1X_MEM_COMPRESS, ALLOC_TAG); if (!stream.wrkmem) { ERR("out of memory\n"); ExFreePool(comp_data); return STATUS_INSUFFICIENT_RESOURCES; } out_size = (uint32_t*)comp_data; *out_size = sizeof(uint32_t); stream.in = inbuf; stream.out = comp_data + (2 * sizeof(uint32_t)); for (unsigned int i = 0; i < num_pages; i++) { uint32_t* pagelen = (uint32_t*)(stream.out - sizeof(uint32_t)); stream.inlen = (uint32_t)min(LZO_PAGE_SIZE, outlen - (i * LZO_PAGE_SIZE)); Status = lzo1x_1_compress(&stream); if (!NT_SUCCESS(Status)) { ERR("lzo1x_1_compress returned %08lx\n", Status); ExFreePool(comp_data); return Status; } *pagelen = stream.outlen; *out_size += stream.outlen + sizeof(uint32_t); stream.in += LZO_PAGE_SIZE; stream.out += stream.outlen + sizeof(uint32_t); // new page needs to start at a 32-bit boundary if (LZO_PAGE_SIZE - (*out_size % LZO_PAGE_SIZE) < sizeof(uint32_t)) { RtlZeroMemory(stream.out, LZO_PAGE_SIZE - (*out_size % LZO_PAGE_SIZE)); stream.out += LZO_PAGE_SIZE - (*out_size % LZO_PAGE_SIZE); *out_size += LZO_PAGE_SIZE - (*out_size % LZO_PAGE_SIZE); } } ExFreePool(stream.wrkmem); if (*out_size >= outlen) *space_left = 0; else { *space_left = outlen - *out_size; RtlCopyMemory(outbuf, comp_data, *out_size); } ExFreePool(comp_data); return STATUS_SUCCESS; } NTSTATUS zstd_compress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, uint32_t level, unsigned int* space_left) { ZSTD_CStream* stream; size_t init_res, written; ZSTD_inBuffer input; ZSTD_outBuffer output; ZSTD_parameters params; stream = ZSTD_createCStream_advanced(zstd_mem); if (!stream) { ERR("ZSTD_createCStream failed.\n"); return STATUS_INTERNAL_ERROR; } params = ZSTD_getParams(level, inlen, 0); if (params.cParams.windowLog > ZSTD_BTRFS_MAX_WINDOWLOG) params.cParams.windowLog = ZSTD_BTRFS_MAX_WINDOWLOG; init_res = ZSTD_initCStream_advanced(stream, NULL, 0, params, inlen); if (ZSTD_isError(init_res)) { ERR("ZSTD_initCStream_advanced failed: %s\n", ZSTD_getErrorName(init_res)); ZSTD_freeCStream(stream); return STATUS_INTERNAL_ERROR; } input.src = inbuf; input.size = inlen; input.pos = 0; output.dst = outbuf; output.size = outlen; output.pos = 0; while (input.pos < input.size && output.pos < output.size) { written = ZSTD_compressStream(stream, &output, &input); if (ZSTD_isError(written)) { ERR("ZSTD_compressStream failed: %s\n", ZSTD_getErrorName(written)); ZSTD_freeCStream(stream); return STATUS_INTERNAL_ERROR; } } written = ZSTD_endStream(stream, &output); if (ZSTD_isError(written)) { ERR("ZSTD_endStream failed: %s\n", ZSTD_getErrorName(written)); ZSTD_freeCStream(stream); return STATUS_INTERNAL_ERROR; } ZSTD_freeCStream(stream); if (input.pos < input.size) // output would be larger than input *space_left = 0; else *space_left = output.size - output.pos; return STATUS_SUCCESS; } typedef struct { uint8_t buf[COMPRESSED_EXTENT_SIZE]; uint8_t compression_type; unsigned int inlen; unsigned int outlen; calc_job* cj; } comp_part; NTSTATUS write_compressed(fcb* fcb, uint64_t start_data, uint64_t end_data, void* data, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; uint64_t i; unsigned int num_parts = (unsigned int)sector_align(end_data - start_data, COMPRESSED_EXTENT_SIZE) / COMPRESSED_EXTENT_SIZE; uint8_t type; comp_part* parts; unsigned int buflen = 0; uint8_t* buf; chunk* c = NULL; LIST_ENTRY* le; uint64_t address, extaddr; void* csum = NULL; if (fcb->Vcb->options.compress_type != 0 && fcb->prop_compression == PropCompression_None) type = fcb->Vcb->options.compress_type; else { if (!(fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_COMPRESS_ZSTD) && fcb->prop_compression == PropCompression_ZSTD) type = BTRFS_COMPRESSION_ZSTD; else if (fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_COMPRESS_ZSTD && fcb->prop_compression != PropCompression_Zlib && fcb->prop_compression != PropCompression_LZO) type = BTRFS_COMPRESSION_ZSTD; else if (!(fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_COMPRESS_LZO) && fcb->prop_compression == PropCompression_LZO) type = BTRFS_COMPRESSION_LZO; else if (fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_COMPRESS_LZO && fcb->prop_compression != PropCompression_Zlib) type = BTRFS_COMPRESSION_LZO; else type = BTRFS_COMPRESSION_ZLIB; } Status = excise_extents(fcb->Vcb, fcb, start_data, end_data, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); return Status; } parts = ExAllocatePoolWithTag(PagedPool, sizeof(comp_part) * num_parts, ALLOC_TAG); if (!parts) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } for (i = 0; i < num_parts; i++) { if (i == num_parts - 1) parts[i].inlen = ((unsigned int)(end_data - start_data) - ((num_parts - 1) * COMPRESSED_EXTENT_SIZE)); else parts[i].inlen = COMPRESSED_EXTENT_SIZE; Status = add_calc_job_comp(fcb->Vcb, type, (uint8_t*)data + (i * COMPRESSED_EXTENT_SIZE), parts[i].inlen, parts[i].buf, parts[i].inlen, &parts[i].cj); if (!NT_SUCCESS(Status)) { ERR("add_calc_job_comp returned %08lx\n", Status); for (unsigned int j = 0; j < i; j++) { KeWaitForSingleObject(&parts[j].cj->event, Executive, KernelMode, false, NULL); ExFreePool(parts[j].cj); } ExFreePool(parts); return Status; } } Status = STATUS_SUCCESS; for (int i = num_parts - 1; i >= 0; i--) { calc_thread_main(fcb->Vcb, parts[i].cj); KeWaitForSingleObject(&parts[i].cj->event, Executive, KernelMode, false, NULL); if (!NT_SUCCESS(parts[i].cj->Status)) Status = parts[i].cj->Status; } if (!NT_SUCCESS(Status)) { ERR("calc job returned %08lx\n", Status); for (unsigned int i = 0; i < num_parts; i++) { ExFreePool(parts[i].cj); } ExFreePool(parts); return Status; } for (unsigned int i = 0; i < num_parts; i++) { if (parts[i].cj->space_left >= fcb->Vcb->superblock.sector_size) { parts[i].compression_type = type; parts[i].outlen = parts[i].inlen - parts[i].cj->space_left; if (type == BTRFS_COMPRESSION_LZO) fcb->Vcb->superblock.incompat_flags |= BTRFS_INCOMPAT_FLAGS_COMPRESS_LZO; else if (type == BTRFS_COMPRESSION_ZSTD) fcb->Vcb->superblock.incompat_flags |= BTRFS_INCOMPAT_FLAGS_COMPRESS_ZSTD; if ((parts[i].outlen & (fcb->Vcb->superblock.sector_size - 1)) != 0) { unsigned int newlen = (unsigned int)sector_align(parts[i].outlen, fcb->Vcb->superblock.sector_size); RtlZeroMemory(parts[i].buf + parts[i].outlen, newlen - parts[i].outlen); parts[i].outlen = newlen; } } else { parts[i].compression_type = BTRFS_COMPRESSION_NONE; parts[i].outlen = (unsigned int)sector_align(parts[i].inlen, fcb->Vcb->superblock.sector_size); } buflen += parts[i].outlen; ExFreePool(parts[i].cj); } // check if first 128 KB of file is incompressible if (start_data == 0 && parts[0].compression_type == BTRFS_COMPRESSION_NONE && !fcb->Vcb->options.compress_force) { TRACE("adding nocompress flag to subvol %I64x, inode %I64x\n", fcb->subvol->id, fcb->inode); fcb->inode_item.flags |= BTRFS_INODE_NOCOMPRESS; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); } // join together into continuous buffer buf = ExAllocatePoolWithTag(PagedPool, buflen, ALLOC_TAG); if (!buf) { ERR("out of memory\n"); ExFreePool(parts); return STATUS_INSUFFICIENT_RESOURCES; } { uint8_t* buf2 = buf; for (i = 0; i < num_parts; i++) { if (parts[i].compression_type == BTRFS_COMPRESSION_NONE) RtlCopyMemory(buf2, (uint8_t*)data + (i * COMPRESSED_EXTENT_SIZE), parts[i].outlen); else RtlCopyMemory(buf2, parts[i].buf, parts[i].outlen); buf2 += parts[i].outlen; } } // find an address ExAcquireResourceSharedLite(&fcb->Vcb->chunk_lock, true); le = fcb->Vcb->chunks.Flink; while (le != &fcb->Vcb->chunks) { chunk* c2 = CONTAINING_RECORD(le, chunk, list_entry); if (!c2->readonly && !c2->reloc) { acquire_chunk_lock(c2, fcb->Vcb); if (c2->chunk_item->type == fcb->Vcb->data_flags && (c2->chunk_item->size - c2->used) >= buflen) { if (find_data_address_in_chunk(fcb->Vcb, c2, buflen, &address)) { c = c2; c->used += buflen; space_list_subtract(c, address, buflen, rollback); release_chunk_lock(c2, fcb->Vcb); break; } } release_chunk_lock(c2, fcb->Vcb); } le = le->Flink; } ExReleaseResourceLite(&fcb->Vcb->chunk_lock); if (!c) { chunk* c2; ExAcquireResourceExclusiveLite(&fcb->Vcb->chunk_lock, true); Status = alloc_chunk(fcb->Vcb, fcb->Vcb->data_flags, &c2, false); ExReleaseResourceLite(&fcb->Vcb->chunk_lock); if (!NT_SUCCESS(Status)) { ERR("alloc_chunk returned %08lx\n", Status); ExFreePool(buf); ExFreePool(parts); return Status; } acquire_chunk_lock(c2, fcb->Vcb); if (find_data_address_in_chunk(fcb->Vcb, c2, buflen, &address)) { c = c2; c->used += buflen; space_list_subtract(c, address, buflen, rollback); } release_chunk_lock(c2, fcb->Vcb); } if (!c) { WARN("couldn't find any data chunks with %x bytes free\n", buflen); ExFreePool(buf); ExFreePool(parts); return STATUS_DISK_FULL; } // write to disk TRACE("writing %x bytes to %I64x\n", buflen, address); Status = write_data_complete(fcb->Vcb, address, buf, buflen, Irp, NULL, false, 0, fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? HighPagePriority : NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("write_data_complete returned %08lx\n", Status); ExFreePool(buf); ExFreePool(parts); return Status; } // FIXME - do rest of the function while we're waiting for I/O to finish? // calculate csums if necessary if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) { unsigned int sl = buflen >> fcb->Vcb->sector_shift; csum = ExAllocatePoolWithTag(PagedPool, sl * fcb->Vcb->csum_size, ALLOC_TAG); if (!csum) { ERR("out of memory\n"); ExFreePool(buf); ExFreePool(parts); return STATUS_INSUFFICIENT_RESOURCES; } do_calc_job(fcb->Vcb, buf, sl, csum); } ExFreePool(buf); // add extents to fcb extaddr = address; for (i = 0; i < num_parts; i++) { EXTENT_DATA* ed; EXTENT_DATA2* ed2; void* csum2; ed = ExAllocatePoolWithTag(PagedPool, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2), ALLOC_TAG); if (!ed) { ERR("out of memory\n"); ExFreePool(parts); if (csum) ExFreePool(csum); return STATUS_INSUFFICIENT_RESOURCES; } ed->generation = fcb->Vcb->superblock.generation; ed->decoded_size = parts[i].inlen; ed->compression = parts[i].compression_type; ed->encryption = BTRFS_ENCRYPTION_NONE; ed->encoding = BTRFS_ENCODING_NONE; ed->type = EXTENT_TYPE_REGULAR; ed2 = (EXTENT_DATA2*)ed->data; ed2->address = extaddr; ed2->size = parts[i].outlen; ed2->offset = 0; ed2->num_bytes = parts[i].inlen; if (csum) { csum2 = ExAllocatePoolWithTag(PagedPool, (parts[i].outlen * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift, ALLOC_TAG); if (!csum2) { ERR("out of memory\n"); ExFreePool(ed); ExFreePool(parts); ExFreePool(csum); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(csum2, (uint8_t*)csum + (((extaddr - address) * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift), (parts[i].outlen * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift); } else csum2 = NULL; Status = add_extent_to_fcb(fcb, start_data + (i * COMPRESSED_EXTENT_SIZE), ed, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2), true, csum2, rollback); if (!NT_SUCCESS(Status)) { ERR("add_extent_to_fcb returned %08lx\n", Status); ExFreePool(ed); ExFreePool(parts); if (csum) ExFreePool(csum); return Status; } ExFreePool(ed); fcb->inode_item.st_blocks += parts[i].inlen; extaddr += parts[i].outlen; } if (csum) ExFreePool(csum); // update extent refcounts ExAcquireResourceExclusiveLite(&c->changed_extents_lock, true); extaddr = address; for (i = 0; i < num_parts; i++) { add_changed_extent_ref(c, extaddr, parts[i].outlen, fcb->subvol->id, fcb->inode, start_data + (i * COMPRESSED_EXTENT_SIZE), 1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM); extaddr += parts[i].outlen; } ExReleaseResourceLite(&c->changed_extents_lock); fcb->extents_changed = true; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); ExFreePool(parts); return STATUS_SUCCESS; } ================================================ FILE: src/crc32c-aarch64.asm ================================================ AREA .text, CODE, READONLY GLOBAL calc_crc32c_hw ; uint32_t calc_crc32c_hw(uint32_t seed, const uint8_t* buf, uint32_t len); calc_crc32c_hw ; w0 = crc / seed ; x1 = buf ; w2 = len ; x3 = scratch cmp w2, #8 b.lt crchw_stragglers ldr x3, [x1] crc32cx w0, w0, x3 add x1, x1, #8 sub w2, w2, #8 b calc_crc32c_hw crchw_stragglers cmp w2, #4 b.lt crchw_stragglers2 ldr w3, [x1] crc32cw w0, w0, w3 add x1, x1, #4 sub w2, w2, #4 crchw_stragglers2 cmp w2, #2 b.lt crchw_stragglers3 ldrh w3, [x1] crc32ch w0, w0, w3 add x1, x1, #2 sub w2, w2, #2 crchw_stragglers3 cmp w2, #0 b.eq crchw_end ldrb w3, [x1] crc32cb w0, w0, w3 crchw_end ret END ================================================ FILE: src/crc32c-gas.S ================================================ /* Copyright (c) Mark Harmstone 2020 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ .intel_syntax noprefix #ifdef __x86_64__ .extern crctable .global calc_crc32c_sw /* uint32_t __stdcall calc_crc32c_sw(uint32_t seed, uint8_t* msg, uint32_t msglen); */ calc_crc32c_sw: /* rax = crc / seed * rdx = buf * r8 = len * rcx = tmp * r10 = tmp2 */ mov rax, rcx crcloop: test r8, r8 jz crcend mov rcx, rax shr rcx, 8 mov r10b, byte ptr [rdx] xor al, r10b and rax, 255 shl rax, 2 movabs r10, offset crctable mov eax, dword ptr [r10 + rax] xor rax, rcx inc rdx dec r8 jmp crcloop crcend: ret /****************************************************/ /* uint32_t __stdcall calc_crc32c_hw(uint32_t seed, uint8_t* msg, uint32_t msglen); */ .global calc_crc32c_hw calc_crc32c_hw: /* rax = crc / seed * rdx = buf * r8 = len */ mov rax, rcx crchw_loop: cmp r8, 8 jl crchw_stragglers crc32 rax, qword ptr [rdx] add rdx, 8 sub r8, 8 jmp crchw_loop crchw_stragglers: cmp r8, 4 jl crchw_stragglers2 crc32 eax, dword ptr [rdx] add rdx, 4 sub r8, 4 crchw_stragglers2: cmp r8, 2 jl crchw_stragglers3 crc32 eax, word ptr [rdx] add rdx, 2 sub r8, 2 crchw_stragglers3: test r8, r8 jz crchw_end crc32 eax, byte ptr [rdx] inc rdx dec r8 jmp crchw_stragglers3 crchw_end: ret #elif defined(_X86_) .extern _crctable .global _calc_crc32c_sw@12 .global _calc_crc32c_hw@12 /* uint32_t __stdcall calc_crc32c_sw(uint32_t seed, uint8_t* msg, uint32_t msglen); */ _calc_crc32c_sw@12: push ebp mov ebp, esp push esi push ebx mov eax, [ebp+8] mov edx, [ebp+12] mov ebx, [ebp+16] /* eax = crc / seed * ebx = len * esi = tmp * edx = buf * ecx = tmp2 */ crcloop: test ebx, ebx jz crcend mov esi, eax shr esi, 8 mov cl, byte ptr [edx] xor al, cl and eax, 255 shl eax, 2 mov eax, [_crctable + eax] xor eax, esi inc edx dec ebx jmp crcloop crcend: pop ebx pop esi pop ebp ret 12 /****************************************************/ /* uint32_t __stdcall calc_crc32c_hw(uint32_t seed, uint8_t* msg, uint32_t msglen); */ _calc_crc32c_hw@12: push ebp mov ebp, esp mov eax, [ebp+8] mov edx, [ebp+12] mov ecx, [ebp+16] /* eax = crc / seed * ecx = len * edx = buf */ crchw_loop: cmp ecx, 4 jl crchw_stragglers crc32 eax, dword ptr [edx] add edx, 4 sub ecx, 4 jmp crchw_loop crchw_stragglers: cmp ecx, 2 jl crchw_stragglers2 crc32 eax, word ptr [edx] add edx, 2 sub ecx, 2 crchw_stragglers2: test ecx, ecx jz crchw_end crc32 eax, byte ptr [edx] inc edx dec ecx jmp crchw_stragglers2 crchw_end: pop ebp ret 12 #endif ================================================ FILE: src/crc32c-masm.asm ================================================ ; Copyright (c) Mark Harmstone 2020 ; ; This file is part of WinBtrfs. ; ; WinBtrfs is free software: you can redistribute it and/or modify ; it under the terms of the GNU Lesser General Public Licence as published by ; the Free Software Foundation, either version 3 of the Licence, or ; (at your option) any later version. ; ; WinBtrfs is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU Lesser General Public Licence for more details. ; ; You should have received a copy of the GNU Lesser General Public Licence ; along with WinBtrfs. If not, see . IFDEF RAX ELSE .686P ENDIF _TEXT SEGMENT IFDEF RAX EXTERN crctable:qword PUBLIC calc_crc32c_sw ; uint32_t __stdcall calc_crc32c_sw(uint32_t seed, uint8_t* msg, uint32_t msglen); calc_crc32c_sw: ; rax = crc / seed ; rdx = buf ; r8 = len ; rcx = tmp ; r10 = tmp2 mov rax, rcx crcloop: test r8, r8 jz crcend mov rcx, rax shr rcx, 8 mov r10b, byte ptr [rdx] xor al, r10b and rax, 255 shl rax, 2 mov r10, offset crctable mov eax, dword ptr [r10 + rax] xor rax, rcx inc rdx dec r8 jmp crcloop crcend: ret ; **************************************************** ; uint32_t __stdcall calc_crc32c_hw(uint32_t seed, uint8_t* msg, uint32_t msglen); PUBLIC calc_crc32c_hw calc_crc32c_hw: ; rax = crc / seed ; rdx = buf ; r8 = len mov rax, rcx crchw_loop: cmp r8, 8 jl crchw_stragglers crc32 rax, qword ptr [rdx] add rdx, 8 sub r8, 8 jmp crchw_loop crchw_stragglers: cmp r8, 4 jl crchw_stragglers2 crc32 eax, dword ptr [rdx] add rdx, 4 sub r8, 4 crchw_stragglers2: cmp r8, 2 jl crchw_stragglers3 crc32 eax, word ptr [rdx] add rdx, 2 sub r8, 2 crchw_stragglers3: test r8, r8 jz crchw_end crc32 eax, byte ptr [rdx] inc rdx dec r8 jmp crchw_stragglers3 crchw_end: ret ELSE EXTERN crctable:ABS ; uint32_t __stdcall calc_crc32c_sw(uint32_t seed, uint8_t* msg, uint32_t msglen); PUBLIC calc_crc32c_sw@12 calc_crc32c_sw@12: push ebp mov ebp, esp push esi push ebx mov eax, [ebp+8] mov edx, [ebp+12] mov ebx, [ebp+16] ; eax = crc / seed ; ebx = len ; esi = tmp ; edx = buf ; ecx = tmp2 crcloop: test ebx, ebx jz crcend mov esi, eax shr esi, 8 mov cl, byte ptr [edx] xor al, cl and eax, 255 shl eax, 2 mov eax, [crctable + eax] xor eax, esi inc edx dec ebx jmp crcloop crcend: pop ebx pop esi pop ebp ret 12 ; **************************************************** ; uint32_t __stdcall calc_crc32c_hw(uint32_t seed, uint8_t* msg, uint32_t msglen); PUBLIC calc_crc32c_hw@12 calc_crc32c_hw@12: push ebp mov ebp, esp mov eax, [ebp+8] mov edx, [ebp+12] mov ecx, [ebp+16] ; eax = crc / seed ; ecx = len ; edx = buf crchw_loop: cmp ecx, 4 jl crchw_stragglers crc32 eax, dword ptr [edx] add edx, 4 sub ecx, 4 jmp crchw_loop crchw_stragglers: cmp ecx, 2 jl crchw_stragglers2 crc32 eax, word ptr [edx] add edx, 2 sub ecx, 2 crchw_stragglers2: test ecx, ecx jz crchw_end crc32 eax, byte ptr [edx] inc edx dec ecx jmp crchw_stragglers2 crchw_end: pop ebp ret 12 ENDIF _TEXT ENDS end ================================================ FILE: src/crc32c.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "crc32c.h" #include #include #include crc_func calc_crc32c = calc_crc32c_sw; const uint32_t crctable[] = { 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb, 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b, 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, 0x105ec76f, 0xe235446c, 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384, 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b, 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a, 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, 0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa, 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad, 0x1642ae59, 0xe4292d5a, 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a, 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, 0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957, 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198, 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927, 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, 0xdbfc821c, 0x2997011f, 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7, 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789, 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859, 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, 0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6, 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de, 0xdde0eb2a, 0x2f8b6829, 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c, 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, 0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c, 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc, 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c, 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, 0xa24bb5a6, 0x502036a5, 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d, 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982, 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d, 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, 0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed, 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8, 0xe52cc12c, 0x1747422f, 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff, 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, 0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540, 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f, 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee, 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, 0x69e9f0d5, 0x9b8273d6, 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e, 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e, 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e, 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351, }; // x86 and amd64 versions live in asm files #if !defined(_X86_) && !defined(_AMD64_) uint32_t __stdcall calc_crc32c_sw(_In_ uint32_t seed, _In_reads_bytes_(msglen) uint8_t* msg, _In_ uint32_t msglen) { uint32_t rem = seed; for (uint32_t i = 0; i < msglen; i++) { rem = crctable[(rem ^ msg[i]) & 0xff] ^ (rem >> 8); } return rem; } #endif ================================================ FILE: src/crc32c.h ================================================ #pragma once #include #ifdef __cplusplus extern "C" { #endif #if defined(_X86_) || defined(_AMD64_) || defined(_ARM64_) uint32_t __stdcall calc_crc32c_hw(uint32_t seed, uint8_t* msg, uint32_t msglen); #endif uint32_t __stdcall calc_crc32c_sw(uint32_t seed, uint8_t* msg, uint32_t msglen); typedef uint32_t (__stdcall *crc_func)(uint32_t seed, uint8_t* msg, uint32_t msglen); extern crc_func calc_crc32c; #ifdef __cplusplus } #endif ================================================ FILE: src/create.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include #include "btrfs_drv.h" #include "crc32c.h" #include extern PDEVICE_OBJECT master_devobj; extern tFsRtlGetEcpListFromIrp fFsRtlGetEcpListFromIrp; extern tFsRtlGetNextExtraCreateParameter fFsRtlGetNextExtraCreateParameter; extern tFsRtlValidateReparsePointBuffer fFsRtlValidateReparsePointBuffer; static const WCHAR datastring[] = L"::$DATA"; static const char root_dir[] = "$Root"; static const WCHAR root_dir_utf16[] = L"$Root"; // Windows 10 #define ATOMIC_CREATE_ECP_IN_FLAG_REPARSE_POINT_SPECIFIED 0x0002 #define ATOMIC_CREATE_ECP_IN_FLAG_OP_FLAGS_SPECIFIED 0x0080 #define ATOMIC_CREATE_ECP_IN_FLAG_BEST_EFFORT 0x0100 #define ATOMIC_CREATE_ECP_OUT_FLAG_REPARSE_POINT_SET 0x0002 #define ATOMIC_CREATE_ECP_OUT_FLAG_OP_FLAGS_HONORED 0x0080 #define ATOMIC_CREATE_ECP_IN_OP_FLAG_CASE_SENSITIVE_FLAGS_SPECIFIED 1 #define ATOMIC_CREATE_ECP_OUT_OP_FLAG_CASE_SENSITIVE_FLAGS_SET 1 #ifndef SL_IGNORE_READONLY_ATTRIBUTE #define SL_IGNORE_READONLY_ATTRIBUTE 0x40 // introduced in Windows 10, not in mingw #endif typedef struct _FILE_TIMESTAMPS { LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; } FILE_TIMESTAMPS, *PFILE_TIMESTAMPS; typedef struct _ATOMIC_CREATE_ECP_CONTEXT { USHORT Size; USHORT InFlags; USHORT OutFlags; USHORT ReparseBufferLength; PREPARSE_DATA_BUFFER ReparseBuffer; LONGLONG FileSize; LONGLONG ValidDataLength; PFILE_TIMESTAMPS FileTimestamps; ULONG FileAttributes; ULONG UsnSourceInfo; USN Usn; ULONG SuppressFileAttributeInheritanceMask; ULONG InOpFlags; ULONG OutOpFlags; ULONG InGenFlags; ULONG OutGenFlags; ULONG CaseSensitiveFlagsMask; ULONG InCaseSensitiveFlags; ULONG OutCaseSensitiveFlags; } ATOMIC_CREATE_ECP_CONTEXT, *PATOMIC_CREATE_ECP_CONTEXT; static const GUID GUID_ECP_ATOMIC_CREATE = { 0x4720bd83, 0x52ac, 0x4104, { 0xa1, 0x30, 0xd1, 0xec, 0x6a, 0x8c, 0xc8, 0xe5 } }; static const GUID GUID_ECP_QUERY_ON_CREATE = { 0x1aca62e9, 0xabb4, 0x4ff2, { 0xbb, 0x5c, 0x1c, 0x79, 0x02, 0x5e, 0x41, 0x7f } }; static const GUID GUID_ECP_CREATE_REDIRECTION = { 0x188d6bd6, 0xa126, 0x4fa8, { 0xbd, 0xf2, 0x1c, 0xcd, 0xf8, 0x96, 0xf3, 0xe0 } }; typedef struct { device_extension* Vcb; ACCESS_MASK granted_access; file_ref* fileref; NTSTATUS Status; KEVENT event; } oplock_context; fcb* create_fcb(device_extension* Vcb, POOL_TYPE pool_type) { fcb* fcb; if (pool_type == NonPagedPool) { fcb = ExAllocatePoolWithTag(pool_type, sizeof(struct _fcb), ALLOC_TAG); if (!fcb) { ERR("out of memory\n"); return NULL; } } else { fcb = ExAllocateFromPagedLookasideList(&Vcb->fcb_lookaside); if (!fcb) { ERR("out of memory\n"); return NULL; } } #ifdef DEBUG_FCB_REFCOUNTS WARN("allocating fcb %p\n", fcb); #endif RtlZeroMemory(fcb, sizeof(struct _fcb)); fcb->pool_type = pool_type; fcb->Header.NodeTypeCode = BTRFS_NODE_TYPE_FCB; fcb->Header.NodeByteSize = sizeof(struct _fcb); fcb->nonpaged = ExAllocateFromNPagedLookasideList(&Vcb->fcb_np_lookaside); if (!fcb->nonpaged) { ERR("out of memory\n"); if (pool_type == NonPagedPool) ExFreePool(fcb); else ExFreeToPagedLookasideList(&Vcb->fcb_lookaside, fcb); return NULL; } RtlZeroMemory(fcb->nonpaged, sizeof(struct _fcb_nonpaged)); ExInitializeResourceLite(&fcb->nonpaged->paging_resource); fcb->Header.PagingIoResource = &fcb->nonpaged->paging_resource; ExInitializeFastMutex(&fcb->nonpaged->HeaderMutex); FsRtlSetupAdvancedHeader(&fcb->Header, &fcb->nonpaged->HeaderMutex); fcb->refcount = 1; #ifdef DEBUG_FCB_REFCOUNTS WARN("fcb %p: refcount now %i\n", fcb, fcb->refcount); #endif ExInitializeResourceLite(&fcb->nonpaged->resource); fcb->Header.Resource = &fcb->nonpaged->resource; ExInitializeResourceLite(&fcb->nonpaged->dir_children_lock); FsRtlInitializeFileLock(&fcb->lock, NULL, NULL); FsRtlInitializeOplock(fcb_oplock(fcb)); InitializeListHead(&fcb->extents); InitializeListHead(&fcb->hardlinks); InitializeListHead(&fcb->xattrs); InitializeListHead(&fcb->dir_children_index); InitializeListHead(&fcb->dir_children_hash); InitializeListHead(&fcb->dir_children_hash_uc); return fcb; } file_ref* create_fileref(device_extension* Vcb) { file_ref* fr; fr = ExAllocateFromPagedLookasideList(&Vcb->fileref_lookaside); if (!fr) { ERR("out of memory\n"); return NULL; } RtlZeroMemory(fr, sizeof(file_ref)); fr->refcount = 1; #ifdef DEBUG_FCB_REFCOUNTS WARN("fileref %p: refcount now 1\n", fr); #endif InitializeListHead(&fr->children); return fr; } NTSTATUS find_file_in_dir(PUNICODE_STRING filename, fcb* fcb, root** subvol, uint64_t* inode, dir_child** pdc, bool case_sensitive) { NTSTATUS Status; UNICODE_STRING fnus; uint32_t hash; LIST_ENTRY* le; uint8_t c; bool locked = false; if (!case_sensitive) { Status = RtlUpcaseUnicodeString(&fnus, filename, true); if (!NT_SUCCESS(Status)) { ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); return Status; } } else fnus = *filename; Status = check_file_name_valid(filename, false, false); if (!NT_SUCCESS(Status)) return Status; hash = calc_crc32c(0xffffffff, (uint8_t*)fnus.Buffer, fnus.Length); c = hash >> 24; if (!ExIsResourceAcquiredSharedLite(&fcb->nonpaged->dir_children_lock)) { ExAcquireResourceSharedLite(&fcb->nonpaged->dir_children_lock, true); locked = true; } if (case_sensitive) { if (!fcb->hash_ptrs[c]) { Status = STATUS_OBJECT_NAME_NOT_FOUND; goto end; } le = fcb->hash_ptrs[c]; while (le != &fcb->dir_children_hash) { dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_hash); if (dc->hash == hash) { if (dc->name.Length == fnus.Length && RtlCompareMemory(dc->name.Buffer, fnus.Buffer, fnus.Length) == fnus.Length) { if (dc->key.obj_type == TYPE_ROOT_ITEM) { LIST_ENTRY* le2; *subvol = NULL; le2 = fcb->Vcb->roots.Flink; while (le2 != &fcb->Vcb->roots) { root* r2 = CONTAINING_RECORD(le2, root, list_entry); if (r2->id == dc->key.obj_id) { *subvol = r2; break; } le2 = le2->Flink; } *inode = SUBVOL_ROOT_INODE; } else { *subvol = fcb->subvol; *inode = dc->key.obj_id; } *pdc = dc; Status = STATUS_SUCCESS; goto end; } } else if (dc->hash > hash) { Status = STATUS_OBJECT_NAME_NOT_FOUND; goto end; } le = le->Flink; } } else { if (!fcb->hash_ptrs_uc[c]) { Status = STATUS_OBJECT_NAME_NOT_FOUND; goto end; } le = fcb->hash_ptrs_uc[c]; while (le != &fcb->dir_children_hash_uc) { dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_hash_uc); if (dc->hash_uc == hash) { if (dc->name_uc.Length == fnus.Length && RtlCompareMemory(dc->name_uc.Buffer, fnus.Buffer, fnus.Length) == fnus.Length) { if (dc->key.obj_type == TYPE_ROOT_ITEM) { LIST_ENTRY* le2; *subvol = NULL; le2 = fcb->Vcb->roots.Flink; while (le2 != &fcb->Vcb->roots) { root* r2 = CONTAINING_RECORD(le2, root, list_entry); if (r2->id == dc->key.obj_id) { *subvol = r2; break; } le2 = le2->Flink; } *inode = SUBVOL_ROOT_INODE; } else { *subvol = fcb->subvol; *inode = dc->key.obj_id; } *pdc = dc; Status = STATUS_SUCCESS; goto end; } } else if (dc->hash_uc > hash) { Status = STATUS_OBJECT_NAME_NOT_FOUND; goto end; } le = le->Flink; } } Status = STATUS_OBJECT_NAME_NOT_FOUND; end: if (locked) ExReleaseResourceLite(&fcb->nonpaged->dir_children_lock); if (!case_sensitive) ExFreePool(fnus.Buffer); return Status; } static NTSTATUS split_path(device_extension* Vcb, PUNICODE_STRING path, LIST_ENTRY* parts, bool* stream) { ULONG len, i; bool has_stream; WCHAR* buf; name_bit* nb; NTSTATUS Status; len = path->Length / sizeof(WCHAR); if (len > 0 && (path->Buffer[len - 1] == '/' || path->Buffer[len - 1] == '\\')) len--; if (len == 0 || (path->Buffer[len - 1] == '/' || path->Buffer[len - 1] == '\\')) { WARN("zero-length filename part\n"); return STATUS_OBJECT_NAME_INVALID; } has_stream = false; for (i = 0; i < len; i++) { if (path->Buffer[i] == '/' || path->Buffer[i] == '\\') { has_stream = false; } else if (path->Buffer[i] == ':') { has_stream = true; } } buf = path->Buffer; for (i = 0; i < len; i++) { if (path->Buffer[i] == '/' || path->Buffer[i] == '\\') { if (buf[0] == '/' || buf[0] == '\\') { WARN("zero-length filename part\n"); Status = STATUS_OBJECT_NAME_INVALID; goto cleanup; } nb = ExAllocateFromPagedLookasideList(&Vcb->name_bit_lookaside); if (!nb) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto cleanup; } nb->us.Buffer = buf; nb->us.Length = nb->us.MaximumLength = (USHORT)(&path->Buffer[i] - buf) * sizeof(WCHAR); InsertTailList(parts, &nb->list_entry); buf = &path->Buffer[i+1]; } } nb = ExAllocateFromPagedLookasideList(&Vcb->name_bit_lookaside); if (!nb) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto cleanup; } nb->us.Buffer = buf; nb->us.Length = nb->us.MaximumLength = (USHORT)(&path->Buffer[i] - buf) * sizeof(WCHAR); InsertTailList(parts, &nb->list_entry); if (has_stream) { static const WCHAR datasuf[] = {':','$','D','A','T','A',0}; UNICODE_STRING dsus; dsus.Buffer = (WCHAR*)datasuf; dsus.Length = dsus.MaximumLength = sizeof(datasuf) - sizeof(WCHAR); for (i = 0; i < nb->us.Length / sizeof(WCHAR); i++) { if (nb->us.Buffer[i] == ':') { name_bit* nb2; if (i + 1 == nb->us.Length / sizeof(WCHAR)) { WARN("zero-length stream name\n"); Status = STATUS_OBJECT_NAME_INVALID; goto cleanup; } nb2 = ExAllocateFromPagedLookasideList(&Vcb->name_bit_lookaside); if (!nb2) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto cleanup; } nb2->us.Buffer = &nb->us.Buffer[i+1]; nb2->us.Length = nb2->us.MaximumLength = (uint16_t)(nb->us.Length - (i * sizeof(WCHAR)) - sizeof(WCHAR)); InsertTailList(parts, &nb2->list_entry); nb->us.Length = (uint16_t)i * sizeof(WCHAR); nb->us.MaximumLength = nb->us.Length; nb = nb2; break; } } // FIXME - should comparison be case-insensitive? // remove :$DATA suffix if (nb->us.Length >= dsus.Length && RtlCompareMemory(&nb->us.Buffer[(nb->us.Length - dsus.Length)/sizeof(WCHAR)], dsus.Buffer, dsus.Length) == dsus.Length) nb->us.Length -= dsus.Length; if (nb->us.Length == 0) { RemoveTailList(parts); ExFreeToPagedLookasideList(&Vcb->name_bit_lookaside, nb); has_stream = false; } } // if path is just stream name, remove first empty item if (has_stream && path->Length >= sizeof(WCHAR) && path->Buffer[0] == ':') { name_bit *nb1 = CONTAINING_RECORD(RemoveHeadList(parts), name_bit, list_entry); ExFreeToPagedLookasideList(&Vcb->name_bit_lookaside, nb1); } *stream = has_stream; return STATUS_SUCCESS; cleanup: while (!IsListEmpty(parts)) { nb = CONTAINING_RECORD(RemoveHeadList(parts), name_bit, list_entry); ExFreeToPagedLookasideList(&Vcb->name_bit_lookaside, nb); } return Status; } NTSTATUS load_csum(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, void* csum, uint64_t start, uint64_t length, PIRP Irp) { NTSTATUS Status; KEY searchkey; traverse_ptr tp, next_tp; uint64_t i, j; bool b; void* ptr = csum; searchkey.obj_id = EXTENT_CSUM_ID; searchkey.obj_type = TYPE_EXTENT_CSUM; searchkey.offset = start; Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } i = 0; do { if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { ULONG readlen; if (start < tp.item->key.offset) j = 0; else j = ((start - tp.item->key.offset) >> Vcb->sector_shift) + i; if (j * Vcb->csum_size > tp.item->size || tp.item->key.offset > start + (i << Vcb->sector_shift)) { ERR("checksum not found for %I64x\n", start + (i << Vcb->sector_shift)); return STATUS_INTERNAL_ERROR; } readlen = (ULONG)min((tp.item->size / Vcb->csum_size) - j, length - i); RtlCopyMemory(ptr, tp.item->data + (j * Vcb->csum_size), readlen * Vcb->csum_size); ptr = (uint8_t*)ptr + (readlen * Vcb->csum_size); i += readlen; if (i == length) break; } b = find_next_item(Vcb, &tp, &next_tp, false, Irp); if (b) tp = next_tp; } while (b); if (i < length) { ERR("could not read checksums: offset %I64x, length %I64x sectors\n", start, length); return STATUS_INTERNAL_ERROR; } return STATUS_SUCCESS; } NTSTATUS load_dir_children(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, fcb* fcb, bool ignore_size, PIRP Irp) { KEY searchkey; traverse_ptr tp, next_tp; NTSTATUS Status; ULONG num_children = 0; uint64_t max_index = 2; fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG); if (!fcb->hash_ptrs) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256); fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG); if (!fcb->hash_ptrs_uc) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256); if (!ignore_size && fcb->inode_item.st_size == 0) return STATUS_SUCCESS; searchkey.obj_id = fcb->inode; searchkey.obj_type = TYPE_DIR_INDEX; searchkey.offset = 2; Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (keycmp(tp.item->key, searchkey) == -1) { if (find_next_item(Vcb, &tp, &next_tp, false, Irp)) { tp = next_tp; TRACE("moving on to %I64x,%x,%I64x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); } } while (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { DIR_ITEM* di = (DIR_ITEM*)tp.item->data; dir_child* dc; ULONG utf16len; if (tp.item->size < sizeof(DIR_ITEM)) { 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)); goto cont; } if (di->n == 0) { 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); goto cont; } Status = utf8_to_utf16(NULL, 0, &utf16len, di->name, di->n); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 1 returned %08lx\n", Status); goto cont; } dc = ExAllocatePoolWithTag(PagedPool, sizeof(dir_child), ALLOC_TAG); if (!dc) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } dc->key = di->key; dc->index = tp.item->key.offset; dc->type = di->type; dc->fileref = NULL; dc->root_dir = false; max_index = dc->index; dc->utf8.MaximumLength = dc->utf8.Length = di->n; dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, di->n, ALLOC_TAG); if (!dc->utf8.Buffer) { ERR("out of memory\n"); ExFreePool(dc); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(dc->utf8.Buffer, di->name, di->n); dc->name.MaximumLength = dc->name.Length = (uint16_t)utf16len; dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, dc->name.MaximumLength, ALLOC_TAG); if (!dc->name.Buffer) { ERR("out of memory\n"); ExFreePool(dc->utf8.Buffer); ExFreePool(dc); return STATUS_INSUFFICIENT_RESOURCES; } Status = utf8_to_utf16(dc->name.Buffer, utf16len, &utf16len, di->name, di->n); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 2 returned %08lx\n", Status); ExFreePool(dc->utf8.Buffer); ExFreePool(dc->name.Buffer); ExFreePool(dc); goto cont; } Status = RtlUpcaseUnicodeString(&dc->name_uc, &dc->name, true); if (!NT_SUCCESS(Status)) { ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); ExFreePool(dc->utf8.Buffer); ExFreePool(dc->name.Buffer); ExFreePool(dc); goto cont; } dc->hash = calc_crc32c(0xffffffff, (uint8_t*)dc->name.Buffer, dc->name.Length); dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)dc->name_uc.Buffer, dc->name_uc.Length); InsertTailList(&fcb->dir_children_index, &dc->list_entry_index); insert_dir_child_into_hash_lists(fcb, dc); num_children++; cont: if (find_next_item(Vcb, &tp, &next_tp, false, Irp)) tp = next_tp; else break; } if (!Vcb->options.no_root_dir && fcb->inode == SUBVOL_ROOT_INODE) { root* top_subvol; if (Vcb->root_fileref && Vcb->root_fileref->fcb) top_subvol = Vcb->root_fileref->fcb->subvol; else top_subvol = find_default_subvol(Vcb, NULL); if (fcb->subvol == top_subvol && top_subvol->id != BTRFS_ROOT_FSTREE) { dir_child* dc = ExAllocatePoolWithTag(PagedPool, sizeof(dir_child), ALLOC_TAG); if (!dc) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } dc->key.obj_id = BTRFS_ROOT_FSTREE; dc->key.obj_type = TYPE_ROOT_ITEM; dc->key.offset = 0; dc->index = max_index + 1; dc->type = BTRFS_TYPE_DIRECTORY; dc->fileref = NULL; dc->root_dir = true; dc->utf8.MaximumLength = dc->utf8.Length = sizeof(root_dir) - sizeof(char); dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, sizeof(root_dir) - sizeof(char), ALLOC_TAG); if (!dc->utf8.Buffer) { ERR("out of memory\n"); ExFreePool(dc); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(dc->utf8.Buffer, root_dir, sizeof(root_dir) - sizeof(char)); dc->name.MaximumLength = dc->name.Length = sizeof(root_dir_utf16) - sizeof(WCHAR); dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, sizeof(root_dir_utf16) - sizeof(WCHAR), ALLOC_TAG); if (!dc->name.Buffer) { ERR("out of memory\n"); ExFreePool(dc->utf8.Buffer); ExFreePool(dc); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(dc->name.Buffer, root_dir_utf16, sizeof(root_dir_utf16) - sizeof(WCHAR)); Status = RtlUpcaseUnicodeString(&dc->name_uc, &dc->name, true); if (!NT_SUCCESS(Status)) { ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); ExFreePool(dc->utf8.Buffer); ExFreePool(dc->name.Buffer); ExFreePool(dc); goto cont; } dc->hash = calc_crc32c(0xffffffff, (uint8_t*)dc->name.Buffer, dc->name.Length); dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)dc->name_uc.Buffer, dc->name_uc.Length); InsertTailList(&fcb->dir_children_index, &dc->list_entry_index); insert_dir_child_into_hash_lists(fcb, dc); } } return STATUS_SUCCESS; } NTSTATUS open_fcb(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb, root* subvol, uint64_t inode, uint8_t type, PANSI_STRING utf8, bool always_add_hl, fcb* parent, fcb** pfcb, POOL_TYPE pooltype, PIRP Irp) { KEY searchkey; traverse_ptr tp, next_tp; NTSTATUS Status; fcb *fcb, *deleted_fcb = NULL; bool atts_set = false, sd_set = false, no_data; LIST_ENTRY* lastle = NULL; EXTENT_DATA* ed = NULL; uint64_t fcbs_version = 0; uint32_t hash; hash = calc_crc32c(0xffffffff, (uint8_t*)&inode, sizeof(uint64_t)); acquire_fcb_lock_shared(Vcb); if (subvol->fcbs_ptrs[hash >> 24]) { LIST_ENTRY* le = subvol->fcbs_ptrs[hash >> 24]; while (le != &subvol->fcbs) { fcb = CONTAINING_RECORD(le, struct _fcb, list_entry); if (fcb->inode == inode) { if (!fcb->ads) { if (fcb->deleted) deleted_fcb = fcb; else { #ifdef DEBUG_FCB_REFCOUNTS LONG rc = InterlockedIncrement(&fcb->refcount); WARN("fcb %p: refcount now %i (subvol %I64x, inode %I64x)\n", fcb, rc, fcb->subvol->id, fcb->inode); #else InterlockedIncrement(&fcb->refcount); #endif *pfcb = fcb; release_fcb_lock(Vcb); return STATUS_SUCCESS; } } } else if (fcb->hash > hash) { if (deleted_fcb) { InterlockedIncrement(&deleted_fcb->refcount); *pfcb = deleted_fcb; release_fcb_lock(Vcb); return STATUS_SUCCESS; } lastle = le->Blink; fcbs_version = subvol->fcbs_version; break; } le = le->Flink; } } release_fcb_lock(Vcb); if (deleted_fcb) { InterlockedIncrement(&deleted_fcb->refcount); *pfcb = deleted_fcb; return STATUS_SUCCESS; } fcb = create_fcb(Vcb, pooltype); if (!fcb) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } fcb->Vcb = Vcb; fcb->subvol = subvol; fcb->inode = inode; fcb->hash = hash; fcb->type = type; searchkey.obj_id = inode; searchkey.obj_type = TYPE_INODE_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, subvol, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); reap_fcb(fcb); return Status; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { WARN("couldn't find INODE_ITEM for inode %I64x in subvol %I64x\n", inode, subvol->id); reap_fcb(fcb); return STATUS_INVALID_PARAMETER; } if (tp.item->size > 0) RtlCopyMemory(&fcb->inode_item, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size)); if (fcb->type == 0) { // guess the type from the inode mode, if the caller doesn't know already if ((fcb->inode_item.st_mode & __S_IFDIR) == __S_IFDIR) fcb->type = BTRFS_TYPE_DIRECTORY; else if ((fcb->inode_item.st_mode & __S_IFCHR) == __S_IFCHR) fcb->type = BTRFS_TYPE_CHARDEV; else if ((fcb->inode_item.st_mode & __S_IFBLK) == __S_IFBLK) fcb->type = BTRFS_TYPE_BLOCKDEV; else if ((fcb->inode_item.st_mode & __S_IFIFO) == __S_IFIFO) fcb->type = BTRFS_TYPE_FIFO; else if ((fcb->inode_item.st_mode & __S_IFLNK) == __S_IFLNK) fcb->type = BTRFS_TYPE_SYMLINK; else if ((fcb->inode_item.st_mode & __S_IFSOCK) == __S_IFSOCK) fcb->type = BTRFS_TYPE_SOCKET; else fcb->type = BTRFS_TYPE_FILE; } no_data = fcb->inode_item.st_size == 0 || (fcb->type != BTRFS_TYPE_FILE && fcb->type != BTRFS_TYPE_SYMLINK); while (find_next_item(Vcb, &tp, &next_tp, false, Irp)) { tp = next_tp; if (tp.item->key.obj_id > inode) break; if ((no_data && tp.item->key.obj_type > TYPE_XATTR_ITEM) || tp.item->key.obj_type > TYPE_EXTENT_DATA) break; if ((always_add_hl || fcb->inode_item.st_nlink > 1) && tp.item->key.obj_type == TYPE_INODE_REF) { ULONG len; INODE_REF* ir; len = tp.item->size; ir = (INODE_REF*)tp.item->data; while (len >= sizeof(INODE_REF) - 1) { hardlink* hl; ULONG stringlen; hl = ExAllocatePoolWithTag(pooltype, sizeof(hardlink), ALLOC_TAG); if (!hl) { ERR("out of memory\n"); reap_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } hl->parent = tp.item->key.offset; hl->index = ir->index; hl->utf8.Length = hl->utf8.MaximumLength = ir->n; if (hl->utf8.Length > 0) { hl->utf8.Buffer = ExAllocatePoolWithTag(pooltype, hl->utf8.MaximumLength, ALLOC_TAG); RtlCopyMemory(hl->utf8.Buffer, ir->name, ir->n); } Status = utf8_to_utf16(NULL, 0, &stringlen, ir->name, ir->n); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 1 returned %08lx\n", Status); ExFreePool(hl); reap_fcb(fcb); return Status; } hl->name.Length = hl->name.MaximumLength = (uint16_t)stringlen; if (stringlen == 0) hl->name.Buffer = NULL; else { hl->name.Buffer = ExAllocatePoolWithTag(pooltype, hl->name.MaximumLength, ALLOC_TAG); if (!hl->name.Buffer) { ERR("out of memory\n"); ExFreePool(hl); reap_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } Status = utf8_to_utf16(hl->name.Buffer, stringlen, &stringlen, ir->name, ir->n); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 2 returned %08lx\n", Status); ExFreePool(hl->name.Buffer); ExFreePool(hl); reap_fcb(fcb); return Status; } } InsertTailList(&fcb->hardlinks, &hl->list_entry); len -= sizeof(INODE_REF) - 1 + ir->n; ir = (INODE_REF*)&ir->name[ir->n]; } } else if ((always_add_hl || fcb->inode_item.st_nlink > 1) && tp.item->key.obj_type == TYPE_INODE_EXTREF) { ULONG len; INODE_EXTREF* ier; len = tp.item->size; ier = (INODE_EXTREF*)tp.item->data; while (len >= sizeof(INODE_EXTREF) - 1) { hardlink* hl; ULONG stringlen; hl = ExAllocatePoolWithTag(pooltype, sizeof(hardlink), ALLOC_TAG); if (!hl) { ERR("out of memory\n"); reap_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } hl->parent = ier->dir; hl->index = ier->index; hl->utf8.Length = hl->utf8.MaximumLength = ier->n; if (hl->utf8.Length > 0) { hl->utf8.Buffer = ExAllocatePoolWithTag(pooltype, hl->utf8.MaximumLength, ALLOC_TAG); RtlCopyMemory(hl->utf8.Buffer, ier->name, ier->n); } Status = utf8_to_utf16(NULL, 0, &stringlen, ier->name, ier->n); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 1 returned %08lx\n", Status); ExFreePool(hl); reap_fcb(fcb); return Status; } hl->name.Length = hl->name.MaximumLength = (uint16_t)stringlen; if (stringlen == 0) hl->name.Buffer = NULL; else { hl->name.Buffer = ExAllocatePoolWithTag(pooltype, hl->name.MaximumLength, ALLOC_TAG); if (!hl->name.Buffer) { ERR("out of memory\n"); ExFreePool(hl); reap_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } Status = utf8_to_utf16(hl->name.Buffer, stringlen, &stringlen, ier->name, ier->n); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 2 returned %08lx\n", Status); ExFreePool(hl->name.Buffer); ExFreePool(hl); reap_fcb(fcb); return Status; } } InsertTailList(&fcb->hardlinks, &hl->list_entry); len -= sizeof(INODE_EXTREF) - 1 + ier->n; ier = (INODE_EXTREF*)&ier->name[ier->n]; } } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) { ULONG len; DIR_ITEM* di; static const char xapref[] = "user."; if (tp.item->size < offsetof(DIR_ITEM, name[0])) { 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])); continue; } len = tp.item->size; di = (DIR_ITEM*)tp.item->data; do { if (len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) break; if (tp.item->key.offset == EA_REPARSE_HASH && di->n == sizeof(EA_REPARSE) - 1 && RtlCompareMemory(EA_REPARSE, di->name, di->n) == di->n) { if (di->m > 0) { fcb->reparse_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, di->m, ALLOC_TAG); if (!fcb->reparse_xattr.Buffer) { ERR("out of memory\n"); reap_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(fcb->reparse_xattr.Buffer, &di->name[di->n], di->m); } else fcb->reparse_xattr.Buffer = NULL; fcb->reparse_xattr.Length = fcb->reparse_xattr.MaximumLength = di->m; } else if (tp.item->key.offset == EA_EA_HASH && di->n == sizeof(EA_EA) - 1 && RtlCompareMemory(EA_EA, di->name, di->n) == di->n) { if (di->m > 0) { ULONG offset; Status = IoCheckEaBufferValidity((FILE_FULL_EA_INFORMATION*)&di->name[di->n], di->m, &offset); if (!NT_SUCCESS(Status)) WARN("IoCheckEaBufferValidity returned %08lx (error at offset %lu)\n", Status, offset); else { FILE_FULL_EA_INFORMATION* eainfo; fcb->ea_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, di->m, ALLOC_TAG); if (!fcb->ea_xattr.Buffer) { ERR("out of memory\n"); reap_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(fcb->ea_xattr.Buffer, &di->name[di->n], di->m); fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = di->m; fcb->ealen = 4; // calculate ealen eainfo = (FILE_FULL_EA_INFORMATION*)&di->name[di->n]; do { fcb->ealen += 5 + eainfo->EaNameLength + eainfo->EaValueLength; if (eainfo->NextEntryOffset == 0) break; eainfo = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)eainfo) + eainfo->NextEntryOffset); } while (true); } } } else if (tp.item->key.offset == EA_DOSATTRIB_HASH && di->n == sizeof(EA_DOSATTRIB) - 1 && RtlCompareMemory(EA_DOSATTRIB, di->name, di->n) == di->n) { if (di->m > 0) { if (get_file_attributes_from_xattr(&di->name[di->n], di->m, &fcb->atts)) { atts_set = true; if (fcb->type == BTRFS_TYPE_DIRECTORY) fcb->atts |= FILE_ATTRIBUTE_DIRECTORY; else if (fcb->type == BTRFS_TYPE_SYMLINK) fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT; if (fcb->type != BTRFS_TYPE_DIRECTORY) fcb->atts &= ~FILE_ATTRIBUTE_DIRECTORY; if (inode == SUBVOL_ROOT_INODE) { if (subvol->root_item.flags & BTRFS_SUBVOL_READONLY) fcb->atts |= FILE_ATTRIBUTE_READONLY; else fcb->atts &= ~FILE_ATTRIBUTE_READONLY; } } } } else if (tp.item->key.offset == EA_NTACL_HASH && di->n == sizeof(EA_NTACL) - 1 && RtlCompareMemory(EA_NTACL, di->name, di->n) == di->n) { if (di->m > 0) { fcb->sd = ExAllocatePoolWithTag(PagedPool, di->m, ALLOC_TAG); if (!fcb->sd) { ERR("out of memory\n"); reap_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(fcb->sd, &di->name[di->n], di->m); // We have to test against our copy rather than the source, as RtlValidRelativeSecurityDescriptor // will fail if the ACLs aren't 32-bit aligned. if (!RtlValidRelativeSecurityDescriptor(fcb->sd, di->m, 0)) ExFreePool(fcb->sd); else sd_set = true; } } 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) { if (di->m > 0) { static const char lzo[] = "lzo"; static const char zlib[] = "zlib"; static const char zstd[] = "zstd"; if (di->m == sizeof(lzo) - 1 && RtlCompareMemory(&di->name[di->n], lzo, di->m) == di->m) fcb->prop_compression = PropCompression_LZO; else if (di->m == sizeof(zlib) - 1 && RtlCompareMemory(&di->name[di->n], zlib, di->m) == di->m) fcb->prop_compression = PropCompression_Zlib; else if (di->m == sizeof(zstd) - 1 && RtlCompareMemory(&di->name[di->n], zstd, di->m) == di->m) fcb->prop_compression = PropCompression_ZSTD; else fcb->prop_compression = PropCompression_None; } } 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) { if (di->m > 0) { fcb->case_sensitive = di->m == 1 && di->name[di->n] == '1'; fcb->case_sensitive_set = true; } } else if (di->n > sizeof(xapref) - 1 && RtlCompareMemory(xapref, di->name, sizeof(xapref) - 1) == sizeof(xapref) - 1) { dir_child* dc; ULONG utf16len; Status = utf8_to_utf16(NULL, 0, &utf16len, &di->name[sizeof(xapref) - 1], di->n + 1 - sizeof(xapref)); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 1 returned %08lx\n", Status); reap_fcb(fcb); return Status; } dc = ExAllocatePoolWithTag(PagedPool, sizeof(dir_child), ALLOC_TAG); if (!dc) { ERR("out of memory\n"); reap_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(dc, sizeof(dir_child)); dc->utf8.MaximumLength = dc->utf8.Length = di->n + 1 - sizeof(xapref); dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, dc->utf8.MaximumLength, ALLOC_TAG); if (!dc->utf8.Buffer) { ERR("out of memory\n"); ExFreePool(dc); reap_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(dc->utf8.Buffer, &di->name[sizeof(xapref) - 1], dc->utf8.Length); dc->name.MaximumLength = dc->name.Length = (uint16_t)utf16len; dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, dc->name.MaximumLength, ALLOC_TAG); if (!dc->name.Buffer) { ERR("out of memory\n"); ExFreePool(dc->utf8.Buffer); ExFreePool(dc); reap_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } Status = utf8_to_utf16(dc->name.Buffer, utf16len, &utf16len, dc->utf8.Buffer, dc->utf8.Length); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 2 returned %08lx\n", Status); ExFreePool(dc->utf8.Buffer); ExFreePool(dc->name.Buffer); ExFreePool(dc); reap_fcb(fcb); return Status; } Status = RtlUpcaseUnicodeString(&dc->name_uc, &dc->name, true); if (!NT_SUCCESS(Status)) { ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); ExFreePool(dc->utf8.Buffer); ExFreePool(dc->name.Buffer); ExFreePool(dc); reap_fcb(fcb); return Status; } dc->size = di->m; InsertTailList(&fcb->dir_children_index, &dc->list_entry_index); } else { xattr* xa; xa = ExAllocatePoolWithTag(PagedPool, offsetof(xattr, data[0]) + di->m + di->n, ALLOC_TAG); if (!xa) { ERR("out of memory\n"); reap_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } xa->namelen = di->n; xa->valuelen = di->m; xa->dirty = false; RtlCopyMemory(xa->data, di->name, di->m + di->n); InsertTailList(&fcb->xattrs, &xa->list_entry); } len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n; if (len < offsetof(DIR_ITEM, name[0])) break; di = (DIR_ITEM*)&di->name[di->m + di->n]; } while (true); } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) { extent* ext; bool unique = false; ed = (EXTENT_DATA*)tp.item->data; if (tp.item->size < sizeof(EXTENT_DATA)) { 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_DATA)); reap_fcb(fcb); return STATUS_INTERNAL_ERROR; } if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)&ed->data[0]; if (tp.item->size < sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) { 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_DATA) - 1 + sizeof(EXTENT_DATA2)); reap_fcb(fcb); return STATUS_INTERNAL_ERROR; } if (ed2->address == 0 || ed2->size == 0) // sparse continue; if (ed2->size != 0 && is_tree_unique(Vcb, tp.tree, Irp)) unique = is_extent_unique(Vcb, ed2->address, ed2->size, Irp); } ext = ExAllocatePoolWithTag(pooltype, offsetof(extent, extent_data) + tp.item->size, ALLOC_TAG); if (!ext) { ERR("out of memory\n"); reap_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } ext->offset = tp.item->key.offset; RtlCopyMemory(&ext->extent_data, tp.item->data, tp.item->size); ext->datalen = tp.item->size; ext->unique = unique; ext->ignore = false; ext->inserted = false; ext->csum = NULL; InsertTailList(&fcb->extents, &ext->list_entry); } } if (fcb->type == BTRFS_TYPE_DIRECTORY) { Status = load_dir_children(Vcb, fcb, false, Irp); if (!NT_SUCCESS(Status)) { ERR("load_dir_children returned %08lx\n", Status); reap_fcb(fcb); return Status; } } if (no_data) { fcb->Header.AllocationSize.QuadPart = 0; fcb->Header.FileSize.QuadPart = 0; fcb->Header.ValidDataLength.QuadPart = 0; } else { if (ed && ed->type == EXTENT_TYPE_INLINE) fcb->Header.AllocationSize.QuadPart = fcb->inode_item.st_size; else fcb->Header.AllocationSize.QuadPart = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size); fcb->Header.FileSize.QuadPart = fcb->inode_item.st_size; fcb->Header.ValidDataLength.QuadPart = fcb->inode_item.st_size; } if (!atts_set) fcb->atts = get_file_attributes(Vcb, fcb->subvol, fcb->inode, fcb->type, utf8 && utf8->Buffer[0] == '.', true, Irp); if (!sd_set) fcb_get_sd(fcb, parent, false, Irp); acquire_fcb_lock_exclusive(Vcb); if (lastle && subvol->fcbs_version == fcbs_version) { InsertHeadList(lastle, &fcb->list_entry); if (!subvol->fcbs_ptrs[hash >> 24] || CONTAINING_RECORD(subvol->fcbs_ptrs[hash >> 24], struct _fcb, list_entry)->hash > hash) subvol->fcbs_ptrs[hash >> 24] = &fcb->list_entry; } else { lastle = NULL; if (subvol->fcbs_ptrs[hash >> 24]) { LIST_ENTRY* le = subvol->fcbs_ptrs[hash >> 24]; while (le != &subvol->fcbs) { struct _fcb* fcb2 = CONTAINING_RECORD(le, struct _fcb, list_entry); if (fcb2->inode == inode) { if (!fcb2->ads) { if (fcb2->deleted) deleted_fcb = fcb2; else { #ifdef DEBUG_FCB_REFCOUNTS LONG rc = InterlockedIncrement(&fcb2->refcount); WARN("fcb %p: refcount now %i (subvol %I64x, inode %I64x)\n", fcb2, rc, fcb2->subvol->id, fcb2->inode); #else InterlockedIncrement(&fcb2->refcount); #endif *pfcb = fcb2; reap_fcb(fcb); release_fcb_lock(Vcb); return STATUS_SUCCESS; } } } else if (fcb2->hash > hash) { if (deleted_fcb) { InterlockedIncrement(&deleted_fcb->refcount); *pfcb = deleted_fcb; reap_fcb(fcb); release_fcb_lock(Vcb); return STATUS_SUCCESS; } lastle = le->Blink; break; } le = le->Flink; } } if (fcb->type == BTRFS_TYPE_DIRECTORY && fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT && fcb->reparse_xattr.Length == 0) { fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT; if (!Vcb->readonly && !is_subvol_readonly(subvol, Irp)) { fcb->atts_changed = true; mark_fcb_dirty(fcb); } } if (!lastle) { uint8_t c = hash >> 24; if (c != 0xff) { uint8_t d = c + 1; do { if (subvol->fcbs_ptrs[d]) { lastle = subvol->fcbs_ptrs[d]->Blink; break; } d++; } while (d != 0); } } if (lastle) { InsertHeadList(lastle, &fcb->list_entry); if (lastle == &subvol->fcbs || (CONTAINING_RECORD(lastle, struct _fcb, list_entry)->hash >> 24) != (hash >> 24)) subvol->fcbs_ptrs[hash >> 24] = &fcb->list_entry; } else { InsertTailList(&subvol->fcbs, &fcb->list_entry); if (fcb->list_entry.Blink == &subvol->fcbs || (CONTAINING_RECORD(fcb->list_entry.Blink, struct _fcb, list_entry)->hash >> 24) != (hash >> 24)) subvol->fcbs_ptrs[hash >> 24] = &fcb->list_entry; } } if (fcb->inode == SUBVOL_ROOT_INODE && fcb->subvol->id == BTRFS_ROOT_FSTREE && fcb->subvol != Vcb->root_fileref->fcb->subvol) fcb->atts |= FILE_ATTRIBUTE_HIDDEN; subvol->fcbs_version++; InsertTailList(&Vcb->all_fcbs, &fcb->list_entry_all); release_fcb_lock(Vcb); fcb->Header.IsFastIoPossible = fast_io_possible(fcb); *pfcb = fcb; return STATUS_SUCCESS; } static NTSTATUS open_fcb_stream(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb, dir_child* dc, fcb* parent, fcb** pfcb, PIRP Irp) { fcb* fcb; uint8_t* xattrdata; uint16_t xattrlen, overhead; NTSTATUS Status; KEY searchkey; traverse_ptr tp; static const char xapref[] = "user."; ANSI_STRING xattr; uint32_t crc32; xattr.Length = sizeof(xapref) - 1 + dc->utf8.Length; xattr.MaximumLength = xattr.Length + 1; xattr.Buffer = ExAllocatePoolWithTag(PagedPool, xattr.MaximumLength, ALLOC_TAG); if (!xattr.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(xattr.Buffer, xapref, sizeof(xapref) - 1); RtlCopyMemory(&xattr.Buffer[sizeof(xapref) - 1], dc->utf8.Buffer, dc->utf8.Length); xattr.Buffer[xattr.Length] = 0; fcb = create_fcb(Vcb, PagedPool); if (!fcb) { ERR("out of memory\n"); ExFreePool(xattr.Buffer); return STATUS_INSUFFICIENT_RESOURCES; } fcb->Vcb = Vcb; crc32 = calc_crc32c(0xfffffffe, (uint8_t*)xattr.Buffer, xattr.Length); if (!get_xattr(Vcb, parent->subvol, parent->inode, xattr.Buffer, crc32, &xattrdata, &xattrlen, Irp)) { ERR("get_xattr failed\n"); reap_fcb(fcb); ExFreePool(xattr.Buffer); return STATUS_INTERNAL_ERROR; } fcb->subvol = parent->subvol; fcb->inode = parent->inode; fcb->type = parent->type; fcb->ads = true; fcb->adshash = crc32; fcb->adsxattr = xattr; // find XATTR_ITEM overhead and hence calculate maximum length searchkey.obj_id = parent->inode; searchkey.obj_type = TYPE_XATTR_ITEM; searchkey.offset = crc32; Status = find_item(Vcb, parent->subvol, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); reap_fcb(fcb); return Status; } if (keycmp(tp.item->key, searchkey)) { ERR("error - could not find key for xattr\n"); reap_fcb(fcb); return STATUS_INTERNAL_ERROR; } if (tp.item->size < xattrlen) { 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); reap_fcb(fcb); return STATUS_INTERNAL_ERROR; } overhead = tp.item->size - xattrlen; fcb->adsmaxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) - overhead; fcb->adsdata.Buffer = (char*)xattrdata; fcb->adsdata.Length = fcb->adsdata.MaximumLength = xattrlen; fcb->Header.IsFastIoPossible = fast_io_possible(fcb); fcb->Header.AllocationSize.QuadPart = xattrlen; fcb->Header.FileSize.QuadPart = xattrlen; fcb->Header.ValidDataLength.QuadPart = xattrlen; TRACE("stream found: size = %x, hash = %08x\n", xattrlen, fcb->adshash); *pfcb = fcb; return STATUS_SUCCESS; } NTSTATUS open_fileref_child(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) _In_ device_extension* Vcb, _In_ file_ref* sf, _In_ PUNICODE_STRING name, _In_ bool case_sensitive, _In_ bool lastpart, _In_ bool streampart, _In_ POOL_TYPE pooltype, _Out_ file_ref** psf2, _In_opt_ PIRP Irp) { NTSTATUS Status; file_ref* sf2; if (sf->fcb == Vcb->dummy_fcb) return STATUS_OBJECT_NAME_NOT_FOUND; if (streampart) { bool locked = false; LIST_ENTRY* le; UNICODE_STRING name_uc; dir_child* dc = NULL; fcb* fcb; struct _fcb* duff_fcb = NULL; file_ref* duff_fr = NULL; if (!case_sensitive) { Status = RtlUpcaseUnicodeString(&name_uc, name, true); if (!NT_SUCCESS(Status)) { ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); return Status; } } if (!ExIsResourceAcquiredSharedLite(&sf->fcb->nonpaged->dir_children_lock)) { ExAcquireResourceSharedLite(&sf->fcb->nonpaged->dir_children_lock, true); locked = true; } le = sf->fcb->dir_children_index.Flink; while (le != &sf->fcb->dir_children_index) { dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_index); if (dc2->index == 0) { if ((case_sensitive && dc2->name.Length == name->Length && RtlCompareMemory(dc2->name.Buffer, name->Buffer, dc2->name.Length) == dc2->name.Length) || (!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) ) { dc = dc2; break; } } else break; le = le->Flink; } if (!dc) { if (locked) ExReleaseResourceLite(&sf->fcb->nonpaged->dir_children_lock); if (!case_sensitive) ExFreePool(name_uc.Buffer); return STATUS_OBJECT_NAME_NOT_FOUND; } if (dc->fileref) { if (locked) ExReleaseResourceLite(&sf->fcb->nonpaged->dir_children_lock); if (!case_sensitive) ExFreePool(name_uc.Buffer); increase_fileref_refcount(dc->fileref); *psf2 = dc->fileref; return STATUS_SUCCESS; } if (locked) ExReleaseResourceLite(&sf->fcb->nonpaged->dir_children_lock); if (!case_sensitive) ExFreePool(name_uc.Buffer); Status = open_fcb_stream(Vcb, dc, sf->fcb, &fcb, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fcb_stream returned %08lx\n", Status); return Status; } fcb->hash = sf->fcb->hash; acquire_fcb_lock_exclusive(Vcb); if (sf->fcb->subvol->fcbs_ptrs[fcb->hash >> 24]) { le = sf->fcb->subvol->fcbs_ptrs[fcb->hash >> 24]; while (le != &sf->fcb->subvol->fcbs) { struct _fcb* fcb2 = CONTAINING_RECORD(le, struct _fcb, list_entry); if (fcb2->inode == fcb->inode) { if (fcb2->ads && fcb2->adshash == fcb->adshash) { // FIXME - handle hash collisions duff_fcb = fcb; fcb = fcb2; break; } } else if (fcb2->hash > fcb->hash) break; le = le->Flink; } } if (!duff_fcb) { InsertHeadList(&sf->fcb->list_entry, &fcb->list_entry); InsertTailList(&Vcb->all_fcbs, &fcb->list_entry_all); fcb->subvol->fcbs_version++; } release_fcb_lock(Vcb); if (duff_fcb) { reap_fcb(duff_fcb); InterlockedIncrement(&fcb->refcount); } sf2 = create_fileref(Vcb); if (!sf2) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } ExAcquireResourceExclusiveLite(&sf->fcb->nonpaged->dir_children_lock, true); if (dc->fileref) { duff_fr = sf2; sf2 = dc->fileref; increase_fileref_refcount(sf2); } else { sf2->fcb = fcb; sf2->parent = (struct _file_ref*)sf; sf2->dc = dc; dc->fileref = sf2; increase_fileref_refcount(sf); InsertTailList(&sf->children, &sf2->list_entry); } ExReleaseResourceLite(&sf->fcb->nonpaged->dir_children_lock); if (duff_fr) ExFreeToPagedLookasideList(&Vcb->fileref_lookaside, duff_fr); } else { root* subvol; uint64_t inode; dir_child* dc; Status = find_file_in_dir(name, sf->fcb, &subvol, &inode, &dc, case_sensitive); if (Status == STATUS_OBJECT_NAME_NOT_FOUND) { TRACE("could not find %.*S\n", (int)(name->Length / sizeof(WCHAR)), name->Buffer); return lastpart ? STATUS_OBJECT_NAME_NOT_FOUND : STATUS_OBJECT_PATH_NOT_FOUND; } else if (Status == STATUS_OBJECT_NAME_INVALID) { TRACE("invalid filename: %.*S\n", (int)(name->Length / sizeof(WCHAR)), name->Buffer); return Status; } else if (!NT_SUCCESS(Status)) { ERR("find_file_in_dir returned %08lx\n", Status); return Status; } else { fcb* fcb; file_ref* duff_fr = NULL; if (dc->fileref) { if (!lastpart && dc->type != BTRFS_TYPE_DIRECTORY) { TRACE("passed path including file as subdirectory\n"); return STATUS_OBJECT_PATH_NOT_FOUND; } InterlockedIncrement(&dc->fileref->refcount); *psf2 = dc->fileref; return STATUS_SUCCESS; } if (!subvol || (subvol != Vcb->root_fileref->fcb->subvol && inode == SUBVOL_ROOT_INODE && subvol->parent != sf->fcb->subvol->id && !dc->root_dir)) { fcb = Vcb->dummy_fcb; InterlockedIncrement(&fcb->refcount); } else { Status = open_fcb(Vcb, subvol, inode, dc->type, &dc->utf8, false, sf->fcb, &fcb, pooltype, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fcb returned %08lx\n", Status); return Status; } } if (dc->type != BTRFS_TYPE_DIRECTORY && !lastpart && !(fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT)) { TRACE("passed path including file as subdirectory\n"); free_fcb(fcb); return STATUS_OBJECT_PATH_NOT_FOUND; } sf2 = create_fileref(Vcb); if (!sf2) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } sf2->fcb = fcb; ExAcquireResourceExclusiveLite(&sf->fcb->nonpaged->dir_children_lock, true); if (!dc->fileref) { sf2->parent = (struct _file_ref*)sf; sf2->dc = dc; dc->fileref = sf2; InsertTailList(&sf->children, &sf2->list_entry); increase_fileref_refcount(sf); if (dc->type == BTRFS_TYPE_DIRECTORY) fcb->fileref = sf2; } else { duff_fr = sf2; sf2 = dc->fileref; increase_fileref_refcount(sf2); } ExReleaseResourceLite(&sf->fcb->nonpaged->dir_children_lock); if (duff_fr) reap_fileref(Vcb, duff_fr); } } *psf2 = sf2; return STATUS_SUCCESS; } NTSTATUS open_fileref(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) _In_ device_extension* Vcb, _Out_ file_ref** pfr, _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, _In_ bool case_sensitive, _In_opt_ PIRP Irp) { UNICODE_STRING fnus2; file_ref *dir, *sf, *sf2; LIST_ENTRY parts; bool has_stream = false; NTSTATUS Status; LIST_ENTRY* le; TRACE("(%p, %p, %p, %u, %p)\n", Vcb, pfr, related, parent, parsed); if (Vcb->removing || Vcb->locked) return STATUS_ACCESS_DENIED; fnus2 = *fnus; if (fnus2.Length < sizeof(WCHAR) && !related) { ERR("error - fnus was too short\n"); return STATUS_INTERNAL_ERROR; } if (related && fnus->Length == 0) { increase_fileref_refcount(related); *pfr = related; return STATUS_SUCCESS; } if (related) { dir = related; } else { if (fnus2.Buffer[0] != '\\') { ERR("error - filename %.*S did not begin with \\\n", (int)(fnus2.Length / sizeof(WCHAR)), fnus2.Buffer); return STATUS_OBJECT_PATH_NOT_FOUND; } // if path starts with two backslashes, ignore one of them if (fnus2.Length >= 2 * sizeof(WCHAR) && fnus2.Buffer[1] == '\\') { fnus2.Buffer++; fnus2.Length -= sizeof(WCHAR); fnus2.MaximumLength -= sizeof(WCHAR); } if (fnus2.Length == sizeof(WCHAR)) { if (Vcb->root_fileref->open_count == 0 && !(Vcb->Vpb->Flags & VPB_MOUNTED)) // don't allow root to be opened on unmounted FS return STATUS_DEVICE_NOT_READY; increase_fileref_refcount(Vcb->root_fileref); *pfr = Vcb->root_fileref; if (fn_offset) *fn_offset = 0; return STATUS_SUCCESS; } else if (fnus2.Length >= 2 * sizeof(WCHAR) && fnus2.Buffer[1] == '\\') return STATUS_OBJECT_NAME_INVALID; dir = Vcb->root_fileref; fnus2.Buffer++; fnus2.Length -= sizeof(WCHAR); fnus2.MaximumLength -= sizeof(WCHAR); } if (dir->fcb->type != BTRFS_TYPE_DIRECTORY && (fnus->Length < sizeof(WCHAR) || fnus->Buffer[0] != ':')) { WARN("passed related fileref which isn't a directory (fnus = %.*S)\n", (int)(fnus->Length / sizeof(WCHAR)), fnus->Buffer); return STATUS_OBJECT_PATH_NOT_FOUND; } InitializeListHead(&parts); if (fnus->Length != 0 && (fnus->Length != sizeof(datastring) - sizeof(WCHAR) || RtlCompareMemory(fnus->Buffer, datastring, sizeof(datastring) - sizeof(WCHAR)) != sizeof(datastring) - sizeof(WCHAR))) { Status = split_path(Vcb, &fnus2, &parts, &has_stream); if (!NT_SUCCESS(Status)) { ERR("split_path returned %08lx\n", Status); return Status; } } sf = dir; increase_fileref_refcount(dir); if (parent && !IsListEmpty(&parts)) { name_bit* nb; nb = CONTAINING_RECORD(RemoveTailList(&parts), name_bit, list_entry); ExFreeToPagedLookasideList(&Vcb->name_bit_lookaside, nb); if (has_stream && !IsListEmpty(&parts)) { nb = CONTAINING_RECORD(RemoveTailList(&parts), name_bit, list_entry); ExFreeToPagedLookasideList(&Vcb->name_bit_lookaside, nb); has_stream = false; } } if (IsListEmpty(&parts)) { Status = STATUS_SUCCESS; *pfr = dir; if (fn_offset) *fn_offset = 0; goto end2; } le = parts.Flink; do { name_bit* nb = CONTAINING_RECORD(le, name_bit, list_entry); bool lastpart = le->Flink == &parts || (has_stream && le->Flink->Flink == &parts); bool streampart = has_stream && le->Flink == &parts; bool cs = case_sensitive; if (!cs) { if (streampart && sf->parent) cs = sf->parent->fcb->case_sensitive; else cs = sf->fcb->case_sensitive; } Status = open_fileref_child(Vcb, sf, &nb->us, cs, lastpart, streampart, pooltype, &sf2, Irp); if (!NT_SUCCESS(Status)) { if (Status == STATUS_OBJECT_PATH_NOT_FOUND || Status == STATUS_OBJECT_NAME_NOT_FOUND || Status == STATUS_OBJECT_NAME_INVALID) TRACE("open_fileref_child returned %08lx\n", Status); else ERR("open_fileref_child returned %08lx\n", Status); goto end; } if (le->Flink == &parts) { // last entry if (fn_offset) { if (has_stream) nb = CONTAINING_RECORD(le->Blink, name_bit, list_entry); *fn_offset = (ULONG)(nb->us.Buffer - fnus->Buffer); } break; } if (sf2->fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) { Status = STATUS_REPARSE; if (parsed) { name_bit* nb2 = CONTAINING_RECORD(le->Flink, name_bit, list_entry); *parsed = (USHORT)(nb2->us.Buffer - fnus->Buffer - 1) * sizeof(WCHAR); } break; } free_fileref(sf); sf = sf2; le = le->Flink; } while (le != &parts); if (Status != STATUS_REPARSE) Status = STATUS_SUCCESS; *pfr = sf2; end: free_fileref(sf); while (!IsListEmpty(&parts)) { name_bit* nb = CONTAINING_RECORD(RemoveHeadList(&parts), name_bit, list_entry); ExFreeToPagedLookasideList(&Vcb->name_bit_lookaside, nb); } end2: TRACE("returning %08lx\n", Status); return Status; } NTSTATUS add_dir_child(fcb* fcb, uint64_t inode, bool subvol, PANSI_STRING utf8, PUNICODE_STRING name, uint8_t type, dir_child** pdc) { NTSTATUS Status; dir_child* dc; bool locked; dc = ExAllocatePoolWithTag(PagedPool, sizeof(dir_child), ALLOC_TAG); if (!dc) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(dc, sizeof(dir_child)); dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8->Length, ALLOC_TAG); if (!dc->utf8.Buffer) { ERR("out of memory\n"); ExFreePool(dc); return STATUS_INSUFFICIENT_RESOURCES; } dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, name->Length, ALLOC_TAG); if (!dc->name.Buffer) { ERR("out of memory\n"); ExFreePool(dc->utf8.Buffer); ExFreePool(dc); return STATUS_INSUFFICIENT_RESOURCES; } dc->key.obj_id = inode; dc->key.obj_type = subvol ? TYPE_ROOT_ITEM : TYPE_INODE_ITEM; dc->key.offset = subvol ? 0xffffffffffffffff : 0; dc->type = type; dc->fileref = NULL; dc->utf8.Length = dc->utf8.MaximumLength = utf8->Length; RtlCopyMemory(dc->utf8.Buffer, utf8->Buffer, utf8->Length); dc->name.Length = dc->name.MaximumLength = name->Length; RtlCopyMemory(dc->name.Buffer, name->Buffer, name->Length); Status = RtlUpcaseUnicodeString(&dc->name_uc, name, true); if (!NT_SUCCESS(Status)) { ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); ExFreePool(dc->utf8.Buffer); ExFreePool(dc->name.Buffer); ExFreePool(dc); return Status; } dc->hash = calc_crc32c(0xffffffff, (uint8_t*)dc->name.Buffer, dc->name.Length); dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)dc->name_uc.Buffer, dc->name_uc.Length); locked = ExIsResourceAcquiredExclusive(&fcb->nonpaged->dir_children_lock); if (!locked) ExAcquireResourceExclusiveLite(&fcb->nonpaged->dir_children_lock, true); if (IsListEmpty(&fcb->dir_children_index)) dc->index = 2; else { dir_child* dc2 = CONTAINING_RECORD(fcb->dir_children_index.Blink, dir_child, list_entry_index); dc->index = max(2, dc2->index + 1); } InsertTailList(&fcb->dir_children_index, &dc->list_entry_index); insert_dir_child_into_hash_lists(fcb, dc); if (!locked) ExReleaseResourceLite(&fcb->nonpaged->dir_children_lock); *pdc = dc; return STATUS_SUCCESS; } uint32_t inherit_mode(fcb* parfcb, bool is_dir) { uint32_t mode; if (!parfcb) return 0755; mode = parfcb->inode_item.st_mode & ~S_IFDIR; mode &= ~S_ISVTX; // clear sticky bit mode &= ~S_ISUID; // clear setuid bit if (!is_dir) mode &= ~S_ISGID; // if not directory, clear setgid bit return mode; } static NTSTATUS file_create_parse_ea(fcb* fcb, FILE_FULL_EA_INFORMATION* ea) { NTSTATUS Status; LIST_ENTRY ealist, *le; uint16_t size = 0; char* buf; InitializeListHead(&ealist); do { STRING s; bool found = false; s.Length = s.MaximumLength = ea->EaNameLength; s.Buffer = ea->EaName; RtlUpperString(&s, &s); le = ealist.Flink; while (le != &ealist) { ea_item* item = CONTAINING_RECORD(le, ea_item, list_entry); if (item->name.Length == s.Length && RtlCompareMemory(item->name.Buffer, s.Buffer, s.Length) == s.Length) { item->flags = ea->Flags; item->value.Length = item->value.MaximumLength = ea->EaValueLength; item->value.Buffer = &ea->EaName[ea->EaNameLength + 1]; found = true; break; } le = le->Flink; } if (!found) { ea_item* item = ExAllocatePoolWithTag(PagedPool, sizeof(ea_item), ALLOC_TAG); if (!item) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } item->name.Length = item->name.MaximumLength = ea->EaNameLength; item->name.Buffer = ea->EaName; item->value.Length = item->value.MaximumLength = ea->EaValueLength; item->value.Buffer = &ea->EaName[ea->EaNameLength + 1]; item->flags = ea->Flags; InsertTailList(&ealist, &item->list_entry); } if (ea->NextEntryOffset == 0) break; ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset); } while (true); // handle LXSS values le = ealist.Flink; while (le != &ealist) { LIST_ENTRY* le2 = le->Flink; ea_item* item = CONTAINING_RECORD(le, ea_item, list_entry); if (item->name.Length == sizeof(lxuid) - 1 && RtlCompareMemory(item->name.Buffer, lxuid, item->name.Length) == item->name.Length) { if (item->value.Length < sizeof(uint32_t)) { ERR("uid value was shorter than expected\n"); Status = STATUS_INVALID_PARAMETER; goto end; } RtlCopyMemory(&fcb->inode_item.st_uid, item->value.Buffer, sizeof(uint32_t)); fcb->sd_dirty = true; fcb->sd_deleted = false; RemoveEntryList(&item->list_entry); ExFreePool(item); } else if (item->name.Length == sizeof(lxgid) - 1 && RtlCompareMemory(item->name.Buffer, lxgid, item->name.Length) == item->name.Length) { if (item->value.Length < sizeof(uint32_t)) { ERR("gid value was shorter than expected\n"); Status = STATUS_INVALID_PARAMETER; goto end; } RtlCopyMemory(&fcb->inode_item.st_gid, item->value.Buffer, sizeof(uint32_t)); RemoveEntryList(&item->list_entry); ExFreePool(item); } else if (item->name.Length == sizeof(lxmod) - 1 && RtlCompareMemory(item->name.Buffer, lxmod, item->name.Length) == item->name.Length) { 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; uint32_t val; if (item->value.Length < sizeof(uint32_t)) { ERR("mode value was shorter than expected\n"); Status = STATUS_INVALID_PARAMETER; goto end; } val = *(uint32_t*)item->value.Buffer; fcb->inode_item.st_mode &= ~allowed; fcb->inode_item.st_mode |= val & allowed; if (fcb->type != BTRFS_TYPE_DIRECTORY) { if (__S_ISTYPE(val, __S_IFCHR)) { fcb->type = BTRFS_TYPE_CHARDEV; fcb->inode_item.st_mode &= ~__S_IFMT; fcb->inode_item.st_mode |= __S_IFCHR; } else if (__S_ISTYPE(val, __S_IFBLK)) { fcb->type = BTRFS_TYPE_BLOCKDEV; fcb->inode_item.st_mode &= ~__S_IFMT; fcb->inode_item.st_mode |= __S_IFBLK; } else if (__S_ISTYPE(val, __S_IFIFO)) { fcb->type = BTRFS_TYPE_FIFO; fcb->inode_item.st_mode &= ~__S_IFMT; fcb->inode_item.st_mode |= __S_IFIFO; } else if (__S_ISTYPE(val, __S_IFSOCK)) { fcb->type = BTRFS_TYPE_SOCKET; fcb->inode_item.st_mode &= ~__S_IFMT; fcb->inode_item.st_mode |= __S_IFSOCK; } } RemoveEntryList(&item->list_entry); ExFreePool(item); } else if (item->name.Length == sizeof(lxdev) - 1 && RtlCompareMemory(item->name.Buffer, lxdev, item->name.Length) == item->name.Length) { uint32_t major, minor; if (item->value.Length < sizeof(uint64_t)) { ERR("dev value was shorter than expected\n"); Status = STATUS_INVALID_PARAMETER; goto end; } major = *(uint32_t*)item->value.Buffer; minor = *(uint32_t*)&item->value.Buffer[sizeof(uint32_t)]; fcb->inode_item.st_rdev = (minor & 0xFFFFF) | ((major & 0xFFFFFFFFFFF) << 20); RemoveEntryList(&item->list_entry); ExFreePool(item); } le = le2; } if (fcb->type != BTRFS_TYPE_CHARDEV && fcb->type != BTRFS_TYPE_BLOCKDEV) fcb->inode_item.st_rdev = 0; if (IsListEmpty(&ealist)) return STATUS_SUCCESS; le = ealist.Flink; while (le != &ealist) { ea_item* item = CONTAINING_RECORD(le, ea_item, list_entry); if (size % 4 > 0) size += 4 - (size % 4); size += (uint16_t)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + item->name.Length + 1 + item->value.Length; le = le->Flink; } buf = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG); if (!buf) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = size; fcb->ea_xattr.Buffer = buf; fcb->ealen = 4; ea = NULL; le = ealist.Flink; while (le != &ealist) { ea_item* item = CONTAINING_RECORD(le, ea_item, list_entry); if (ea) { ea->NextEntryOffset = (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + ea->EaValueLength; if (ea->NextEntryOffset % 4 > 0) ea->NextEntryOffset += 4 - (ea->NextEntryOffset % 4); ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset); } else ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer; ea->NextEntryOffset = 0; ea->Flags = item->flags; ea->EaNameLength = (UCHAR)item->name.Length; ea->EaValueLength = item->value.Length; RtlCopyMemory(ea->EaName, item->name.Buffer, item->name.Length); ea->EaName[item->name.Length] = 0; RtlCopyMemory(&ea->EaName[item->name.Length + 1], item->value.Buffer, item->value.Length); fcb->ealen += 5 + item->name.Length + item->value.Length; le = le->Flink; } fcb->ea_changed = true; Status = STATUS_SUCCESS; end: while (!IsListEmpty(&ealist)) { ea_item* item = CONTAINING_RECORD(RemoveHeadList(&ealist), ea_item, list_entry); ExFreePool(item); } return Status; } static NTSTATUS file_create2(_In_ PIRP Irp, _Requires_exclusive_lock_held_(_Curr_->fcb_lock) _In_ device_extension* Vcb, _In_ PUNICODE_STRING fpus, _In_ file_ref* parfileref, _In_ ULONG options, _In_reads_bytes_opt_(ealen) FILE_FULL_EA_INFORMATION* ea, _In_ ULONG ealen, _Out_ file_ref** pfr, bool case_sensitive, _In_ LIST_ENTRY* rollback) { NTSTATUS Status; fcb* fcb; ULONG utf8len; char* utf8 = NULL; uint64_t inode; uint8_t type; LARGE_INTEGER time; BTRFS_TIME now; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); POOL_TYPE pool_type = IrpSp->Flags & SL_OPEN_PAGING_FILE ? NonPagedPool : PagedPool; USHORT defda; file_ref* fileref; dir_child* dc; ANSI_STRING utf8as; LIST_ENTRY* lastle = NULL; file_ref* existing_fileref = NULL; #ifdef DEBUG_FCB_REFCOUNTS LONG rc; #endif if (parfileref->fcb == Vcb->dummy_fcb) return STATUS_ACCESS_DENIED; if (options & FILE_DIRECTORY_FILE && IrpSp->Parameters.Create.FileAttributes & FILE_ATTRIBUTE_TEMPORARY) return STATUS_INVALID_PARAMETER; Status = utf16_to_utf8(NULL, 0, &utf8len, fpus->Buffer, fpus->Length); if (!NT_SUCCESS(Status)) { ERR("utf16_to_utf8 returned %08lx\n", Status); return Status; } utf8 = ExAllocatePoolWithTag(pool_type, utf8len + 1, ALLOC_TAG); if (!utf8) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } Status = utf16_to_utf8(utf8, utf8len, &utf8len, fpus->Buffer, fpus->Length); if (!NT_SUCCESS(Status)) { ERR("utf16_to_utf8 returned %08lx\n", Status); ExFreePool(utf8); return Status; } utf8[utf8len] = 0; KeQuerySystemTime(&time); win_time_to_unix(time, &now); TRACE("create file %.*S\n", (int)(fpus->Length / sizeof(WCHAR)), fpus->Buffer); ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true); TRACE("parfileref->fcb->inode_item.st_size (inode %I64x) was %I64x\n", parfileref->fcb->inode, parfileref->fcb->inode_item.st_size); parfileref->fcb->inode_item.st_size += utf8len * 2; TRACE("parfileref->fcb->inode_item.st_size (inode %I64x) now %I64x\n", parfileref->fcb->inode, parfileref->fcb->inode_item.st_size); parfileref->fcb->inode_item.transid = Vcb->superblock.generation; parfileref->fcb->inode_item.sequence++; parfileref->fcb->inode_item.st_ctime = now; parfileref->fcb->inode_item.st_mtime = now; ExReleaseResourceLite(parfileref->fcb->Header.Resource); parfileref->fcb->inode_item_changed = true; mark_fcb_dirty(parfileref->fcb); inode = InterlockedIncrement64(&parfileref->fcb->subvol->lastinode); type = options & FILE_DIRECTORY_FILE ? BTRFS_TYPE_DIRECTORY : BTRFS_TYPE_FILE; // FIXME - link FILE_ATTRIBUTE_READONLY to st_mode TRACE("requested attributes = %x\n", IrpSp->Parameters.Create.FileAttributes); defda = 0; if (utf8[0] == '.') defda |= FILE_ATTRIBUTE_HIDDEN; if (options & FILE_DIRECTORY_FILE) { defda |= FILE_ATTRIBUTE_DIRECTORY; IrpSp->Parameters.Create.FileAttributes |= FILE_ATTRIBUTE_DIRECTORY; } else IrpSp->Parameters.Create.FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY; if (!(IrpSp->Parameters.Create.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { IrpSp->Parameters.Create.FileAttributes |= FILE_ATTRIBUTE_ARCHIVE; defda |= FILE_ATTRIBUTE_ARCHIVE; } TRACE("defda = %x\n", defda); if (IrpSp->Parameters.Create.FileAttributes == FILE_ATTRIBUTE_NORMAL) IrpSp->Parameters.Create.FileAttributes = defda; fcb = create_fcb(Vcb, pool_type); if (!fcb) { ERR("out of memory\n"); ExFreePool(utf8); ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true); parfileref->fcb->inode_item.st_size -= utf8len * 2; ExReleaseResourceLite(parfileref->fcb->Header.Resource); return STATUS_INSUFFICIENT_RESOURCES; } fcb->Vcb = Vcb; if (IrpSp->Flags & SL_OPEN_PAGING_FILE) fcb->Header.Flags2 |= FSRTL_FLAG2_IS_PAGING_FILE; fcb->inode_item.generation = Vcb->superblock.generation; fcb->inode_item.transid = Vcb->superblock.generation; fcb->inode_item.st_size = 0; fcb->inode_item.st_blocks = 0; fcb->inode_item.block_group = 0; fcb->inode_item.st_nlink = 1; fcb->inode_item.st_gid = GID_NOBODY; // FIXME? fcb->inode_item.st_mode = inherit_mode(parfileref->fcb, type == BTRFS_TYPE_DIRECTORY); // use parent's permissions by default fcb->inode_item.st_rdev = 0; fcb->inode_item.flags = 0; fcb->inode_item.sequence = 1; fcb->inode_item.st_atime = now; fcb->inode_item.st_ctime = now; fcb->inode_item.st_mtime = now; fcb->inode_item.otime = now; if (type == BTRFS_TYPE_DIRECTORY) fcb->inode_item.st_mode |= S_IFDIR; else { fcb->inode_item.st_mode |= S_IFREG; fcb->inode_item.st_mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH); // remove executable bit if not directory } if (IrpSp->Flags & SL_OPEN_PAGING_FILE) { fcb->inode_item.flags = BTRFS_INODE_NODATACOW | BTRFS_INODE_NODATASUM | BTRFS_INODE_NOCOMPRESS; } else { // inherit nodatacow flag from parent directory if (parfileref->fcb->inode_item.flags & BTRFS_INODE_NODATACOW || Vcb->options.nodatacow) { fcb->inode_item.flags |= BTRFS_INODE_NODATACOW; if (type != BTRFS_TYPE_DIRECTORY) fcb->inode_item.flags |= BTRFS_INODE_NODATASUM; } if (parfileref->fcb->inode_item.flags & BTRFS_INODE_COMPRESS && !(fcb->inode_item.flags & BTRFS_INODE_NODATACOW)) { fcb->inode_item.flags |= BTRFS_INODE_COMPRESS; } } if (!(fcb->inode_item.flags & BTRFS_INODE_NODATACOW)) { fcb->prop_compression = parfileref->fcb->prop_compression; fcb->prop_compression_changed = fcb->prop_compression != PropCompression_None; } else fcb->prop_compression = PropCompression_None; fcb->inode_item_changed = true; fcb->Header.IsFastIoPossible = fast_io_possible(fcb); fcb->Header.AllocationSize.QuadPart = 0; fcb->Header.FileSize.QuadPart = 0; fcb->Header.ValidDataLength.QuadPart = 0; fcb->atts = IrpSp->Parameters.Create.FileAttributes & ~FILE_ATTRIBUTE_NORMAL; fcb->atts_changed = fcb->atts != defda; #ifdef DEBUG_FCB_REFCOUNTS rc = InterlockedIncrement(&parfileref->fcb->refcount); WARN("fcb %p: refcount now %i\n", parfileref->fcb, rc); #else InterlockedIncrement(&parfileref->fcb->refcount); #endif fcb->subvol = parfileref->fcb->subvol; fcb->inode = inode; fcb->type = type; fcb->created = true; fcb->deleted = true; fcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&inode, sizeof(uint64_t)); acquire_fcb_lock_exclusive(Vcb); if (fcb->subvol->fcbs_ptrs[fcb->hash >> 24]) { LIST_ENTRY* le = fcb->subvol->fcbs_ptrs[fcb->hash >> 24]; while (le != &fcb->subvol->fcbs) { struct _fcb* fcb2 = CONTAINING_RECORD(le, struct _fcb, list_entry); if (fcb2->hash > fcb->hash) { lastle = le->Blink; break; } le = le->Flink; } } if (!lastle) { uint8_t c = fcb->hash >> 24; if (c != 0xff) { uint8_t d = c + 1; do { if (fcb->subvol->fcbs_ptrs[d]) { lastle = fcb->subvol->fcbs_ptrs[d]->Blink; break; } d++; } while (d != 0); } } if (lastle) { InsertHeadList(lastle, &fcb->list_entry); if (lastle == &fcb->subvol->fcbs || (CONTAINING_RECORD(lastle, struct _fcb, list_entry)->hash >> 24) != (fcb->hash >> 24)) fcb->subvol->fcbs_ptrs[fcb->hash >> 24] = &fcb->list_entry; } else { InsertTailList(&fcb->subvol->fcbs, &fcb->list_entry); if (fcb->list_entry.Blink == &fcb->subvol->fcbs || (CONTAINING_RECORD(fcb->list_entry.Blink, struct _fcb, list_entry)->hash >> 24) != (fcb->hash >> 24)) fcb->subvol->fcbs_ptrs[fcb->hash >> 24] = &fcb->list_entry; } InsertTailList(&Vcb->all_fcbs, &fcb->list_entry_all); fcb->subvol->fcbs_version++; release_fcb_lock(Vcb); mark_fcb_dirty(fcb); Status = fcb_get_new_sd(fcb, parfileref, IrpSp->Parameters.Create.SecurityContext->AccessState); if (!NT_SUCCESS(Status)) { ERR("fcb_get_new_sd returned %08lx\n", Status); free_fcb(fcb); ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true); parfileref->fcb->inode_item.st_size -= utf8len * 2; ExReleaseResourceLite(parfileref->fcb->Header.Resource); ExFreePool(utf8); return Status; } fcb->sd_dirty = true; if (ea && ealen > 0) { Status = file_create_parse_ea(fcb, ea); if (!NT_SUCCESS(Status)) { ERR("file_create_parse_ea returned %08lx\n", Status); free_fcb(fcb); ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true); parfileref->fcb->inode_item.st_size -= utf8len * 2; ExReleaseResourceLite(parfileref->fcb->Header.Resource); ExFreePool(utf8); return Status; } } fileref = create_fileref(Vcb); if (!fileref) { ERR("out of memory\n"); free_fcb(fcb); ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true); parfileref->fcb->inode_item.st_size -= utf8len * 2; ExReleaseResourceLite(parfileref->fcb->Header.Resource); ExFreePool(utf8); return STATUS_INSUFFICIENT_RESOURCES; } fileref->fcb = fcb; if (Irp->Overlay.AllocationSize.QuadPart > 0 && !write_fcb_compressed(fcb) && fcb->type != BTRFS_TYPE_DIRECTORY) { Status = extend_file(fcb, fileref, Irp->Overlay.AllocationSize.QuadPart, true, NULL, rollback); if (!NT_SUCCESS(Status)) { ERR("extend_file returned %08lx\n", Status); reap_fileref(Vcb, fileref); ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true); parfileref->fcb->inode_item.st_size -= utf8len * 2; ExReleaseResourceLite(parfileref->fcb->Header.Resource); ExFreePool(utf8); return Status; } } if (fcb->type == BTRFS_TYPE_DIRECTORY) { fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG); if (!fcb->hash_ptrs) { ERR("out of memory\n"); reap_fileref(Vcb, fileref); ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true); parfileref->fcb->inode_item.st_size -= utf8len * 2; ExReleaseResourceLite(parfileref->fcb->Header.Resource); ExFreePool(utf8); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256); fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG); if (!fcb->hash_ptrs_uc) { ERR("out of memory\n"); reap_fileref(Vcb, fileref); ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true); parfileref->fcb->inode_item.st_size -= utf8len * 2; ExReleaseResourceLite(parfileref->fcb->Header.Resource); ExFreePool(utf8); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256); } fcb->deleted = false; fileref->created = true; fcb->subvol->root_item.ctransid = Vcb->superblock.generation; fcb->subvol->root_item.ctime = now; utf8as.Buffer = utf8; utf8as.Length = utf8as.MaximumLength = (uint16_t)utf8len; ExAcquireResourceExclusiveLite(&parfileref->fcb->nonpaged->dir_children_lock, true); // check again doesn't already exist if (case_sensitive) { uint32_t dc_hash = calc_crc32c(0xffffffff, (uint8_t*)fpus->Buffer, fpus->Length); if (parfileref->fcb->hash_ptrs[dc_hash >> 24]) { LIST_ENTRY* le = parfileref->fcb->hash_ptrs[dc_hash >> 24]; while (le != &parfileref->fcb->dir_children_hash) { dc = CONTAINING_RECORD(le, dir_child, list_entry_hash); if (dc->hash == dc_hash && dc->name.Length == fpus->Length && RtlCompareMemory(dc->name.Buffer, fpus->Buffer, fpus->Length) == fpus->Length) { existing_fileref = dc->fileref; break; } else if (dc->hash > dc_hash) break; le = le->Flink; } } } else { UNICODE_STRING fpusuc; Status = RtlUpcaseUnicodeString(&fpusuc, fpus, true); if (!NT_SUCCESS(Status)) { ExReleaseResourceLite(&parfileref->fcb->nonpaged->dir_children_lock); ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); reap_fileref(Vcb, fileref); ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true); parfileref->fcb->inode_item.st_size -= utf8len * 2; ExReleaseResourceLite(parfileref->fcb->Header.Resource); ExFreePool(utf8); return Status; } uint32_t dc_hash = calc_crc32c(0xffffffff, (uint8_t*)fpusuc.Buffer, fpusuc.Length); if (parfileref->fcb->hash_ptrs_uc[dc_hash >> 24]) { LIST_ENTRY* le = parfileref->fcb->hash_ptrs_uc[dc_hash >> 24]; while (le != &parfileref->fcb->dir_children_hash_uc) { dc = CONTAINING_RECORD(le, dir_child, list_entry_hash_uc); if (dc->hash_uc == dc_hash && dc->name.Length == fpusuc.Length && RtlCompareMemory(dc->name.Buffer, fpusuc.Buffer, fpusuc.Length) == fpusuc.Length) { existing_fileref = dc->fileref; break; } else if (dc->hash_uc > dc_hash) break; le = le->Flink; } } ExFreePool(fpusuc.Buffer); } if (existing_fileref) { ExReleaseResourceLite(&parfileref->fcb->nonpaged->dir_children_lock); reap_fileref(Vcb, fileref); ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true); parfileref->fcb->inode_item.st_size -= utf8len * 2; ExReleaseResourceLite(parfileref->fcb->Header.Resource); ExFreePool(utf8); increase_fileref_refcount(existing_fileref); *pfr = existing_fileref; return STATUS_OBJECT_NAME_COLLISION; } Status = add_dir_child(parfileref->fcb, fcb->inode, false, &utf8as, fpus, fcb->type, &dc); if (!NT_SUCCESS(Status)) { ExReleaseResourceLite(&parfileref->fcb->nonpaged->dir_children_lock); ERR("add_dir_child returned %08lx\n", Status); reap_fileref(Vcb, fileref); ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true); parfileref->fcb->inode_item.st_size -= utf8len * 2; ExReleaseResourceLite(parfileref->fcb->Header.Resource); ExFreePool(utf8); return Status; } fileref->parent = parfileref; fileref->dc = dc; dc->fileref = fileref; if (type == BTRFS_TYPE_DIRECTORY) fileref->fcb->fileref = fileref; InsertTailList(&parfileref->children, &fileref->list_entry); ExReleaseResourceLite(&parfileref->fcb->nonpaged->dir_children_lock); ExFreePool(utf8); mark_fileref_dirty(fileref); increase_fileref_refcount(parfileref); *pfr = fileref; TRACE("created new file in subvol %I64x, inode %I64x\n", fcb->subvol->id, fcb->inode); return STATUS_SUCCESS; } static NTSTATUS create_stream(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb, file_ref** pfileref, file_ref** pparfileref, PUNICODE_STRING fpus, PUNICODE_STRING stream, PIRP Irp, ULONG options, POOL_TYPE pool_type, bool case_sensitive, LIST_ENTRY* rollback) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); file_ref *fileref, *newpar, *parfileref; fcb* fcb; static const char xapref[] = "user."; static const WCHAR DOSATTRIB[] = L"DOSATTRIB"; static const WCHAR EA[] = L"EA"; static const WCHAR reparse[] = L"reparse"; static const WCHAR casesensitive_str[] = L"casesensitive"; LARGE_INTEGER time; BTRFS_TIME now; ULONG utf8len, overhead; NTSTATUS Status; KEY searchkey; traverse_ptr tp; dir_child* dc; dir_child* existing_dc = NULL; ACCESS_MASK granted_access; #ifdef DEBUG_FCB_REFCOUNTS LONG rc; #endif TRACE("fpus = %.*S\n", (int)(fpus->Length / sizeof(WCHAR)), fpus->Buffer); TRACE("stream = %.*S\n", (int)(stream->Length / sizeof(WCHAR)), stream->Buffer); parfileref = *pparfileref; if (parfileref->fcb == Vcb->dummy_fcb) return STATUS_ACCESS_DENIED; Status = check_file_name_valid(stream, false, true); if (!NT_SUCCESS(Status)) return Status; Status = open_fileref(Vcb, &newpar, fpus, parfileref, false, NULL, NULL, PagedPool, case_sensitive, Irp); if (Status == STATUS_OBJECT_NAME_NOT_FOUND) { UNICODE_STRING fpus2; Status = check_file_name_valid(fpus, false, false); if (!NT_SUCCESS(Status)) return Status; fpus2.Length = fpus2.MaximumLength = fpus->Length; fpus2.Buffer = ExAllocatePoolWithTag(pool_type, fpus2.MaximumLength, ALLOC_TAG); if (!fpus2.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(fpus2.Buffer, fpus->Buffer, fpus2.Length); SeLockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext); if (!SeAccessCheck(parfileref->fcb->sd, &IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext, true, options & FILE_DIRECTORY_FILE ? FILE_ADD_SUBDIRECTORY : FILE_ADD_FILE, 0, NULL, IoGetFileObjectGenericMapping(), IrpSp->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode, &granted_access, &Status)) { SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext); return Status; } SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext); Status = file_create2(Irp, Vcb, &fpus2, parfileref, options, NULL, 0, &newpar, case_sensitive, rollback); if (!NT_SUCCESS(Status)) { ERR("file_create2 returned %08lx\n", Status); ExFreePool(fpus2.Buffer); return Status; } else if (Status != STATUS_OBJECT_NAME_COLLISION) { send_notification_fileref(newpar, options & FILE_DIRECTORY_FILE ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL); queue_notification_fcb(newpar->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL); } ExFreePool(fpus2.Buffer); } else if (!NT_SUCCESS(Status)) { ERR("open_fileref returned %08lx\n", Status); return Status; } parfileref = newpar; *pparfileref = parfileref; if (parfileref->fcb->type != BTRFS_TYPE_FILE && parfileref->fcb->type != BTRFS_TYPE_SYMLINK && parfileref->fcb->type != BTRFS_TYPE_DIRECTORY) { WARN("parent not file, directory, or symlink\n"); free_fileref(parfileref); return STATUS_INVALID_PARAMETER; } if (options & FILE_DIRECTORY_FILE) { WARN("tried to create directory as stream\n"); free_fileref(parfileref); return STATUS_INVALID_PARAMETER; } if (parfileref->fcb->atts & FILE_ATTRIBUTE_READONLY && !(IrpSp->Flags & SL_IGNORE_READONLY_ATTRIBUTE)) { free_fileref(parfileref); return STATUS_ACCESS_DENIED; } SeLockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext); if (!SeAccessCheck(parfileref->fcb->sd, &IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext, true, FILE_WRITE_DATA, 0, NULL, IoGetFileObjectGenericMapping(), IrpSp->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode, &granted_access, &Status)) { SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext); free_fileref(parfileref); return Status; } SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext); if ((stream->Length == sizeof(DOSATTRIB) - sizeof(WCHAR) && RtlCompareMemory(stream->Buffer, DOSATTRIB, stream->Length) == stream->Length) || (stream->Length == sizeof(EA) - sizeof(WCHAR) && RtlCompareMemory(stream->Buffer, EA, stream->Length) == stream->Length) || (stream->Length == sizeof(reparse) - sizeof(WCHAR) && RtlCompareMemory(stream->Buffer, reparse, stream->Length) == stream->Length) || (stream->Length == sizeof(casesensitive_str) - sizeof(WCHAR) && RtlCompareMemory(stream->Buffer, casesensitive_str, stream->Length) == stream->Length)) { free_fileref(parfileref); return STATUS_OBJECT_NAME_INVALID; } fcb = create_fcb(Vcb, pool_type); if (!fcb) { ERR("out of memory\n"); free_fileref(parfileref); return STATUS_INSUFFICIENT_RESOURCES; } fcb->Vcb = Vcb; fcb->Header.IsFastIoPossible = fast_io_possible(fcb); fcb->Header.AllocationSize.QuadPart = 0; fcb->Header.FileSize.QuadPart = 0; fcb->Header.ValidDataLength.QuadPart = 0; #ifdef DEBUG_FCB_REFCOUNTS rc = InterlockedIncrement(&parfileref->fcb->refcount); WARN("fcb %p: refcount now %i\n", parfileref->fcb, rc); #else InterlockedIncrement(&parfileref->fcb->refcount); #endif fcb->subvol = parfileref->fcb->subvol; fcb->inode = parfileref->fcb->inode; fcb->hash = parfileref->fcb->hash; fcb->type = parfileref->fcb->type; fcb->ads = true; Status = utf16_to_utf8(NULL, 0, &utf8len, stream->Buffer, stream->Length); if (!NT_SUCCESS(Status)) { ERR("utf16_to_utf8 1 returned %08lx\n", Status); reap_fcb(fcb); free_fileref(parfileref); return Status; } fcb->adsxattr.Length = (uint16_t)utf8len + sizeof(xapref) - 1; fcb->adsxattr.MaximumLength = fcb->adsxattr.Length + 1; fcb->adsxattr.Buffer = ExAllocatePoolWithTag(pool_type, fcb->adsxattr.MaximumLength, ALLOC_TAG); if (!fcb->adsxattr.Buffer) { ERR("out of memory\n"); reap_fcb(fcb); free_fileref(parfileref); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(fcb->adsxattr.Buffer, xapref, sizeof(xapref) - 1); Status = utf16_to_utf8(&fcb->adsxattr.Buffer[sizeof(xapref) - 1], utf8len, &utf8len, stream->Buffer, stream->Length); if (!NT_SUCCESS(Status)) { ERR("utf16_to_utf8 2 returned %08lx\n", Status); reap_fcb(fcb); free_fileref(parfileref); return Status; } fcb->adsxattr.Buffer[fcb->adsxattr.Length] = 0; TRACE("adsxattr = %s\n", fcb->adsxattr.Buffer); fcb->adshash = calc_crc32c(0xfffffffe, (uint8_t*)fcb->adsxattr.Buffer, fcb->adsxattr.Length); TRACE("adshash = %08x\n", fcb->adshash); searchkey.obj_id = parfileref->fcb->inode; searchkey.obj_type = TYPE_XATTR_ITEM; searchkey.offset = fcb->adshash; Status = find_item(Vcb, parfileref->fcb->subvol, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); reap_fcb(fcb); free_fileref(parfileref); return Status; } if (!keycmp(tp.item->key, searchkey)) overhead = tp.item->size; else overhead = 0; fcb->adsmaxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) - (sizeof(DIR_ITEM) - 1); if (utf8len + sizeof(xapref) - 1 + overhead > fcb->adsmaxlen) { WARN("not enough room for new DIR_ITEM (%Iu + %lu > %lu)\n", utf8len + sizeof(xapref) - 1, overhead, fcb->adsmaxlen); reap_fcb(fcb); free_fileref(parfileref); return STATUS_DISK_FULL; } else fcb->adsmaxlen -= overhead + utf8len + sizeof(xapref) - 1; fcb->created = true; fcb->deleted = true; acquire_fcb_lock_exclusive(Vcb); InsertHeadList(&parfileref->fcb->list_entry, &fcb->list_entry); // insert in list after parent fcb InsertTailList(&Vcb->all_fcbs, &fcb->list_entry_all); parfileref->fcb->subvol->fcbs_version++; release_fcb_lock(Vcb); mark_fcb_dirty(fcb); fileref = create_fileref(Vcb); if (!fileref) { ERR("out of memory\n"); free_fcb(fcb); free_fileref(parfileref); return STATUS_INSUFFICIENT_RESOURCES; } fileref->fcb = fcb; dc = ExAllocatePoolWithTag(PagedPool, sizeof(dir_child), ALLOC_TAG); if (!dc) { ERR("out of memory\n"); reap_fileref(Vcb, fileref); free_fileref(parfileref); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(dc, sizeof(dir_child)); dc->utf8.MaximumLength = dc->utf8.Length = fcb->adsxattr.Length + 1 - sizeof(xapref); dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, dc->utf8.MaximumLength, ALLOC_TAG); if (!dc->utf8.Buffer) { ERR("out of memory\n"); ExFreePool(dc); reap_fileref(Vcb, fileref); free_fileref(parfileref); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(dc->utf8.Buffer, &fcb->adsxattr.Buffer[sizeof(xapref) - 1], fcb->adsxattr.Length + 1 - sizeof(xapref)); dc->name.MaximumLength = dc->name.Length = stream->Length; dc->name.Buffer = ExAllocatePoolWithTag(pool_type, dc->name.MaximumLength, ALLOC_TAG); if (!dc->name.Buffer) { ERR("out of memory\n"); ExFreePool(dc->utf8.Buffer); ExFreePool(dc); reap_fileref(Vcb, fileref); free_fileref(parfileref); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(dc->name.Buffer, stream->Buffer, stream->Length); Status = RtlUpcaseUnicodeString(&dc->name_uc, &dc->name, true); if (!NT_SUCCESS(Status)) { ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); ExFreePool(dc->utf8.Buffer); ExFreePool(dc->name.Buffer); ExFreePool(dc); reap_fileref(Vcb, fileref); free_fileref(parfileref); return Status; } KeQuerySystemTime(&time); win_time_to_unix(time, &now); ExAcquireResourceExclusiveLite(&parfileref->fcb->nonpaged->dir_children_lock, true); LIST_ENTRY* le = parfileref->fcb->dir_children_index.Flink; while (le != &parfileref->fcb->dir_children_index) { dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_index); if (dc2->index == 0) { if ((case_sensitive && dc2->name.Length == dc->name.Length && RtlCompareMemory(dc2->name.Buffer, dc->name.Buffer, dc2->name.Length) == dc2->name.Length) || (!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) ) { existing_dc = dc2; break; } } else break; le = le->Flink; } if (existing_dc) { ExFreePool(dc->utf8.Buffer); ExFreePool(dc->name.Buffer); ExFreePool(dc); reap_fileref(Vcb, fileref); free_fileref(parfileref); increase_fileref_refcount(existing_dc->fileref); *pfileref = existing_dc->fileref; return STATUS_OBJECT_NAME_COLLISION; } dc->fileref = fileref; fileref->dc = dc; fileref->parent = (struct _file_ref*)parfileref; fcb->deleted = false; InsertHeadList(&parfileref->fcb->dir_children_index, &dc->list_entry_index); InsertTailList(&parfileref->children, &fileref->list_entry); ExReleaseResourceLite(&parfileref->fcb->nonpaged->dir_children_lock); mark_fileref_dirty(fileref); parfileref->fcb->inode_item.transid = Vcb->superblock.generation; parfileref->fcb->inode_item.sequence++; parfileref->fcb->inode_item.st_ctime = now; parfileref->fcb->inode_item_changed = true; mark_fcb_dirty(parfileref->fcb); parfileref->fcb->subvol->root_item.ctransid = Vcb->superblock.generation; parfileref->fcb->subvol->root_item.ctime = now; increase_fileref_refcount(parfileref); *pfileref = fileref; send_notification_fileref(parfileref, FILE_NOTIFY_CHANGE_STREAM_NAME, FILE_ACTION_ADDED_STREAM, &fileref->dc->name); return STATUS_SUCCESS; } // LXSS programs can be distinguished by the fact they have a NULL PEB. #ifdef _AMD64_ static __inline bool called_from_lxss() { NTSTATUS Status; PROCESS_BASIC_INFORMATION pbi; ULONG retlen; Status = ZwQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &pbi, sizeof(pbi), &retlen); if (!NT_SUCCESS(Status)) { ERR("ZwQueryInformationProcess returned %08lx\n", Status); return false; } return !pbi.PebBaseAddress; } #else #define called_from_lxss() false #endif static NTSTATUS file_create(PIRP Irp, _Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb, PFILE_OBJECT FileObject, file_ref* related, bool loaded_related, PUNICODE_STRING fnus, ULONG disposition, ULONG options, file_ref** existing_fileref, LIST_ENTRY* rollback) { NTSTATUS Status; file_ref *fileref, *parfileref = NULL; ULONG i, j; ccb* ccb; static const WCHAR datasuf[] = {':','$','D','A','T','A',0}; UNICODE_STRING dsus, fpus, stream; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); POOL_TYPE pool_type = IrpSp->Flags & SL_OPEN_PAGING_FILE ? NonPagedPool : PagedPool; ECP_LIST* ecp_list; ATOMIC_CREATE_ECP_CONTEXT* acec = NULL; #ifdef DEBUG_FCB_REFCOUNTS LONG oc; #endif TRACE("(%p, %p, %p, %.*S, %lx, %lx)\n", Irp, Vcb, FileObject, (int)(fnus->Length / sizeof(WCHAR)), fnus->Buffer, disposition, options); if (Vcb->readonly) return STATUS_MEDIA_WRITE_PROTECTED; if (options & FILE_DELETE_ON_CLOSE && IrpSp->Parameters.Create.FileAttributes & FILE_ATTRIBUTE_READONLY && !(IrpSp->Flags & SL_IGNORE_READONLY_ATTRIBUTE)) { return STATUS_CANNOT_DELETE; } if (fFsRtlGetEcpListFromIrp && fFsRtlGetNextExtraCreateParameter) { if (NT_SUCCESS(fFsRtlGetEcpListFromIrp(Irp, &ecp_list)) && ecp_list) { void* ctx = NULL; GUID type; ULONG ctxsize; do { Status = fFsRtlGetNextExtraCreateParameter(ecp_list, ctx, &type, &ctx, &ctxsize); if (NT_SUCCESS(Status)) { if (RtlCompareMemory(&type, &GUID_ECP_ATOMIC_CREATE, sizeof(GUID)) == sizeof(GUID)) { if (ctxsize >= sizeof(ATOMIC_CREATE_ECP_CONTEXT)) acec = ctx; else { ERR("GUID_ECP_ATOMIC_CREATE context was too short: %lu bytes, expected %Iu\n", ctxsize, sizeof(ATOMIC_CREATE_ECP_CONTEXT)); } } else if (RtlCompareMemory(&type, &GUID_ECP_QUERY_ON_CREATE, sizeof(GUID)) == sizeof(GUID)) WARN("unhandled ECP GUID_ECP_QUERY_ON_CREATE\n"); else if (RtlCompareMemory(&type, &GUID_ECP_CREATE_REDIRECTION, sizeof(GUID)) == sizeof(GUID)) WARN("unhandled ECP GUID_ECP_CREATE_REDIRECTION\n"); else { WARN("unhandled ECP {%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}\n", type.Data1, type.Data2, type.Data3, type.Data4[0], type.Data4[1], type.Data4[2], type.Data4[3], type.Data4[4], type.Data4[5], type.Data4[6], type.Data4[7]); } } } while (NT_SUCCESS(Status)); } } dsus.Buffer = (WCHAR*)datasuf; dsus.Length = dsus.MaximumLength = sizeof(datasuf) - sizeof(WCHAR); fpus.Buffer = NULL; if (!loaded_related) { Status = open_fileref(Vcb, &parfileref, fnus, related, true, NULL, NULL, pool_type, IrpSp->Flags & SL_CASE_SENSITIVE, Irp); if (!NT_SUCCESS(Status)) goto end; } else parfileref = related; if (parfileref->fcb->type != BTRFS_TYPE_DIRECTORY && (fnus->Length < sizeof(WCHAR) || fnus->Buffer[0] != ':')) { Status = STATUS_OBJECT_PATH_NOT_FOUND; goto end; } if (is_subvol_readonly(parfileref->fcb->subvol, Irp)) { Status = STATUS_ACCESS_DENIED; goto end; } i = (fnus->Length / sizeof(WCHAR))-1; while ((fnus->Buffer[i] == '\\' || fnus->Buffer[i] == '/') && i > 0) { i--; } j = i; while (i > 0 && fnus->Buffer[i-1] != '\\' && fnus->Buffer[i-1] != '/') { i--; } fpus.MaximumLength = (USHORT)((j - i + 2) * sizeof(WCHAR)); fpus.Buffer = ExAllocatePoolWithTag(pool_type, fpus.MaximumLength, ALLOC_TAG); if (!fpus.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } fpus.Length = (USHORT)((j - i + 1) * sizeof(WCHAR)); RtlCopyMemory(fpus.Buffer, &fnus->Buffer[i], (j - i + 1) * sizeof(WCHAR)); fpus.Buffer[j - i + 1] = 0; if (fpus.Length > dsus.Length) { // check for :$DATA suffix UNICODE_STRING lb; lb.Buffer = &fpus.Buffer[(fpus.Length - dsus.Length)/sizeof(WCHAR)]; lb.Length = lb.MaximumLength = dsus.Length; TRACE("lb = %.*S\n", (int)(lb.Length/sizeof(WCHAR)), lb.Buffer); if (FsRtlAreNamesEqual(&dsus, &lb, true, NULL)) { TRACE("ignoring :$DATA suffix\n"); fpus.Length -= lb.Length; if (fpus.Length > sizeof(WCHAR) && fpus.Buffer[(fpus.Length-1)/sizeof(WCHAR)] == ':') fpus.Length -= sizeof(WCHAR); TRACE("fpus = %.*S\n", (int)(fpus.Length / sizeof(WCHAR)), fpus.Buffer); } } stream.Length = 0; for (i = 0; i < fpus.Length / sizeof(WCHAR); i++) { if (fpus.Buffer[i] == ':') { stream.Length = (USHORT)(fpus.Length - (i * sizeof(WCHAR)) - sizeof(WCHAR)); stream.Buffer = &fpus.Buffer[i+1]; fpus.Buffer[i] = 0; fpus.Length = (USHORT)(i * sizeof(WCHAR)); break; } } if (stream.Length > 0) { Status = create_stream(Vcb, &fileref, &parfileref, &fpus, &stream, Irp, options, pool_type, IrpSp->Flags & SL_CASE_SENSITIVE, rollback); if (!NT_SUCCESS(Status)) { ERR("create_stream returned %08lx\n", Status); goto end; } IoSetShareAccess(IrpSp->Parameters.Create.SecurityContext->DesiredAccess, IrpSp->Parameters.Create.ShareAccess, FileObject, &fileref->fcb->share_access); } else { ACCESS_MASK granted_access; Status = check_file_name_valid(&fpus, false, false); if (!NT_SUCCESS(Status)) goto end; SeLockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext); if (!SeAccessCheck(parfileref->fcb->sd, &IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext, true, options & FILE_DIRECTORY_FILE ? FILE_ADD_SUBDIRECTORY : FILE_ADD_FILE, 0, NULL, IoGetFileObjectGenericMapping(), IrpSp->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode, &granted_access, &Status)) { SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext); goto end; } SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext); if (Irp->AssociatedIrp.SystemBuffer && IrpSp->Parameters.Create.EaLength > 0) { ULONG offset; Status = IoCheckEaBufferValidity(Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.Create.EaLength, &offset); if (!NT_SUCCESS(Status)) { ERR("IoCheckEaBufferValidity returned %08lx (error at offset %lu)\n", Status, offset); goto end; } } Status = file_create2(Irp, Vcb, &fpus, parfileref, options, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.Create.EaLength, &fileref, IrpSp->Flags & SL_CASE_SENSITIVE, rollback); if (Status == STATUS_OBJECT_NAME_COLLISION) { *existing_fileref = fileref; goto end; } else if (!NT_SUCCESS(Status)) { ERR("file_create2 returned %08lx\n", Status); goto end; } IoSetShareAccess(IrpSp->Parameters.Create.SecurityContext->DesiredAccess, IrpSp->Parameters.Create.ShareAccess, FileObject, &fileref->fcb->share_access); send_notification_fileref(fileref, options & FILE_DIRECTORY_FILE ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL); queue_notification_fcb(fileref->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL); } FileObject->FsContext = fileref->fcb; ccb = ExAllocatePoolWithTag(NonPagedPool, sizeof(*ccb), ALLOC_TAG); if (!ccb) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; fileref->deleted = true; fileref->fcb->deleted = true; if (stream.Length == 0) { ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true); parfileref->fcb->inode_item.st_size -= fileref->dc->utf8.Length * 2; ExReleaseResourceLite(parfileref->fcb->Header.Resource); } free_fileref(fileref); goto end; } RtlZeroMemory(ccb, sizeof(*ccb)); ccb->fileref = fileref; ccb->NodeType = BTRFS_NODE_TYPE_CCB; ccb->NodeSize = sizeof(*ccb); ccb->disposition = disposition; ccb->options = options; ccb->query_dir_offset = 0; RtlInitUnicodeString(&ccb->query_string, NULL); ccb->has_wildcard = false; ccb->specific_file = false; ccb->access = IrpSp->Parameters.Create.SecurityContext->DesiredAccess; ccb->case_sensitive = IrpSp->Flags & SL_CASE_SENSITIVE; ccb->reserving = false; ccb->lxss = called_from_lxss(); #ifdef DEBUG_FCB_REFCOUNTS oc = InterlockedIncrement(&fileref->open_count); ERR("fileref %p: open_count now %i\n", fileref, oc); #else InterlockedIncrement(&fileref->open_count); #endif InterlockedIncrement(&Vcb->open_files); FileObject->FsContext2 = ccb; FileObject->SectionObjectPointer = &fileref->fcb->nonpaged->segment_object; // FIXME - ATOMIC_CREATE_ECP_IN_FLAG_BEST_EFFORT if (acec && acec->InFlags & ATOMIC_CREATE_ECP_IN_FLAG_REPARSE_POINT_SPECIFIED) { if (acec->ReparseBufferLength > sizeof(uint32_t) && *(uint32_t*)acec->ReparseBuffer == IO_REPARSE_TAG_SYMLINK) { fileref->fcb->inode_item.st_mode &= ~(__S_IFIFO | __S_IFCHR | __S_IFBLK | __S_IFSOCK); fileref->fcb->type = BTRFS_TYPE_FILE; fileref->fcb->atts &= ~FILE_ATTRIBUTE_DIRECTORY; } if (fileref->fcb->type == BTRFS_TYPE_SOCKET || fileref->fcb->type == BTRFS_TYPE_FIFO || fileref->fcb->type == BTRFS_TYPE_CHARDEV || fileref->fcb->type == BTRFS_TYPE_BLOCKDEV) { // NOP. If called from LXSS, humour it - we hardcode the values elsewhere. } else { Status = set_reparse_point2(fileref->fcb, acec->ReparseBuffer, acec->ReparseBufferLength, NULL, NULL, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("set_reparse_point2 returned %08lx\n", Status); fileref->deleted = true; fileref->fcb->deleted = true; if (stream.Length == 0) { ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true); parfileref->fcb->inode_item.st_size -= fileref->dc->utf8.Length * 2; ExReleaseResourceLite(parfileref->fcb->Header.Resource); } free_fileref(fileref); return Status; } } acec->OutFlags |= ATOMIC_CREATE_ECP_OUT_FLAG_REPARSE_POINT_SET; } if (acec && acec->InFlags & ATOMIC_CREATE_ECP_IN_FLAG_OP_FLAGS_SPECIFIED) { if (acec->InOpFlags & ATOMIC_CREATE_ECP_IN_OP_FLAG_CASE_SENSITIVE_FLAGS_SPECIFIED && fileref->fcb->atts & FILE_ATTRIBUTE_DIRECTORY) { if ((acec->InCaseSensitiveFlags & acec->CaseSensitiveFlagsMask) & FILE_CS_FLAG_CASE_SENSITIVE_DIR) { acec->OutCaseSensitiveFlags = FILE_CS_FLAG_CASE_SENSITIVE_DIR; fileref->fcb->case_sensitive = true; ccb->case_sensitive = true; } acec->OutOpFlags |= ATOMIC_CREATE_ECP_OUT_OP_FLAG_CASE_SENSITIVE_FLAGS_SET; } acec->OutFlags |= ATOMIC_CREATE_ECP_OUT_FLAG_OP_FLAGS_HONORED; } fileref->dc->type = fileref->fcb->type; end: if (fpus.Buffer) ExFreePool(fpus.Buffer); if (parfileref && !loaded_related) free_fileref(parfileref); return Status; } static __inline void debug_create_options(ULONG RequestedOptions) { if (RequestedOptions != 0) { ULONG options = RequestedOptions; TRACE("requested options:\n"); if (options & FILE_DIRECTORY_FILE) { TRACE(" FILE_DIRECTORY_FILE\n"); options &= ~FILE_DIRECTORY_FILE; } if (options & FILE_WRITE_THROUGH) { TRACE(" FILE_WRITE_THROUGH\n"); options &= ~FILE_WRITE_THROUGH; } if (options & FILE_SEQUENTIAL_ONLY) { TRACE(" FILE_SEQUENTIAL_ONLY\n"); options &= ~FILE_SEQUENTIAL_ONLY; } if (options & FILE_NO_INTERMEDIATE_BUFFERING) { TRACE(" FILE_NO_INTERMEDIATE_BUFFERING\n"); options &= ~FILE_NO_INTERMEDIATE_BUFFERING; } if (options & FILE_SYNCHRONOUS_IO_ALERT) { TRACE(" FILE_SYNCHRONOUS_IO_ALERT\n"); options &= ~FILE_SYNCHRONOUS_IO_ALERT; } if (options & FILE_SYNCHRONOUS_IO_NONALERT) { TRACE(" FILE_SYNCHRONOUS_IO_NONALERT\n"); options &= ~FILE_SYNCHRONOUS_IO_NONALERT; } if (options & FILE_NON_DIRECTORY_FILE) { TRACE(" FILE_NON_DIRECTORY_FILE\n"); options &= ~FILE_NON_DIRECTORY_FILE; } if (options & FILE_CREATE_TREE_CONNECTION) { TRACE(" FILE_CREATE_TREE_CONNECTION\n"); options &= ~FILE_CREATE_TREE_CONNECTION; } if (options & FILE_COMPLETE_IF_OPLOCKED) { TRACE(" FILE_COMPLETE_IF_OPLOCKED\n"); options &= ~FILE_COMPLETE_IF_OPLOCKED; } if (options & FILE_NO_EA_KNOWLEDGE) { TRACE(" FILE_NO_EA_KNOWLEDGE\n"); options &= ~FILE_NO_EA_KNOWLEDGE; } if (options & FILE_OPEN_REMOTE_INSTANCE) { TRACE(" FILE_OPEN_REMOTE_INSTANCE\n"); options &= ~FILE_OPEN_REMOTE_INSTANCE; } if (options & FILE_RANDOM_ACCESS) { TRACE(" FILE_RANDOM_ACCESS\n"); options &= ~FILE_RANDOM_ACCESS; } if (options & FILE_DELETE_ON_CLOSE) { TRACE(" FILE_DELETE_ON_CLOSE\n"); options &= ~FILE_DELETE_ON_CLOSE; } if (options & FILE_OPEN_BY_FILE_ID) { TRACE(" FILE_OPEN_BY_FILE_ID\n"); options &= ~FILE_OPEN_BY_FILE_ID; } if (options & FILE_OPEN_FOR_BACKUP_INTENT) { TRACE(" FILE_OPEN_FOR_BACKUP_INTENT\n"); options &= ~FILE_OPEN_FOR_BACKUP_INTENT; } if (options & FILE_NO_COMPRESSION) { TRACE(" FILE_NO_COMPRESSION\n"); options &= ~FILE_NO_COMPRESSION; } #if NTDDI_VERSION >= NTDDI_WIN7 if (options & FILE_OPEN_REQUIRING_OPLOCK) { TRACE(" FILE_OPEN_REQUIRING_OPLOCK\n"); options &= ~FILE_OPEN_REQUIRING_OPLOCK; } if (options & FILE_DISALLOW_EXCLUSIVE) { TRACE(" FILE_DISALLOW_EXCLUSIVE\n"); options &= ~FILE_DISALLOW_EXCLUSIVE; } #endif if (options & FILE_RESERVE_OPFILTER) { TRACE(" FILE_RESERVE_OPFILTER\n"); options &= ~FILE_RESERVE_OPFILTER; } if (options & FILE_OPEN_REPARSE_POINT) { TRACE(" FILE_OPEN_REPARSE_POINT\n"); options &= ~FILE_OPEN_REPARSE_POINT; } if (options & FILE_OPEN_NO_RECALL) { TRACE(" FILE_OPEN_NO_RECALL\n"); options &= ~FILE_OPEN_NO_RECALL; } if (options & FILE_OPEN_FOR_FREE_SPACE_QUERY) { TRACE(" FILE_OPEN_FOR_FREE_SPACE_QUERY\n"); options &= ~FILE_OPEN_FOR_FREE_SPACE_QUERY; } if (options) TRACE(" unknown options: %lx\n", options); } else { TRACE("requested options: (none)\n"); } } static NTSTATUS get_reparse_block(fcb* fcb, uint8_t** data) { NTSTATUS Status; if (fcb->type == BTRFS_TYPE_FILE || fcb->type == BTRFS_TYPE_SYMLINK) { ULONG size, bytes_read, i; if (fcb->type == BTRFS_TYPE_FILE && fcb->inode_item.st_size < sizeof(ULONG)) { WARN("file was too short to be a reparse point\n"); return STATUS_INVALID_PARAMETER; } // 0x10007 = 0xffff (maximum length of data buffer) + 8 bytes header size = (ULONG)min(0x10007, fcb->inode_item.st_size); if (size == 0) return STATUS_INVALID_PARAMETER; *data = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG); if (!*data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } Status = read_file(fcb, *data, 0, size, &bytes_read, NULL); if (!NT_SUCCESS(Status)) { ERR("read_file_fcb returned %08lx\n", Status); ExFreePool(*data); return Status; } if (fcb->type == BTRFS_TYPE_SYMLINK) { ULONG stringlen, reqlen; uint16_t subnamelen, printnamelen; REPARSE_DATA_BUFFER* rdb; Status = utf8_to_utf16(NULL, 0, &stringlen, (char*)*data, bytes_read); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 1 returned %08lx\n", Status); ExFreePool(*data); return Status; } subnamelen = printnamelen = (USHORT)stringlen; reqlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + subnamelen + printnamelen; rdb = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG); if (!rdb) { ERR("out of memory\n"); ExFreePool(*data); return STATUS_INSUFFICIENT_RESOURCES; } rdb->ReparseTag = IO_REPARSE_TAG_SYMLINK; rdb->ReparseDataLength = (USHORT)(reqlen - offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer)); rdb->Reserved = 0; rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0; rdb->SymbolicLinkReparseBuffer.SubstituteNameLength = subnamelen; rdb->SymbolicLinkReparseBuffer.PrintNameOffset = subnamelen; rdb->SymbolicLinkReparseBuffer.PrintNameLength = printnamelen; rdb->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE; Status = utf8_to_utf16(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], stringlen, &stringlen, (char*)*data, size); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 2 returned %08lx\n", Status); ExFreePool(rdb); ExFreePool(*data); return Status; } for (i = 0; i < stringlen / sizeof(WCHAR); i++) { if (rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] == '/') rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] = '\\'; } RtlCopyMemory(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)], &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], rdb->SymbolicLinkReparseBuffer.SubstituteNameLength); ExFreePool(*data); *data = (uint8_t*)rdb; } else { Status = fFsRtlValidateReparsePointBuffer(bytes_read, (REPARSE_DATA_BUFFER*)*data); if (!NT_SUCCESS(Status)) { ERR("FsRtlValidateReparsePointBuffer returned %08lx\n", Status); ExFreePool(*data); return Status; } } } else if (fcb->type == BTRFS_TYPE_DIRECTORY) { if (!fcb->reparse_xattr.Buffer || fcb->reparse_xattr.Length == 0) return STATUS_INTERNAL_ERROR; if (fcb->reparse_xattr.Length < sizeof(ULONG)) { WARN("xattr was too short to be a reparse point\n"); return STATUS_INTERNAL_ERROR; } Status = fFsRtlValidateReparsePointBuffer(fcb->reparse_xattr.Length, (REPARSE_DATA_BUFFER*)fcb->reparse_xattr.Buffer); if (!NT_SUCCESS(Status)) { ERR("FsRtlValidateReparsePointBuffer returned %08lx\n", Status); return Status; } *data = ExAllocatePoolWithTag(PagedPool, fcb->reparse_xattr.Length, ALLOC_TAG); if (!*data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(*data, fcb->reparse_xattr.Buffer, fcb->reparse_xattr.Length); } else return STATUS_INVALID_PARAMETER; return STATUS_SUCCESS; } static void fcb_load_csums(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, fcb* fcb, PIRP Irp) { LIST_ENTRY* le; NTSTATUS Status; if (fcb->csum_loaded) return; if (IsListEmpty(&fcb->extents) || fcb->inode_item.flags & BTRFS_INODE_NODATASUM) goto end; le = fcb->extents.Flink; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (!ext->ignore && ext->extent_data.type == EXTENT_TYPE_REGULAR) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)&ext->extent_data.data[0]; uint64_t len; len = (ext->extent_data.compression == BTRFS_COMPRESSION_NONE ? ed2->num_bytes : ed2->size) >> Vcb->sector_shift; ext->csum = ExAllocatePoolWithTag(NonPagedPool, (ULONG)(len * Vcb->csum_size), ALLOC_TAG); if (!ext->csum) { ERR("out of memory\n"); goto end; } Status = load_csum(Vcb, ext->csum, ed2->address + (ext->extent_data.compression == BTRFS_COMPRESSION_NONE ? ed2->offset : 0), len, Irp); if (!NT_SUCCESS(Status)) { ERR("load_csum returned %08lx\n", Status); goto end; } } le = le->Flink; } end: fcb->csum_loaded = true; } static NTSTATUS open_file3(device_extension* Vcb, PIRP Irp, ACCESS_MASK granted_access, file_ref* fileref, LIST_ENTRY* rollback) { NTSTATUS Status; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); ULONG options = IrpSp->Parameters.Create.Options & FILE_VALID_OPTION_FLAGS; ULONG RequestedDisposition = ((IrpSp->Parameters.Create.Options >> 24) & 0xff); PFILE_OBJECT FileObject = IrpSp->FileObject; POOL_TYPE pool_type = IrpSp->Flags & SL_OPEN_PAGING_FILE ? NonPagedPool : PagedPool; ccb* ccb; if (granted_access & FILE_WRITE_DATA || options & FILE_DELETE_ON_CLOSE) { if (!MmFlushImageSection(&fileref->fcb->nonpaged->segment_object, MmFlushForWrite)) return (options & FILE_DELETE_ON_CLOSE) ? STATUS_CANNOT_DELETE : STATUS_SHARING_VIOLATION; } if (RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF || RequestedDisposition == FILE_SUPERSEDE) { ULONG defda, oldatts, filter; LARGE_INTEGER time; BTRFS_TIME now; if (!fileref->fcb->ads && (IrpSp->Parameters.Create.FileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) != ((fileref->fcb->atts & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN)))) return STATUS_ACCESS_DENIED; if (fileref->fcb->ads) { Status = stream_set_end_of_file_information(Vcb, 0, fileref->fcb, fileref, false); if (!NT_SUCCESS(Status)) { ERR("stream_set_end_of_file_information returned %08lx\n", Status); return Status; } } else { Status = truncate_file(fileref->fcb, 0, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("truncate_file returned %08lx\n", Status); return Status; } } if (Irp->Overlay.AllocationSize.QuadPart > 0) { Status = extend_file(fileref->fcb, fileref, Irp->Overlay.AllocationSize.QuadPart, true, NULL, rollback); if (!NT_SUCCESS(Status)) { ERR("extend_file returned %08lx\n", Status); return Status; } } if (!fileref->fcb->ads) { LIST_ENTRY* le; if (Irp->AssociatedIrp.SystemBuffer && IrpSp->Parameters.Create.EaLength > 0) { ULONG offset; FILE_FULL_EA_INFORMATION* eainfo; Status = IoCheckEaBufferValidity(Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.Create.EaLength, &offset); if (!NT_SUCCESS(Status)) { ERR("IoCheckEaBufferValidity returned %08lx (error at offset %lu)\n", Status, offset); return Status; } fileref->fcb->ealen = 4; // capitalize EA name eainfo = Irp->AssociatedIrp.SystemBuffer; do { STRING s; s.Length = s.MaximumLength = eainfo->EaNameLength; s.Buffer = eainfo->EaName; RtlUpperString(&s, &s); fileref->fcb->ealen += 5 + eainfo->EaNameLength + eainfo->EaValueLength; if (eainfo->NextEntryOffset == 0) break; eainfo = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)eainfo) + eainfo->NextEntryOffset); } while (true); if (fileref->fcb->ea_xattr.Buffer) ExFreePool(fileref->fcb->ea_xattr.Buffer); fileref->fcb->ea_xattr.Buffer = ExAllocatePoolWithTag(pool_type, IrpSp->Parameters.Create.EaLength, ALLOC_TAG); if (!fileref->fcb->ea_xattr.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } fileref->fcb->ea_xattr.Length = fileref->fcb->ea_xattr.MaximumLength = (USHORT)IrpSp->Parameters.Create.EaLength; RtlCopyMemory(fileref->fcb->ea_xattr.Buffer, Irp->AssociatedIrp.SystemBuffer, fileref->fcb->ea_xattr.Length); } else { if (fileref->fcb->ea_xattr.Length > 0) { ExFreePool(fileref->fcb->ea_xattr.Buffer); fileref->fcb->ea_xattr.Buffer = NULL; fileref->fcb->ea_xattr.Length = fileref->fcb->ea_xattr.MaximumLength = 0; fileref->fcb->ea_changed = true; fileref->fcb->ealen = 0; } } // remove streams and send notifications le = fileref->fcb->dir_children_index.Flink; while (le != &fileref->fcb->dir_children_index) { dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index); LIST_ENTRY* le2 = le->Flink; if (dc->index == 0) { if (!dc->fileref) { file_ref* fr2; Status = open_fileref_child(Vcb, fileref, &dc->name, true, true, true, PagedPool, &fr2, NULL); if (!NT_SUCCESS(Status)) WARN("open_fileref_child returned %08lx\n", Status); } if (dc->fileref) { queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_STREAM_NAME, FILE_ACTION_REMOVED_STREAM, &dc->name); Status = delete_fileref(dc->fileref, NULL, false, NULL, rollback); if (!NT_SUCCESS(Status)) { ERR("delete_fileref returned %08lx\n", Status); return Status; } } } else break; le = le2; } } KeQuerySystemTime(&time); win_time_to_unix(time, &now); filter = FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE; if (fileref->fcb->ads) { fileref->parent->fcb->inode_item.st_mtime = now; fileref->parent->fcb->inode_item_changed = true; mark_fcb_dirty(fileref->parent->fcb); queue_notification_fcb(fileref->parent, filter, FILE_ACTION_MODIFIED, &fileref->dc->name); } else { mark_fcb_dirty(fileref->fcb); oldatts = fileref->fcb->atts; defda = get_file_attributes(Vcb, fileref->fcb->subvol, fileref->fcb->inode, fileref->fcb->type, fileref->dc && fileref->dc->name.Length >= sizeof(WCHAR) && fileref->dc->name.Buffer[0] == '.', true, Irp); if (RequestedDisposition == FILE_SUPERSEDE) fileref->fcb->atts = IrpSp->Parameters.Create.FileAttributes | FILE_ATTRIBUTE_ARCHIVE; else fileref->fcb->atts |= IrpSp->Parameters.Create.FileAttributes | FILE_ATTRIBUTE_ARCHIVE; if (fileref->fcb->atts != oldatts) { fileref->fcb->atts_changed = true; fileref->fcb->atts_deleted = IrpSp->Parameters.Create.FileAttributes == defda; filter |= FILE_NOTIFY_CHANGE_ATTRIBUTES; } fileref->fcb->inode_item.transid = Vcb->superblock.generation; fileref->fcb->inode_item.sequence++; fileref->fcb->inode_item.st_ctime = now; fileref->fcb->inode_item.st_mtime = now; fileref->fcb->inode_item_changed = true; queue_notification_fcb(fileref, filter, FILE_ACTION_MODIFIED, NULL); } } else { if (options & FILE_NO_EA_KNOWLEDGE && fileref->fcb->ea_xattr.Length > 0) { FILE_FULL_EA_INFORMATION* ffei = (FILE_FULL_EA_INFORMATION*)fileref->fcb->ea_xattr.Buffer; do { if (ffei->Flags & FILE_NEED_EA) { WARN("returning STATUS_ACCESS_DENIED as no EA knowledge\n"); return STATUS_ACCESS_DENIED; } if (ffei->NextEntryOffset == 0) break; ffei = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ffei) + ffei->NextEntryOffset); } while (true); } } FileObject->FsContext = fileref->fcb; ccb = ExAllocatePoolWithTag(NonPagedPool, sizeof(*ccb), ALLOC_TAG); if (!ccb) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(ccb, sizeof(*ccb)); ccb->NodeType = BTRFS_NODE_TYPE_CCB; ccb->NodeSize = sizeof(*ccb); ccb->disposition = RequestedDisposition; ccb->options = options; ccb->query_dir_offset = 0; RtlInitUnicodeString(&ccb->query_string, NULL); ccb->has_wildcard = false; ccb->specific_file = false; ccb->access = granted_access; ccb->case_sensitive = IrpSp->Flags & SL_CASE_SENSITIVE; ccb->reserving = false; ccb->lxss = called_from_lxss(); ccb->fileref = fileref; FileObject->FsContext2 = ccb; FileObject->SectionObjectPointer = &fileref->fcb->nonpaged->segment_object; switch (RequestedDisposition) { case FILE_SUPERSEDE: Irp->IoStatus.Information = FILE_SUPERSEDED; break; case FILE_OPEN: case FILE_OPEN_IF: Irp->IoStatus.Information = FILE_OPENED; break; case FILE_OVERWRITE: case FILE_OVERWRITE_IF: Irp->IoStatus.Information = FILE_OVERWRITTEN; break; } // Make sure paging files don't have any extents marked as being prealloc, // as this would mean we'd have to lock exclusively when writing. if (IrpSp->Flags & SL_OPEN_PAGING_FILE) { LIST_ENTRY* le; bool changed = false; ExAcquireResourceExclusiveLite(fileref->fcb->Header.Resource, true); le = fileref->fcb->extents.Flink; while (le != &fileref->fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (ext->extent_data.type == EXTENT_TYPE_PREALLOC) { ext->extent_data.type = EXTENT_TYPE_REGULAR; changed = true; } le = le->Flink; } ExReleaseResourceLite(fileref->fcb->Header.Resource); if (changed) { fileref->fcb->extents_changed = true; mark_fcb_dirty(fileref->fcb); } fileref->fcb->Header.Flags2 |= FSRTL_FLAG2_IS_PAGING_FILE; } #ifdef DEBUG_FCB_REFCOUNTS LONG oc = InterlockedIncrement(&fileref->open_count); ERR("fileref %p: open_count now %i\n", fileref, oc); #else InterlockedIncrement(&fileref->open_count); #endif InterlockedIncrement(&Vcb->open_files); return STATUS_SUCCESS; } static void __stdcall oplock_complete(PVOID Context, PIRP Irp) { NTSTATUS Status; LIST_ENTRY rollback; bool skip_lock; oplock_context* ctx = Context; device_extension* Vcb = ctx->Vcb; TRACE("(%p, %p)\n", Context, Irp); InitializeListHead(&rollback); skip_lock = ExIsResourceAcquiredExclusiveLite(&Vcb->tree_lock); if (!skip_lock) ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceSharedLite(&Vcb->fileref_lock, true); // FIXME - trans Status = open_file3(Vcb, Irp, ctx->granted_access, ctx->fileref, &rollback); if (!NT_SUCCESS(Status)) { free_fileref(ctx->fileref); do_rollback(ctx->Vcb, &rollback); } else clear_rollback(&rollback); ExReleaseResourceLite(&Vcb->fileref_lock); if (Status == STATUS_SUCCESS) { fcb* fcb2; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; bool skip_fcb_lock; IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess |= ctx->granted_access; IrpSp->Parameters.Create.SecurityContext->AccessState->RemainingDesiredAccess &= ~(ctx->granted_access | MAXIMUM_ALLOWED); if (!FileObject->Vpb) FileObject->Vpb = Vcb->devobj->Vpb; fcb2 = FileObject->FsContext; if (fcb2->ads) { struct _ccb* ccb2 = FileObject->FsContext2; fcb2 = ccb2->fileref->parent->fcb; } skip_fcb_lock = ExIsResourceAcquiredExclusiveLite(fcb2->Header.Resource); if (!skip_fcb_lock) ExAcquireResourceExclusiveLite(fcb2->Header.Resource, true); fcb_load_csums(Vcb, fcb2, Irp); if (!skip_fcb_lock) ExReleaseResourceLite(fcb2->Header.Resource); } if (!skip_lock) ExReleaseResourceLite(&Vcb->tree_lock); // FIXME - call free_trans if failed and within transaction Irp->IoStatus.Status = Status; IoCompleteRequest(Irp, NT_SUCCESS(Status) ? IO_DISK_INCREMENT : IO_NO_INCREMENT); ctx->Status = Status; KeSetEvent(&ctx->event, 0, false); } static NTSTATUS open_file2(device_extension* Vcb, ULONG RequestedDisposition, file_ref* fileref, ACCESS_MASK* granted_access, PFILE_OBJECT FileObject, UNICODE_STRING* fn, ULONG options, PIRP Irp, LIST_ENTRY* rollback, oplock_context** opctx) { NTSTATUS Status; file_ref* sf; bool readonly; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); if (RequestedDisposition == FILE_SUPERSEDE || RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF) { LARGE_INTEGER zero; if (fileref->fcb->type == BTRFS_TYPE_DIRECTORY || is_subvol_readonly(fileref->fcb->subvol, Irp)) { Status = STATUS_ACCESS_DENIED; goto end; } if (Vcb->readonly) { Status = STATUS_MEDIA_WRITE_PROTECTED; goto end; } zero.QuadPart = 0; if (!MmCanFileBeTruncated(&fileref->fcb->nonpaged->segment_object, &zero)) { Status = STATUS_USER_MAPPED_FILE; goto end; } } if (IrpSp->Parameters.Create.SecurityContext->DesiredAccess != 0) { SeLockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext); if (!SeAccessCheck((fileref->fcb->ads || fileref->fcb == Vcb->dummy_fcb) ? fileref->parent->fcb->sd : fileref->fcb->sd, &IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext, true, IrpSp->Parameters.Create.SecurityContext->DesiredAccess, 0, NULL, IoGetFileObjectGenericMapping(), IrpSp->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode, granted_access, &Status)) { SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext); TRACE("SeAccessCheck failed, returning %08lx\n", Status); goto end; } SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext); } else *granted_access = 0; TRACE("deleted = %s\n", fileref->deleted ? "true" : "false"); sf = fileref; while (sf) { if (sf->delete_on_close) { TRACE("could not open as deletion pending\n"); Status = STATUS_DELETE_PENDING; goto end; } sf = sf->parent; } readonly = (!fileref->fcb->ads && fileref->fcb->atts & FILE_ATTRIBUTE_READONLY && !(IrpSp->Flags & SL_IGNORE_READONLY_ATTRIBUTE)) || (fileref->fcb->ads && fileref->parent->fcb->atts & FILE_ATTRIBUTE_READONLY && !(IrpSp->Flags & SL_IGNORE_READONLY_ATTRIBUTE)) || is_subvol_readonly(fileref->fcb->subvol, Irp) || fileref->fcb == Vcb->dummy_fcb || Vcb->readonly; if (options & FILE_DELETE_ON_CLOSE && (fileref == Vcb->root_fileref || readonly)) { Status = STATUS_CANNOT_DELETE; goto end; } readonly |= fileref->fcb->inode_item.flags_ro & BTRFS_INODE_RO_VERITY; if (readonly) { ACCESS_MASK allowed; allowed = READ_CONTROL | SYNCHRONIZE | ACCESS_SYSTEM_SECURITY | FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | FILE_EXECUTE | FILE_LIST_DIRECTORY | FILE_TRAVERSE; if (!Vcb->readonly && (fileref->fcb == Vcb->dummy_fcb || fileref->fcb->inode == SUBVOL_ROOT_INODE)) allowed |= DELETE; if (fileref->fcb != Vcb->dummy_fcb && !is_subvol_readonly(fileref->fcb->subvol, Irp) && !Vcb->readonly) { allowed |= DELETE | WRITE_OWNER | WRITE_DAC | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES; if (!fileref->fcb->ads && fileref->fcb->type == BTRFS_TYPE_DIRECTORY) allowed |= FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE | FILE_DELETE_CHILD; } else if (fileref->fcb->inode == SUBVOL_ROOT_INODE && is_subvol_readonly(fileref->fcb->subvol, Irp) && !Vcb->readonly) { // We allow a subvolume root to be opened read-write even if its readonly flag is set, so it can be cleared allowed |= FILE_WRITE_ATTRIBUTES; } if (IrpSp->Parameters.Create.SecurityContext->DesiredAccess & MAXIMUM_ALLOWED) { *granted_access &= allowed; IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess &= allowed; } else if (*granted_access & ~allowed) { Status = Vcb->readonly ? STATUS_MEDIA_WRITE_PROTECTED : STATUS_ACCESS_DENIED; goto end; } if (RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF) { WARN("cannot overwrite readonly file\n"); Status = STATUS_ACCESS_DENIED; goto end; } } if ((fileref->fcb->type == BTRFS_TYPE_SYMLINK || fileref->fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) && !(options & FILE_OPEN_REPARSE_POINT)) { REPARSE_DATA_BUFFER* data; /* How reparse points work from the point of view of the filesystem appears to * undocumented. When returning STATUS_REPARSE, MSDN encourages us to return * IO_REPARSE in Irp->IoStatus.Information, but that means we have to do our own * translation. If we instead return the reparse tag in Information, and store * a pointer to the reparse data buffer in Irp->Tail.Overlay.AuxiliaryBuffer, * IopSymlinkProcessReparse will do the translation for us. */ Status = get_reparse_block(fileref->fcb, (uint8_t**)&data); if (!NT_SUCCESS(Status)) { ERR("get_reparse_block returned %08lx\n", Status); Status = STATUS_SUCCESS; } else { Irp->IoStatus.Information = data->ReparseTag; if (fn->Buffer[(fn->Length / sizeof(WCHAR)) - 1] == '\\') data->Reserved = sizeof(WCHAR); Irp->Tail.Overlay.AuxiliaryBuffer = (void*)data; Status = STATUS_REPARSE; goto end; } } if (fileref->fcb->type == BTRFS_TYPE_DIRECTORY && !fileref->fcb->ads) { if (options & FILE_NON_DIRECTORY_FILE && !(fileref->fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT)) { Status = STATUS_FILE_IS_A_DIRECTORY; goto end; } } else if (options & FILE_DIRECTORY_FILE) { TRACE("returning STATUS_NOT_A_DIRECTORY (type = %u)\n", fileref->fcb->type); Status = STATUS_NOT_A_DIRECTORY; goto end; } if (fileref->open_count > 0) { oplock_context* ctx; Status = IoCheckShareAccess(*granted_access, IrpSp->Parameters.Create.ShareAccess, FileObject, &fileref->fcb->share_access, false); if (!NT_SUCCESS(Status)) { if (Status == STATUS_SHARING_VIOLATION) TRACE("IoCheckShareAccess failed, returning %08lx\n", Status); else WARN("IoCheckShareAccess failed, returning %08lx\n", Status); goto end; } ctx = ExAllocatePoolWithTag(NonPagedPool, sizeof(oplock_context), ALLOC_TAG); if (!ctx) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } ctx->Vcb = Vcb; ctx->granted_access = *granted_access; ctx->fileref = fileref; KeInitializeEvent(&ctx->event, NotificationEvent, false); Status = FsRtlCheckOplock(fcb_oplock(fileref->fcb), Irp, ctx, oplock_complete, NULL); if (Status == STATUS_PENDING) { *opctx = ctx; return Status; } ExFreePool(ctx); if (!NT_SUCCESS(Status)) { WARN("FsRtlCheckOplock returned %08lx\n", Status); goto end; } IoUpdateShareAccess(FileObject, &fileref->fcb->share_access); } else IoSetShareAccess(*granted_access, IrpSp->Parameters.Create.ShareAccess, FileObject, &fileref->fcb->share_access); Status = open_file3(Vcb, Irp, *granted_access, fileref, rollback); if (!NT_SUCCESS(Status)) IoRemoveShareAccess(FileObject, &fileref->fcb->share_access); end: if (!NT_SUCCESS(Status)) free_fileref(fileref); return Status; } NTSTATUS open_fileref_by_inode(_Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb, root* subvol, uint64_t inode, file_ref** pfr, PIRP Irp) { NTSTATUS Status; fcb* fcb; uint64_t parent = 0; UNICODE_STRING name; bool hl_alloc = false; file_ref *parfr, *fr; Status = open_fcb(Vcb, subvol, inode, 0, NULL, true, NULL, &fcb, PagedPool, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fcb returned %08lx\n", Status); return Status; } ExAcquireResourceSharedLite(fcb->Header.Resource, true); if (fcb->inode_item.st_nlink == 0 || fcb->deleted) { ExReleaseResourceLite(fcb->Header.Resource); free_fcb(fcb); return STATUS_OBJECT_NAME_NOT_FOUND; } if (fcb->fileref) { *pfr = fcb->fileref; increase_fileref_refcount(fcb->fileref); free_fcb(fcb); ExReleaseResourceLite(fcb->Header.Resource); return STATUS_SUCCESS; } if (IsListEmpty(&fcb->hardlinks)) { ExReleaseResourceLite(fcb->Header.Resource); ExAcquireResourceSharedLite(&Vcb->dirty_filerefs_lock, true); if (!IsListEmpty(&Vcb->dirty_filerefs)) { LIST_ENTRY* le = Vcb->dirty_filerefs.Flink; while (le != &Vcb->dirty_filerefs) { fr = CONTAINING_RECORD(le, file_ref, list_entry_dirty); if (fr->fcb == fcb) { ExReleaseResourceLite(&Vcb->dirty_filerefs_lock); increase_fileref_refcount(fr); free_fcb(fcb); *pfr = fr; return STATUS_SUCCESS; } le = le->Flink; } } ExReleaseResourceLite(&Vcb->dirty_filerefs_lock); { KEY searchkey; traverse_ptr tp; searchkey.obj_id = fcb->inode; searchkey.obj_type = TYPE_INODE_REF; searchkey.offset = 0; Status = find_item(Vcb, subvol, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); free_fcb(fcb); return Status; } do { traverse_ptr next_tp; if (tp.item->key.obj_id > fcb->inode || (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type > TYPE_INODE_EXTREF)) break; if (tp.item->key.obj_id == fcb->inode) { if (tp.item->key.obj_type == TYPE_INODE_REF) { INODE_REF* ir = (INODE_REF*)tp.item->data; if (tp.item->size < offsetof(INODE_REF, name[0]) || tp.item->size < offsetof(INODE_REF, name[0]) + ir->n) { ERR("INODE_REF was too short\n"); free_fcb(fcb); return STATUS_INTERNAL_ERROR; } ULONG stringlen; Status = utf8_to_utf16(NULL, 0, &stringlen, ir->name, ir->n); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 1 returned %08lx\n", Status); free_fcb(fcb); return Status; } name.Length = name.MaximumLength = (uint16_t)stringlen; if (stringlen == 0) name.Buffer = NULL; else { name.Buffer = ExAllocatePoolWithTag(PagedPool, name.MaximumLength, ALLOC_TAG); if (!name.Buffer) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } Status = utf8_to_utf16(name.Buffer, stringlen, &stringlen, ir->name, ir->n); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 2 returned %08lx\n", Status); ExFreePool(name.Buffer); free_fcb(fcb); return Status; } hl_alloc = true; } parent = tp.item->key.offset; break; } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) { INODE_EXTREF* ier = (INODE_EXTREF*)tp.item->data; if (tp.item->size < offsetof(INODE_EXTREF, name[0]) || tp.item->size < offsetof(INODE_EXTREF, name[0]) + ier->n) { ERR("INODE_EXTREF was too short\n"); free_fcb(fcb); return STATUS_INTERNAL_ERROR; } ULONG stringlen; Status = utf8_to_utf16(NULL, 0, &stringlen, ier->name, ier->n); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 1 returned %08lx\n", Status); free_fcb(fcb); return Status; } name.Length = name.MaximumLength = (uint16_t)stringlen; if (stringlen == 0) name.Buffer = NULL; else { name.Buffer = ExAllocatePoolWithTag(PagedPool, name.MaximumLength, ALLOC_TAG); if (!name.Buffer) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } Status = utf8_to_utf16(name.Buffer, stringlen, &stringlen, ier->name, ier->n); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 2 returned %08lx\n", Status); ExFreePool(name.Buffer); free_fcb(fcb); return Status; } hl_alloc = true; } parent = ier->dir; break; } } if (find_next_item(Vcb, &tp, &next_tp, false, Irp)) tp = next_tp; else break; } while (true); } if (parent == 0) { WARN("trying to open inode with no references\n"); free_fcb(fcb); return STATUS_INVALID_PARAMETER; } } else { hardlink* hl = CONTAINING_RECORD(fcb->hardlinks.Flink, hardlink, list_entry); name = hl->name; parent = hl->parent; ExReleaseResourceLite(fcb->Header.Resource); } if (parent == inode) { // subvolume root KEY searchkey; traverse_ptr tp; searchkey.obj_id = subvol->id; searchkey.obj_type = TYPE_ROOT_BACKREF; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); free_fcb(fcb); return Status; } if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { ROOT_REF* rr = (ROOT_REF*)tp.item->data; LIST_ENTRY* le; root* r = NULL; ULONG stringlen; if (tp.item->size < sizeof(ROOT_REF)) { 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)); free_fcb(fcb); return STATUS_INTERNAL_ERROR; } if (tp.item->size < offsetof(ROOT_REF, name[0]) + rr->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); free_fcb(fcb); return STATUS_INTERNAL_ERROR; } le = Vcb->roots.Flink; while (le != &Vcb->roots) { root* r2 = CONTAINING_RECORD(le, root, list_entry); if (r2->id == tp.item->key.offset) { r = r2; break; } le = le->Flink; } if (!r) { ERR("couldn't find subvol %I64x\n", tp.item->key.offset); free_fcb(fcb); return STATUS_INTERNAL_ERROR; } Status = open_fileref_by_inode(Vcb, r, rr->dir, &parfr, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fileref_by_inode returned %08lx\n", Status); free_fcb(fcb); return Status; } Status = utf8_to_utf16(NULL, 0, &stringlen, rr->name, rr->n); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 1 returned %08lx\n", Status); free_fcb(fcb); return Status; } name.Length = name.MaximumLength = (uint16_t)stringlen; if (stringlen == 0) name.Buffer = NULL; else { if (hl_alloc) ExFreePool(name.Buffer); name.Buffer = ExAllocatePoolWithTag(PagedPool, name.MaximumLength, ALLOC_TAG); if (!name.Buffer) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } Status = utf8_to_utf16(name.Buffer, stringlen, &stringlen, rr->name, rr->n); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 2 returned %08lx\n", Status); ExFreePool(name.Buffer); free_fcb(fcb); return Status; } hl_alloc = true; } } else { if (!Vcb->options.no_root_dir && subvol->id == BTRFS_ROOT_FSTREE && Vcb->root_fileref->fcb->subvol != subvol) { Status = open_fileref_by_inode(Vcb, Vcb->root_fileref->fcb->subvol, SUBVOL_ROOT_INODE, &parfr, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fileref_by_inode returned %08lx\n", Status); free_fcb(fcb); return Status; } name.Length = name.MaximumLength = sizeof(root_dir_utf16) - sizeof(WCHAR); name.Buffer = (WCHAR*)root_dir_utf16; } else { ERR("couldn't find parent for subvol %I64x\n", subvol->id); free_fcb(fcb); return STATUS_INTERNAL_ERROR; } } } else { Status = open_fileref_by_inode(Vcb, subvol, parent, &parfr, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fileref_by_inode returned %08lx\n", Status); free_fcb(fcb); return Status; } } Status = open_fileref_child(Vcb, parfr, &name, true, true, false, PagedPool, &fr, Irp); if (hl_alloc) ExFreePool(name.Buffer); if (!NT_SUCCESS(Status)) { ERR("open_fileref_child returned %08lx\n", Status); free_fcb(fcb); free_fileref(parfr); return Status; } *pfr = fr; free_fcb(fcb); free_fileref(parfr); return STATUS_SUCCESS; } static NTSTATUS open_file(PDEVICE_OBJECT DeviceObject, _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback, oplock_context** opctx) { PFILE_OBJECT FileObject = NULL; ULONG RequestedDisposition; ULONG options; NTSTATUS Status; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); USHORT parsed; ULONG fn_offset = 0; file_ref *related, *fileref = NULL; POOL_TYPE pool_type = IrpSp->Flags & SL_OPEN_PAGING_FILE ? NonPagedPool : PagedPool; ACCESS_MASK granted_access; bool loaded_related = false; UNICODE_STRING fn; Irp->IoStatus.Information = 0; RequestedDisposition = ((IrpSp->Parameters.Create.Options >> 24) & 0xff); options = IrpSp->Parameters.Create.Options & FILE_VALID_OPTION_FLAGS; if (options & FILE_DIRECTORY_FILE && RequestedDisposition == FILE_SUPERSEDE) { WARN("error - supersede requested with FILE_DIRECTORY_FILE\n"); return STATUS_INVALID_PARAMETER; } FileObject = IrpSp->FileObject; if (!FileObject) { ERR("FileObject was NULL\n"); return STATUS_INVALID_PARAMETER; } if (FileObject->RelatedFileObject && FileObject->RelatedFileObject->FsContext2) { struct _ccb* relatedccb = FileObject->RelatedFileObject->FsContext2; related = relatedccb->fileref; } else related = NULL; debug_create_options(options); switch (RequestedDisposition) { case FILE_SUPERSEDE: TRACE("requested disposition: FILE_SUPERSEDE\n"); break; case FILE_CREATE: TRACE("requested disposition: FILE_CREATE\n"); break; case FILE_OPEN: TRACE("requested disposition: FILE_OPEN\n"); break; case FILE_OPEN_IF: TRACE("requested disposition: FILE_OPEN_IF\n"); break; case FILE_OVERWRITE: TRACE("requested disposition: FILE_OVERWRITE\n"); break; case FILE_OVERWRITE_IF: TRACE("requested disposition: FILE_OVERWRITE_IF\n"); break; default: ERR("unknown disposition: %lx\n", RequestedDisposition); Status = STATUS_NOT_IMPLEMENTED; goto exit; } fn = FileObject->FileName; TRACE("(%.*S)\n", (int)(fn.Length / sizeof(WCHAR)), fn.Buffer); TRACE("FileObject = %p\n", FileObject); if (Vcb->readonly && (RequestedDisposition == FILE_SUPERSEDE || RequestedDisposition == FILE_CREATE || RequestedDisposition == FILE_OVERWRITE)) { Status = STATUS_MEDIA_WRITE_PROTECTED; goto exit; } if (options & FILE_OPEN_BY_FILE_ID) { if (RequestedDisposition != FILE_OPEN) { WARN("FILE_OPEN_BY_FILE_ID not supported for anything other than FILE_OPEN\n"); Status = STATUS_INVALID_PARAMETER; goto exit; } if (fn.Length == sizeof(uint64_t)) { uint64_t inode; if (!related) { WARN("cannot open by short file ID unless related fileref also provided\n"); Status = STATUS_INVALID_PARAMETER; goto exit; } inode = (*(uint64_t*)fn.Buffer) & 0xffffffffff; if (related->fcb == Vcb->root_fileref->fcb && inode == 0) inode = Vcb->root_fileref->fcb->inode; if (inode == 0) { // we use 0 to mean the parent of a subvolume fileref = related->parent; increase_fileref_refcount(fileref); Status = STATUS_SUCCESS; } else Status = open_fileref_by_inode(Vcb, related->fcb->subvol, inode, &fileref, Irp); goto loaded; } else if (fn.Length == sizeof(FILE_ID_128)) { uint64_t inode, subvol_id; root* subvol = NULL; RtlCopyMemory(&inode, fn.Buffer, sizeof(uint64_t)); RtlCopyMemory(&subvol_id, (uint8_t*)fn.Buffer + sizeof(uint64_t), sizeof(uint64_t)); if (subvol_id == BTRFS_ROOT_FSTREE || (subvol_id >= 0x100 && subvol_id < 0x8000000000000000)) { LIST_ENTRY* le = Vcb->roots.Flink; while (le != &Vcb->roots) { root* r = CONTAINING_RECORD(le, root, list_entry); if (r->id == subvol_id) { subvol = r; break; } le = le->Flink; } } if (!subvol) { WARN("subvol %I64x not found\n", subvol_id); Status = STATUS_OBJECT_NAME_NOT_FOUND; } else Status = open_fileref_by_inode(Vcb, subvol, inode, &fileref, Irp); goto loaded; } else { WARN("invalid ID size for FILE_OPEN_BY_FILE_ID\n"); Status = STATUS_INVALID_PARAMETER; goto exit; } } if (related && fn.Length != 0 && fn.Buffer[0] == '\\') { Status = STATUS_INVALID_PARAMETER; goto exit; } if (!related && RequestedDisposition != FILE_OPEN && !(IrpSp->Flags & SL_OPEN_TARGET_DIRECTORY)) { ULONG fnoff; Status = open_fileref(Vcb, &related, &fn, NULL, true, &parsed, &fnoff, pool_type, IrpSp->Flags & SL_CASE_SENSITIVE, Irp); if (Status == STATUS_OBJECT_NAME_NOT_FOUND) Status = STATUS_OBJECT_PATH_NOT_FOUND; else if (Status == STATUS_REPARSE) fileref = related; else if (NT_SUCCESS(Status)) { fnoff *= sizeof(WCHAR); fnoff += (related->dc ? related->dc->name.Length : 0) + sizeof(WCHAR); if (related->fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) { Status = STATUS_REPARSE; fileref = related; parsed = (USHORT)fnoff - sizeof(WCHAR); } else { fn.Buffer = &fn.Buffer[fnoff / sizeof(WCHAR)]; fn.Length -= (USHORT)fnoff; Status = open_fileref(Vcb, &fileref, &fn, related, IrpSp->Flags & SL_OPEN_TARGET_DIRECTORY, &parsed, &fn_offset, pool_type, IrpSp->Flags & SL_CASE_SENSITIVE, Irp); loaded_related = true; } } } else { Status = open_fileref(Vcb, &fileref, &fn, related, IrpSp->Flags & SL_OPEN_TARGET_DIRECTORY, &parsed, &fn_offset, pool_type, IrpSp->Flags & SL_CASE_SENSITIVE, Irp); } loaded: if (Status == STATUS_REPARSE) { REPARSE_DATA_BUFFER* data; ExAcquireResourceSharedLite(fileref->fcb->Header.Resource, true); Status = get_reparse_block(fileref->fcb, (uint8_t**)&data); ExReleaseResourceLite(fileref->fcb->Header.Resource); if (!NT_SUCCESS(Status)) { ERR("get_reparse_block returned %08lx\n", Status); Status = STATUS_SUCCESS; } else { Status = STATUS_REPARSE; RtlCopyMemory(&Irp->IoStatus.Information, data, sizeof(ULONG)); data->Reserved = FileObject->FileName.Length - parsed; Irp->Tail.Overlay.AuxiliaryBuffer = (void*)data; free_fileref(fileref); goto exit; } } if (NT_SUCCESS(Status) && fileref->deleted) Status = STATUS_OBJECT_NAME_NOT_FOUND; if (NT_SUCCESS(Status)) { if (RequestedDisposition == FILE_CREATE) { TRACE("file already exists, returning STATUS_OBJECT_NAME_COLLISION\n"); Status = STATUS_OBJECT_NAME_COLLISION; free_fileref(fileref); goto exit; } } else if (Status == STATUS_OBJECT_NAME_NOT_FOUND) { if (RequestedDisposition == FILE_OPEN || RequestedDisposition == FILE_OVERWRITE) { TRACE("file doesn't exist, returning STATUS_OBJECT_NAME_NOT_FOUND\n"); goto exit; } } else if (Status == STATUS_OBJECT_PATH_NOT_FOUND || Status == STATUS_OBJECT_NAME_INVALID) { TRACE("open_fileref returned %08lx\n", Status); goto exit; } else { ERR("open_fileref returned %08lx\n", Status); goto exit; } if (NT_SUCCESS(Status)) { // file already exists Status = open_file2(Vcb, RequestedDisposition, fileref, &granted_access, FileObject, &fn, options, Irp, rollback, opctx); } else { file_ref* existing_file = NULL; Status = file_create(Irp, Vcb, FileObject, related, loaded_related, &fn, RequestedDisposition, options, &existing_file, rollback); if (Status == STATUS_OBJECT_NAME_COLLISION) { // already exists fileref = existing_file; Status = open_file2(Vcb, RequestedDisposition, fileref, &granted_access, FileObject, &fn, options, Irp, rollback, opctx); } else { Irp->IoStatus.Information = NT_SUCCESS(Status) ? FILE_CREATED : 0; granted_access = IrpSp->Parameters.Create.SecurityContext->DesiredAccess; } } if (NT_SUCCESS(Status) && !(options & FILE_NO_INTERMEDIATE_BUFFERING)) FileObject->Flags |= FO_CACHE_SUPPORTED; exit: if (loaded_related) free_fileref(related); if (Status == STATUS_SUCCESS) { fcb* fcb2; IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess |= granted_access; IrpSp->Parameters.Create.SecurityContext->AccessState->RemainingDesiredAccess &= ~(granted_access | MAXIMUM_ALLOWED); if (!FileObject->Vpb) FileObject->Vpb = DeviceObject->Vpb; fcb2 = FileObject->FsContext; if (fcb2->ads) { struct _ccb* ccb2 = FileObject->FsContext2; fcb2 = ccb2->fileref->parent->fcb; } ExAcquireResourceExclusiveLite(fcb2->Header.Resource, true); fcb_load_csums(Vcb, fcb2, Irp); ExReleaseResourceLite(fcb2->Header.Resource); } else if (Status != STATUS_REPARSE && Status != STATUS_OBJECT_NAME_NOT_FOUND && Status != STATUS_OBJECT_PATH_NOT_FOUND) TRACE("returning %08lx\n", Status); return Status; } static NTSTATUS verify_vcb(device_extension* Vcb, PIRP Irp) { NTSTATUS Status; LIST_ENTRY* le; bool need_verify = false; ExAcquireResourceSharedLite(&Vcb->tree_lock, true); le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (dev->devobj && dev->removable) { ULONG cc; IO_STATUS_BLOCK iosb; Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, &cc, sizeof(ULONG), true, &iosb); if (IoIsErrorUserInduced(Status)) { ERR("IOCTL_STORAGE_CHECK_VERIFY returned %08lx (user-induced)\n", Status); need_verify = true; } else if (!NT_SUCCESS(Status)) { ERR("IOCTL_STORAGE_CHECK_VERIFY returned %08lx\n", Status); goto end; } else if (iosb.Information < sizeof(ULONG)) { ERR("iosb.Information was too short\n"); Status = STATUS_INTERNAL_ERROR; } else if (cc != dev->change_count) { dev->devobj->Flags |= DO_VERIFY_VOLUME; need_verify = true; } } le = le->Flink; } Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&Vcb->tree_lock); if (need_verify) { PDEVICE_OBJECT devobj; devobj = IoGetDeviceToVerify(Irp->Tail.Overlay.Thread); IoSetDeviceToVerify(Irp->Tail.Overlay.Thread, NULL); if (!devobj) { devobj = IoGetDeviceToVerify(PsGetCurrentThread()); IoSetDeviceToVerify(PsGetCurrentThread(), NULL); } devobj = Vcb->Vpb ? Vcb->Vpb->RealDevice : NULL; if (devobj) Status = IoVerifyVolume(devobj, false); else Status = STATUS_VERIFY_REQUIRED; } return Status; } static bool has_manage_volume_privilege(ACCESS_STATE* access_state, KPROCESSOR_MODE processor_mode) { PRIVILEGE_SET privset; privset.PrivilegeCount = 1; privset.Control = PRIVILEGE_SET_ALL_NECESSARY; privset.Privilege[0].Luid = RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE); privset.Privilege[0].Attributes = 0; return SePrivilegeCheck(&privset, &access_state->SubjectSecurityContext, processor_mode) ? true : false; } _Dispatch_type_(IRP_MJ_CREATE) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS Status; PIO_STACK_LOCATION IrpSp; device_extension* Vcb = DeviceObject->DeviceExtension; bool top_level, locked = false; oplock_context* opctx = NULL; FsRtlEnterFileSystem(); TRACE("create (flags = %lx)\n", Irp->Flags); top_level = is_top_level(Irp); /* return success if just called for FS device object */ if (DeviceObject == master_devobj) { TRACE("create called for FS device object\n"); Irp->IoStatus.Information = FILE_OPENED; Status = STATUS_SUCCESS; goto exit; } else if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = vol_create(DeviceObject, Irp); goto exit; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto exit; } if (!(Vcb->Vpb->Flags & VPB_MOUNTED)) { Status = STATUS_DEVICE_NOT_READY; goto exit; } if (Vcb->removing) { Status = STATUS_ACCESS_DENIED; goto exit; } Status = verify_vcb(Vcb, Irp); if (!NT_SUCCESS(Status)) { ERR("verify_vcb returned %08lx\n", Status); goto exit; } ExAcquireResourceSharedLite(&Vcb->load_lock, true); locked = true; IrpSp = IoGetCurrentIrpStackLocation(Irp); if (IrpSp->Flags != 0) { uint32_t flags = IrpSp->Flags; TRACE("flags:\n"); if (flags & SL_CASE_SENSITIVE) { TRACE("SL_CASE_SENSITIVE\n"); flags &= ~SL_CASE_SENSITIVE; } if (flags & SL_FORCE_ACCESS_CHECK) { TRACE("SL_FORCE_ACCESS_CHECK\n"); flags &= ~SL_FORCE_ACCESS_CHECK; } if (flags & SL_OPEN_PAGING_FILE) { TRACE("SL_OPEN_PAGING_FILE\n"); flags &= ~SL_OPEN_PAGING_FILE; } if (flags & SL_OPEN_TARGET_DIRECTORY) { TRACE("SL_OPEN_TARGET_DIRECTORY\n"); flags &= ~SL_OPEN_TARGET_DIRECTORY; } if (flags & SL_STOP_ON_SYMLINK) { TRACE("SL_STOP_ON_SYMLINK\n"); flags &= ~SL_STOP_ON_SYMLINK; } if (flags & SL_IGNORE_READONLY_ATTRIBUTE) { TRACE("SL_IGNORE_READONLY_ATTRIBUTE\n"); flags &= ~SL_IGNORE_READONLY_ATTRIBUTE; } if (flags) WARN("unknown flags: %x\n", flags); } else { TRACE("flags: (none)\n"); } if (!IrpSp->FileObject) { ERR("FileObject was NULL\n"); Status = STATUS_INVALID_PARAMETER; goto exit; } if (IrpSp->FileObject->RelatedFileObject) { fcb* relatedfcb = IrpSp->FileObject->RelatedFileObject->FsContext; if (relatedfcb && relatedfcb->Vcb != Vcb) { WARN("RelatedFileObject was for different device\n"); Status = STATUS_INVALID_PARAMETER; goto exit; } } // opening volume if (IrpSp->FileObject->FileName.Length == 0 && !IrpSp->FileObject->RelatedFileObject) { ULONG RequestedDisposition = ((IrpSp->Parameters.Create.Options >> 24) & 0xff); ULONG RequestedOptions = IrpSp->Parameters.Create.Options & FILE_VALID_OPTION_FLAGS; #ifdef DEBUG_FCB_REFCOUNTS LONG rc; #endif ccb* ccb; TRACE("open operation for volume\n"); if (RequestedDisposition != FILE_OPEN && RequestedDisposition != FILE_OPEN_IF) { Status = STATUS_ACCESS_DENIED; goto exit; } if (RequestedOptions & FILE_DIRECTORY_FILE) { Status = STATUS_NOT_A_DIRECTORY; goto exit; } ccb = ExAllocatePoolWithTag(NonPagedPool, sizeof(*ccb), ALLOC_TAG); if (!ccb) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlZeroMemory(ccb, sizeof(*ccb)); ccb->NodeType = BTRFS_NODE_TYPE_CCB; ccb->NodeSize = sizeof(*ccb); ccb->disposition = RequestedDisposition; ccb->options = RequestedOptions; ccb->access = IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess; ccb->manage_volume_privilege = has_manage_volume_privilege(IrpSp->Parameters.Create.SecurityContext->AccessState, IrpSp->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode); ccb->reserving = false; ccb->lxss = called_from_lxss(); #ifdef DEBUG_FCB_REFCOUNTS rc = InterlockedIncrement(&Vcb->volume_fcb->refcount); WARN("fcb %p: refcount now %i (volume)\n", Vcb->volume_fcb, rc); #else InterlockedIncrement(&Vcb->volume_fcb->refcount); #endif IrpSp->FileObject->FsContext = Vcb->volume_fcb; IrpSp->FileObject->FsContext2 = ccb; IrpSp->FileObject->SectionObjectPointer = &Vcb->volume_fcb->nonpaged->segment_object; if (!IrpSp->FileObject->Vpb) IrpSp->FileObject->Vpb = DeviceObject->Vpb; InterlockedIncrement(&Vcb->open_files); Irp->IoStatus.Information = FILE_OPENED; Status = STATUS_SUCCESS; } else { LIST_ENTRY rollback; bool skip_lock; InitializeListHead(&rollback); TRACE("file name: %.*S\n", (int)(IrpSp->FileObject->FileName.Length / sizeof(WCHAR)), IrpSp->FileObject->FileName.Buffer); if (IrpSp->FileObject->RelatedFileObject) TRACE("related file = %p\n", IrpSp->FileObject->RelatedFileObject); // Don't lock again if we're being called from within CcCopyRead etc. skip_lock = ExIsResourceAcquiredExclusiveLite(&Vcb->tree_lock); if (!skip_lock) ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceSharedLite(&Vcb->fileref_lock, true); Status = open_file(DeviceObject, Vcb, Irp, &rollback, &opctx); if (!NT_SUCCESS(Status)) do_rollback(Vcb, &rollback); else clear_rollback(&rollback); ExReleaseResourceLite(&Vcb->fileref_lock); if (!skip_lock) ExReleaseResourceLite(&Vcb->tree_lock); } exit: if (Status != STATUS_PENDING) { Irp->IoStatus.Status = Status; IoCompleteRequest(Irp, NT_SUCCESS(Status) ? IO_DISK_INCREMENT : IO_NO_INCREMENT); } if (locked) ExReleaseResourceLite(&Vcb->load_lock); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&opctx->event, Executive, KernelMode, false, NULL); Status = opctx->Status; ExFreePool(opctx); } TRACE("create returning %08lx\n", Status); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } ================================================ FILE: src/devctrl.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include #include #include extern PDRIVER_OBJECT drvobj; extern LIST_ENTRY VcbList; extern ERESOURCE global_loading_lock; static NTSTATUS mountdev_query_stable_guid(device_extension* Vcb, PIRP Irp) { MOUNTDEV_STABLE_GUID* msg = Irp->UserBuffer; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); TRACE("IOCTL_MOUNTDEV_QUERY_STABLE_GUID\n"); if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(MOUNTDEV_STABLE_GUID)) return STATUS_INVALID_PARAMETER; RtlCopyMemory(&msg->StableGuid, &Vcb->superblock.uuid, sizeof(GUID)); Irp->IoStatus.Information = sizeof(MOUNTDEV_STABLE_GUID); return STATUS_SUCCESS; } static NTSTATUS is_writable(device_extension* Vcb) { TRACE("IOCTL_DISK_IS_WRITABLE\n"); return Vcb->readonly ? STATUS_MEDIA_WRITE_PROTECTED : STATUS_SUCCESS; } static NTSTATUS query_filesystems(void* data, ULONG length) { NTSTATUS Status; LIST_ENTRY *le, *le2; btrfs_filesystem* bfs = NULL; ULONG itemsize; ExAcquireResourceSharedLite(&global_loading_lock, true); if (IsListEmpty(&VcbList)) { if (length < sizeof(btrfs_filesystem)) { Status = STATUS_BUFFER_OVERFLOW; goto end; } else { RtlZeroMemory(data, sizeof(btrfs_filesystem)); Status = STATUS_SUCCESS; goto end; } } le = VcbList.Flink; while (le != &VcbList) { device_extension* Vcb = CONTAINING_RECORD(le, device_extension, list_entry); btrfs_filesystem_device* bfd; if (bfs) { bfs->next_entry = itemsize; bfs = (btrfs_filesystem*)((uint8_t*)bfs + itemsize); } else bfs = data; if (length < offsetof(btrfs_filesystem, device)) { Status = STATUS_BUFFER_OVERFLOW; goto end; } itemsize = offsetof(btrfs_filesystem, device); length -= offsetof(btrfs_filesystem, device); bfs->next_entry = 0; RtlCopyMemory(&bfs->uuid, &Vcb->superblock.uuid, sizeof(BTRFS_UUID)); ExAcquireResourceSharedLite(&Vcb->tree_lock, true); bfs->num_devices = (uint32_t)Vcb->superblock.num_devices; bfd = NULL; le2 = Vcb->devices.Flink; while (le2 != &Vcb->devices) { device* dev = CONTAINING_RECORD(le2, device, list_entry); MOUNTDEV_NAME mdn; if (bfd) bfd = (btrfs_filesystem_device*)((uint8_t*)bfd + offsetof(btrfs_filesystem_device, name[0]) + bfd->name_length); else bfd = &bfs->device; if (length < offsetof(btrfs_filesystem_device, name[0])) { ExReleaseResourceLite(&Vcb->tree_lock); Status = STATUS_BUFFER_OVERFLOW; goto end; } itemsize += (ULONG)offsetof(btrfs_filesystem_device, name[0]); length -= (ULONG)offsetof(btrfs_filesystem_device, name[0]); RtlCopyMemory(&bfd->uuid, &dev->devitem.device_uuid, sizeof(BTRFS_UUID)); if (dev->devobj) { Status = dev_ioctl(dev->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) { ExReleaseResourceLite(&Vcb->tree_lock); ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status); goto end; } if (mdn.NameLength > length) { ExReleaseResourceLite(&Vcb->tree_lock); Status = STATUS_BUFFER_OVERFLOW; goto end; } 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); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) { ExReleaseResourceLite(&Vcb->tree_lock); ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status); goto end; } itemsize += bfd->name_length; length -= bfd->name_length; } else { bfd->missing = true; bfd->name_length = 0; } le2 = le2->Flink; } ExReleaseResourceLite(&Vcb->tree_lock); le = le->Flink; } Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&global_loading_lock); return Status; } static NTSTATUS probe_volume(void* data, ULONG length, KPROCESSOR_MODE processor_mode) { MOUNTDEV_NAME* mdn = (MOUNTDEV_NAME*)data; UNICODE_STRING path, pnp_name; NTSTATUS Status; PDEVICE_OBJECT DeviceObject; PFILE_OBJECT FileObject; const GUID* guid; if (length < sizeof(MOUNTDEV_NAME)) return STATUS_INVALID_PARAMETER; if (length < offsetof(MOUNTDEV_NAME, Name[0]) + mdn->NameLength) return STATUS_INVALID_PARAMETER; TRACE("%.*S\n", (int)(mdn->NameLength / sizeof(WCHAR)), mdn->Name); if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; path.Buffer = mdn->Name; path.Length = path.MaximumLength = mdn->NameLength; Status = IoGetDeviceObjectPointer(&path, FILE_READ_ATTRIBUTES, &FileObject, &DeviceObject); if (!NT_SUCCESS(Status)) { ERR("IoGetDeviceObjectPointer returned %08lx\n", Status); return Status; } Status = get_device_pnp_name(DeviceObject, &pnp_name, &guid); if (!NT_SUCCESS(Status)) { ERR("get_device_pnp_name returned %08lx\n", Status); ObDereferenceObject(FileObject); return Status; } if (RtlCompareMemory(guid, &GUID_DEVINTERFACE_DISK, sizeof(GUID)) == sizeof(GUID)) { Status = dev_ioctl(DeviceObject, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, true, NULL); if (!NT_SUCCESS(Status)) WARN("IOCTL_DISK_UPDATE_PROPERTIES returned %08lx\n", Status); } ObDereferenceObject(FileObject); volume_removal(&pnp_name); if (RtlCompareMemory(guid, &GUID_DEVINTERFACE_DISK, sizeof(GUID)) == sizeof(GUID)) disk_arrival(&pnp_name); else volume_arrival(&pnp_name, false); return STATUS_SUCCESS; } static NTSTATUS ioctl_unload(PIRP Irp) { if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_LOAD_DRIVER_PRIVILEGE), Irp->RequestorMode)) { ERR("insufficient privileges\n"); return STATUS_PRIVILEGE_NOT_HELD; } do_shutdown(Irp); return STATUS_SUCCESS; } static NTSTATUS control_ioctl(PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); NTSTATUS Status; switch (IrpSp->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_BTRFS_QUERY_FILESYSTEMS: Status = query_filesystems(map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength); break; case IOCTL_BTRFS_PROBE_VOLUME: Status = probe_volume(Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->RequestorMode); break; case IOCTL_BTRFS_UNLOAD: Status = ioctl_unload(Irp); break; default: TRACE("unhandled ioctl %lx\n", IrpSp->Parameters.DeviceIoControl.IoControlCode); Status = STATUS_NOT_IMPLEMENTED; break; } return Status; } _Dispatch_type_(IRP_MJ_DEVICE_CONTROL) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_device_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS Status; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); device_extension* Vcb = DeviceObject->DeviceExtension; bool top_level; FsRtlEnterFileSystem(); top_level = is_top_level(Irp); Irp->IoStatus.Information = 0; if (Vcb) { if (Vcb->type == VCB_TYPE_CONTROL) { Status = control_ioctl(Irp); goto end; } else if (Vcb->type == VCB_TYPE_VOLUME) { Status = vol_device_control(DeviceObject, Irp); goto end; } else if (Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } } else { Status = STATUS_INVALID_PARAMETER; goto end; } switch (IrpSp->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_MOUNTDEV_QUERY_STABLE_GUID: Status = mountdev_query_stable_guid(Vcb, Irp); goto end; case IOCTL_DISK_IS_WRITABLE: Status = is_writable(Vcb); goto end; default: TRACE("unhandled control code %lx\n", IrpSp->Parameters.DeviceIoControl.IoControlCode); break; } IoSkipCurrentIrpStackLocation(Irp); Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp); goto end2; end: Irp->IoStatus.Status = Status; if (Status != STATUS_PENDING) IoCompleteRequest(Irp, IO_NO_INCREMENT); end2: TRACE("returning %08lx\n", Status); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } ================================================ FILE: src/dirctrl.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include "crc32c.h" // not currently in mingw #ifndef _MSC_VER #define FileIdExtdDirectoryInformation (enum _FILE_INFORMATION_CLASS)60 #define FileIdExtdBothDirectoryInformation (enum _FILE_INFORMATION_CLASS)63 typedef struct _FILE_ID_EXTD_DIR_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; ULONG EaSize; ULONG ReparsePointTag; FILE_ID_128 FileId; WCHAR FileName[1]; } FILE_ID_EXTD_DIR_INFORMATION, *PFILE_ID_EXTD_DIR_INFORMATION; typedef struct _FILE_ID_EXTD_BOTH_DIR_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; ULONG EaSize; ULONG ReparsePointTag; FILE_ID_128 FileId; CCHAR ShortNameLength; WCHAR ShortName[12]; WCHAR FileName[1]; } FILE_ID_EXTD_BOTH_DIR_INFORMATION, *PFILE_ID_EXTD_BOTH_DIR_INFORMATION; #endif enum DirEntryType { DirEntryType_File, DirEntryType_Self, DirEntryType_Parent }; typedef struct { KEY key; UNICODE_STRING name; uint8_t type; enum DirEntryType dir_entry_type; dir_child* dc; } dir_entry; ULONG get_reparse_tag_fcb(fcb* fcb) { ULONG tag; if (fcb->type == BTRFS_TYPE_SYMLINK) return IO_REPARSE_TAG_SYMLINK; else if (fcb->type == BTRFS_TYPE_DIRECTORY) { if (!fcb->reparse_xattr.Buffer || fcb->reparse_xattr.Length < sizeof(ULONG)) return 0; RtlCopyMemory(&tag, fcb->reparse_xattr.Buffer, sizeof(ULONG)); } else { NTSTATUS Status; ULONG br; Status = read_file(fcb, (uint8_t*)&tag, 0, sizeof(ULONG), &br, NULL); if (!NT_SUCCESS(Status)) { ERR("read_file returned %08lx\n", Status); return 0; } } return tag; } ULONG get_reparse_tag(device_extension* Vcb, root* subvol, uint64_t inode, uint8_t type, ULONG atts, bool lxss, PIRP Irp) { fcb* fcb; ULONG tag = 0; NTSTATUS Status; if (type == BTRFS_TYPE_SYMLINK) return IO_REPARSE_TAG_SYMLINK; else if (lxss) { if (type == BTRFS_TYPE_SOCKET) return IO_REPARSE_TAG_AF_UNIX; else if (type == BTRFS_TYPE_FIFO) return IO_REPARSE_TAG_LX_FIFO; else if (type == BTRFS_TYPE_CHARDEV) return IO_REPARSE_TAG_LX_CHR; else if (type == BTRFS_TYPE_BLOCKDEV) return IO_REPARSE_TAG_LX_BLK; } if (type != BTRFS_TYPE_FILE && type != BTRFS_TYPE_DIRECTORY) return 0; if (!(atts & FILE_ATTRIBUTE_REPARSE_POINT)) return 0; Status = open_fcb(Vcb, subvol, inode, type, NULL, false, NULL, &fcb, PagedPool, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fcb returned %08lx\n", Status); return 0; } ExAcquireResourceSharedLite(fcb->Header.Resource, true); tag = get_reparse_tag_fcb(fcb); ExReleaseResourceLite(fcb->Header.Resource); free_fcb(fcb); return tag; } static ULONG get_ea_len(device_extension* Vcb, root* subvol, uint64_t inode, PIRP Irp) { uint8_t* eadata; uint16_t len; if (get_xattr(Vcb, subvol, inode, EA_EA, EA_EA_HASH, &eadata, &len, Irp)) { ULONG offset; NTSTATUS Status; Status = IoCheckEaBufferValidity((FILE_FULL_EA_INFORMATION*)eadata, len, &offset); if (!NT_SUCCESS(Status)) { WARN("IoCheckEaBufferValidity returned %08lx (error at offset %lu)\n", Status, offset); ExFreePool(eadata); return 0; } else { FILE_FULL_EA_INFORMATION* eainfo; ULONG ealen; ealen = 4; eainfo = (FILE_FULL_EA_INFORMATION*)eadata; do { ealen += 5 + eainfo->EaNameLength + eainfo->EaValueLength; if (eainfo->NextEntryOffset == 0) break; eainfo = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)eainfo) + eainfo->NextEntryOffset); } while (true); ExFreePool(eadata); return ealen; } } else return 0; } static NTSTATUS query_dir_item(fcb* fcb, ccb* ccb, void* buf, LONG* len, PIRP Irp, dir_entry* de, root* r) { PIO_STACK_LOCATION IrpSp; LONG needed; uint64_t inode; INODE_ITEM ii; NTSTATUS Status; ULONG atts = 0, ealen = 0; file_ref* fileref = ccb->fileref; IrpSp = IoGetCurrentIrpStackLocation(Irp); if (de->key.obj_type == TYPE_ROOT_ITEM) { // subvol LIST_ENTRY* le; r = NULL; le = fcb->Vcb->roots.Flink; while (le != &fcb->Vcb->roots) { root* subvol = CONTAINING_RECORD(le, root, list_entry); if (subvol->id == de->key.obj_id) { r = subvol; break; } le = le->Flink; } if (r && r->parent != fcb->subvol->id && (!de->dc || !de->dc->root_dir)) r = NULL; inode = SUBVOL_ROOT_INODE; } else { inode = de->key.obj_id; } if (IrpSp->Parameters.QueryDirectory.FileInformationClass != FileNamesInformation) { // FIXME - object ID and reparse point classes too? switch (de->dir_entry_type) { case DirEntryType_File: { if (!r) { LARGE_INTEGER time; ii = fcb->Vcb->dummy_fcb->inode_item; atts = fcb->Vcb->dummy_fcb->atts; ealen = fcb->Vcb->dummy_fcb->ealen; KeQuerySystemTime(&time); win_time_to_unix(time, &ii.otime); ii.st_atime = ii.st_mtime = ii.st_ctime = ii.otime; } else { bool found = false; if (de->dc && de->dc->fileref && de->dc->fileref->fcb) { ii = de->dc->fileref->fcb->inode_item; atts = de->dc->fileref->fcb->atts; ealen = de->dc->fileref->fcb->ealen; found = true; } if (!found) { KEY searchkey; traverse_ptr tp; searchkey.obj_id = inode; searchkey.obj_type = TYPE_INODE_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(fcb->Vcb, r, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { ERR("could not find inode item for inode %I64x in root %I64x\n", inode, r->id); return STATUS_INTERNAL_ERROR; } RtlZeroMemory(&ii, sizeof(INODE_ITEM)); if (tp.item->size > 0) RtlCopyMemory(&ii, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size)); if (IrpSp->Parameters.QueryDirectory.FileInformationClass == FileBothDirectoryInformation || IrpSp->Parameters.QueryDirectory.FileInformationClass == FileDirectoryInformation || IrpSp->Parameters.QueryDirectory.FileInformationClass == FileFullDirectoryInformation || IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdBothDirectoryInformation || IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdFullDirectoryInformation || IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdExtdDirectoryInformation || IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdExtdBothDirectoryInformation) { bool dotfile = de->name.Length > sizeof(WCHAR) && de->name.Buffer[0] == '.'; atts = get_file_attributes(fcb->Vcb, r, inode, de->type, dotfile, false, Irp); } if (IrpSp->Parameters.QueryDirectory.FileInformationClass == FileBothDirectoryInformation || IrpSp->Parameters.QueryDirectory.FileInformationClass == FileFullDirectoryInformation || IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdBothDirectoryInformation || IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdFullDirectoryInformation || IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdExtdDirectoryInformation || IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdExtdBothDirectoryInformation) { ealen = get_ea_len(fcb->Vcb, r, inode, Irp); } } } break; } case DirEntryType_Self: ii = fcb->inode_item; r = fcb->subvol; inode = fcb->inode; atts = fcb->atts; ealen = fcb->ealen; break; case DirEntryType_Parent: if (fileref && fileref->parent) { ii = fileref->parent->fcb->inode_item; r = fileref->parent->fcb->subvol; inode = fileref->parent->fcb->inode; atts = fileref->parent->fcb->atts; ealen = fileref->parent->fcb->ealen; } else { ERR("no fileref\n"); return STATUS_INTERNAL_ERROR; } break; } if (atts == 0) atts = FILE_ATTRIBUTE_NORMAL; } switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) { case FileBothDirectoryInformation: { FILE_BOTH_DIR_INFORMATION* fbdi = buf; TRACE("FileBothDirectoryInformation\n"); needed = offsetof(FILE_BOTH_DIR_INFORMATION, FileName) + de->name.Length; if (needed > *len) { TRACE("buffer overflow - %li > %lu\n", needed, *len); return STATUS_BUFFER_OVERFLOW; } fbdi->NextEntryOffset = 0; fbdi->FileIndex = 0; fbdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime); fbdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime); fbdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime); fbdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime); fbdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size; if (de->type == BTRFS_TYPE_SYMLINK) fbdi->AllocationSize.QuadPart = 0; else if (atts & FILE_ATTRIBUTE_SPARSE_FILE) fbdi->AllocationSize.QuadPart = ii.st_blocks; else fbdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size); fbdi->FileAttributes = atts; fbdi->FileNameLength = de->name.Length; fbdi->EaSize = (r && atts & FILE_ATTRIBUTE_REPARSE_POINT) ? get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp) : ealen; fbdi->ShortNameLength = 0; RtlCopyMemory(fbdi->FileName, de->name.Buffer, de->name.Length); *len -= needed; return STATUS_SUCCESS; } case FileDirectoryInformation: { FILE_DIRECTORY_INFORMATION* fdi = buf; TRACE("FileDirectoryInformation\n"); needed = offsetof(FILE_DIRECTORY_INFORMATION, FileName) + de->name.Length; if (needed > *len) { TRACE("buffer overflow - %li > %lu\n", needed, *len); return STATUS_BUFFER_OVERFLOW; } fdi->NextEntryOffset = 0; fdi->FileIndex = 0; fdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime); fdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime); fdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime); fdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime); fdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size; if (de->type == BTRFS_TYPE_SYMLINK) fdi->AllocationSize.QuadPart = 0; else if (atts & FILE_ATTRIBUTE_SPARSE_FILE) fdi->AllocationSize.QuadPart = ii.st_blocks; else fdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size); fdi->FileAttributes = atts; fdi->FileNameLength = de->name.Length; RtlCopyMemory(fdi->FileName, de->name.Buffer, de->name.Length); *len -= needed; return STATUS_SUCCESS; } case FileFullDirectoryInformation: { FILE_FULL_DIR_INFORMATION* ffdi = buf; TRACE("FileFullDirectoryInformation\n"); needed = offsetof(FILE_FULL_DIR_INFORMATION, FileName) + de->name.Length; if (needed > *len) { TRACE("buffer overflow - %li > %lu\n", needed, *len); return STATUS_BUFFER_OVERFLOW; } ffdi->NextEntryOffset = 0; ffdi->FileIndex = 0; ffdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime); ffdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime); ffdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime); ffdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime); ffdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size; if (de->type == BTRFS_TYPE_SYMLINK) ffdi->AllocationSize.QuadPart = 0; else if (atts & FILE_ATTRIBUTE_SPARSE_FILE) ffdi->AllocationSize.QuadPart = ii.st_blocks; else ffdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size); ffdi->FileAttributes = atts; ffdi->FileNameLength = de->name.Length; ffdi->EaSize = (r && atts & FILE_ATTRIBUTE_REPARSE_POINT) ? get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp) : ealen; RtlCopyMemory(ffdi->FileName, de->name.Buffer, de->name.Length); *len -= needed; return STATUS_SUCCESS; } case FileIdBothDirectoryInformation: { FILE_ID_BOTH_DIR_INFORMATION* fibdi = buf; TRACE("FileIdBothDirectoryInformation\n"); needed = offsetof(FILE_ID_BOTH_DIR_INFORMATION, FileName) + de->name.Length; if (needed > *len) { TRACE("buffer overflow - %li > %lu\n", needed, *len); return STATUS_BUFFER_OVERFLOW; } fibdi->NextEntryOffset = 0; fibdi->FileIndex = 0; fibdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime); fibdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime); fibdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime); fibdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime); fibdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size; if (de->type == BTRFS_TYPE_SYMLINK) fibdi->AllocationSize.QuadPart = 0; else if (atts & FILE_ATTRIBUTE_SPARSE_FILE) fibdi->AllocationSize.QuadPart = ii.st_blocks; else fibdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size); fibdi->FileAttributes = atts; fibdi->FileNameLength = de->name.Length; fibdi->EaSize = (r && atts & FILE_ATTRIBUTE_REPARSE_POINT) ? get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp) : ealen; fibdi->ShortNameLength = 0; fibdi->FileId.QuadPart = r ? make_file_id(r, inode) : make_file_id(fcb->Vcb->dummy_fcb->subvol, fcb->Vcb->dummy_fcb->inode); RtlCopyMemory(fibdi->FileName, de->name.Buffer, de->name.Length); *len -= needed; return STATUS_SUCCESS; } case FileIdFullDirectoryInformation: { FILE_ID_FULL_DIR_INFORMATION* fifdi = buf; TRACE("FileIdFullDirectoryInformation\n"); needed = offsetof(FILE_ID_FULL_DIR_INFORMATION, FileName) + de->name.Length; if (needed > *len) { TRACE("buffer overflow - %li > %lu\n", needed, *len); return STATUS_BUFFER_OVERFLOW; } fifdi->NextEntryOffset = 0; fifdi->FileIndex = 0; fifdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime); fifdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime); fifdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime); fifdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime); fifdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size; if (de->type == BTRFS_TYPE_SYMLINK) fifdi->AllocationSize.QuadPart = 0; else if (atts & FILE_ATTRIBUTE_SPARSE_FILE) fifdi->AllocationSize.QuadPart = ii.st_blocks; else fifdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size); fifdi->FileAttributes = atts; fifdi->FileNameLength = de->name.Length; fifdi->EaSize = (r && atts & FILE_ATTRIBUTE_REPARSE_POINT) ? get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp) : ealen; fifdi->FileId.QuadPart = r ? make_file_id(r, inode) : make_file_id(fcb->Vcb->dummy_fcb->subvol, fcb->Vcb->dummy_fcb->inode); RtlCopyMemory(fifdi->FileName, de->name.Buffer, de->name.Length); *len -= needed; return STATUS_SUCCESS; } #ifndef _MSC_VER #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch" #endif case FileIdExtdDirectoryInformation: { FILE_ID_EXTD_DIR_INFORMATION* fiedi = buf; TRACE("FileIdExtdDirectoryInformation\n"); needed = offsetof(FILE_ID_EXTD_DIR_INFORMATION, FileName) + de->name.Length; if (needed > *len) { TRACE("buffer overflow - %li > %lu\n", needed, *len); return STATUS_BUFFER_OVERFLOW; } fiedi->NextEntryOffset = 0; fiedi->FileIndex = 0; fiedi->CreationTime.QuadPart = unix_time_to_win(&ii.otime); fiedi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime); fiedi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime); fiedi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime); fiedi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size; if (de->type == BTRFS_TYPE_SYMLINK) fiedi->AllocationSize.QuadPart = 0; else if (atts & FILE_ATTRIBUTE_SPARSE_FILE) fiedi->AllocationSize.QuadPart = ii.st_blocks; else fiedi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size); fiedi->FileAttributes = atts; fiedi->FileNameLength = de->name.Length; fiedi->EaSize = ealen; fiedi->ReparsePointTag = get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp); RtlCopyMemory(&fiedi->FileId.Identifier[0], &fcb->inode, sizeof(uint64_t)); RtlCopyMemory(&fiedi->FileId.Identifier[sizeof(uint64_t)], &fcb->subvol->id, sizeof(uint64_t)); RtlCopyMemory(fiedi->FileName, de->name.Buffer, de->name.Length); *len -= needed; return STATUS_SUCCESS; } case FileIdExtdBothDirectoryInformation: { FILE_ID_EXTD_BOTH_DIR_INFORMATION* fiebdi = buf; TRACE("FileIdExtdBothDirectoryInformation\n"); needed = offsetof(FILE_ID_EXTD_BOTH_DIR_INFORMATION, FileName) + de->name.Length; if (needed > *len) { TRACE("buffer overflow - %li > %lu\n", needed, *len); return STATUS_BUFFER_OVERFLOW; } fiebdi->NextEntryOffset = 0; fiebdi->FileIndex = 0; fiebdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime); fiebdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime); fiebdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime); fiebdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime); fiebdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size; if (de->type == BTRFS_TYPE_SYMLINK) fiebdi->AllocationSize.QuadPart = 0; else if (atts & FILE_ATTRIBUTE_SPARSE_FILE) fiebdi->AllocationSize.QuadPart = ii.st_blocks; else fiebdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size); fiebdi->FileAttributes = atts; fiebdi->FileNameLength = de->name.Length; fiebdi->EaSize = ealen; fiebdi->ReparsePointTag = get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp); RtlCopyMemory(&fiebdi->FileId.Identifier[0], &fcb->inode, sizeof(uint64_t)); RtlCopyMemory(&fiebdi->FileId.Identifier[sizeof(uint64_t)], &fcb->subvol->id, sizeof(uint64_t)); fiebdi->ShortNameLength = 0; RtlCopyMemory(fiebdi->FileName, de->name.Buffer, de->name.Length); *len -= needed; return STATUS_SUCCESS; } #ifndef _MSC_VER #pragma GCC diagnostic pop #endif case FileNamesInformation: { FILE_NAMES_INFORMATION* fni = buf; TRACE("FileNamesInformation\n"); needed = offsetof(FILE_NAMES_INFORMATION, FileName) + de->name.Length; if (needed > *len) { TRACE("buffer overflow - %li > %lu\n", needed, *len); return STATUS_BUFFER_OVERFLOW; } fni->NextEntryOffset = 0; fni->FileIndex = 0; fni->FileNameLength = de->name.Length; RtlCopyMemory(fni->FileName, de->name.Buffer, de->name.Length); *len -= needed; return STATUS_SUCCESS; } default: WARN("Unknown FileInformationClass %u\n", IrpSp->Parameters.QueryDirectory.FileInformationClass); return STATUS_NOT_IMPLEMENTED; } return STATUS_NO_MORE_FILES; } static NTSTATUS next_dir_entry(file_ref* fileref, uint64_t* offset, dir_entry* de, dir_child** pdc) { LIST_ENTRY* le; dir_child* dc; if (*pdc) { dir_child* dc2 = *pdc; if (dc2->list_entry_index.Flink != &fileref->fcb->dir_children_index) dc = CONTAINING_RECORD(dc2->list_entry_index.Flink, dir_child, list_entry_index); else dc = NULL; goto next; } if (fileref->parent) { // don't return . and .. if root directory if (*offset == 0) { de->key.obj_id = fileref->fcb->inode; de->key.obj_type = TYPE_INODE_ITEM; de->key.offset = 0; de->dir_entry_type = DirEntryType_Self; de->name.Buffer = L"."; de->name.Length = de->name.MaximumLength = sizeof(WCHAR); de->type = BTRFS_TYPE_DIRECTORY; *offset = 1; *pdc = NULL; return STATUS_SUCCESS; } else if (*offset == 1) { de->key.obj_id = fileref->parent->fcb->inode; de->key.obj_type = TYPE_INODE_ITEM; de->key.offset = 0; de->dir_entry_type = DirEntryType_Parent; de->name.Buffer = L".."; de->name.Length = de->name.MaximumLength = sizeof(WCHAR) * 2; de->type = BTRFS_TYPE_DIRECTORY; *offset = 2; *pdc = NULL; return STATUS_SUCCESS; } } if (*offset < 2) *offset = 2; dc = NULL; le = fileref->fcb->dir_children_index.Flink; // skip entries before offset while (le != &fileref->fcb->dir_children_index) { dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_index); if (dc2->index >= *offset) { dc = dc2; break; } le = le->Flink; } next: if (!dc) return STATUS_NO_MORE_FILES; if (dc->root_dir && fileref->parent) { // hide $Root dir unless in apparent root, to avoid recursion if (dc->list_entry_index.Flink == &fileref->fcb->dir_children_index) return STATUS_NO_MORE_FILES; dc = CONTAINING_RECORD(dc->list_entry_index.Flink, dir_child, list_entry_index); } de->key = dc->key; de->name = dc->name; de->type = dc->type; de->dir_entry_type = DirEntryType_File; de->dc = dc; *offset = dc->index + 1; *pdc = dc; return STATUS_SUCCESS; } static NTSTATUS query_directory(PIRP Irp) { PIO_STACK_LOCATION IrpSp; NTSTATUS Status, status2; fcb* fcb; ccb* ccb; file_ref* fileref; device_extension* Vcb; void* buf; uint8_t *curitem, *lastitem; LONG length; ULONG count; bool has_wildcard = false, specific_file = false, initial; dir_entry de; uint64_t newoffset; dir_child* dc = NULL; TRACE("query directory\n"); IrpSp = IoGetCurrentIrpStackLocation(Irp); fcb = IrpSp->FileObject->FsContext; ccb = IrpSp->FileObject->FsContext2; fileref = ccb ? ccb->fileref : NULL; if (!fileref) return STATUS_INVALID_PARAMETER; if (!ccb) { ERR("ccb was NULL\n"); return STATUS_INVALID_PARAMETER; } if (!fcb) { ERR("fcb was NULL\n"); return STATUS_INVALID_PARAMETER; } if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_LIST_DIRECTORY)) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } Vcb = fcb->Vcb; if (!Vcb) { ERR("Vcb was NULL\n"); return STATUS_INVALID_PARAMETER; } if (fileref->fcb == Vcb->dummy_fcb) return STATUS_NO_MORE_FILES; if (IrpSp->Flags == 0) { TRACE("QD flags: (none)\n"); } else { ULONG flags = IrpSp->Flags; TRACE("QD flags:\n"); if (flags & SL_INDEX_SPECIFIED) { TRACE(" SL_INDEX_SPECIFIED\n"); flags &= ~SL_INDEX_SPECIFIED; } if (flags & SL_RESTART_SCAN) { TRACE(" SL_RESTART_SCAN\n"); flags &= ~SL_RESTART_SCAN; } if (flags & SL_RETURN_SINGLE_ENTRY) { TRACE(" SL_RETURN_SINGLE_ENTRY\n"); flags &= ~SL_RETURN_SINGLE_ENTRY; } if (flags != 0) TRACE(" unknown flags: %lu\n", flags); } if (IrpSp->Flags & SL_RESTART_SCAN) { ccb->query_dir_offset = 0; if (ccb->query_string.Buffer) { RtlFreeUnicodeString(&ccb->query_string); ccb->query_string.Buffer = NULL; } ccb->has_wildcard = false; ccb->specific_file = false; } initial = !ccb->query_string.Buffer; if (IrpSp->Parameters.QueryDirectory.FileName && IrpSp->Parameters.QueryDirectory.FileName->Length > 1) { TRACE("QD filename: %.*S\n", (int)(IrpSp->Parameters.QueryDirectory.FileName->Length / sizeof(WCHAR)), IrpSp->Parameters.QueryDirectory.FileName->Buffer); if (IrpSp->Parameters.QueryDirectory.FileName->Length > sizeof(WCHAR) || IrpSp->Parameters.QueryDirectory.FileName->Buffer[0] != L'*') { specific_file = true; if (FsRtlDoesNameContainWildCards(IrpSp->Parameters.QueryDirectory.FileName)) { has_wildcard = true; specific_file = false; } else if (!initial) return STATUS_NO_MORE_FILES; } if (ccb->query_string.Buffer) RtlFreeUnicodeString(&ccb->query_string); if (has_wildcard) RtlUpcaseUnicodeString(&ccb->query_string, IrpSp->Parameters.QueryDirectory.FileName, true); else { ccb->query_string.Buffer = ExAllocatePoolWithTag(PagedPool, IrpSp->Parameters.QueryDirectory.FileName->Length, ALLOC_TAG); if (!ccb->query_string.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ccb->query_string.Length = ccb->query_string.MaximumLength = IrpSp->Parameters.QueryDirectory.FileName->Length; RtlCopyMemory(ccb->query_string.Buffer, IrpSp->Parameters.QueryDirectory.FileName->Buffer, IrpSp->Parameters.QueryDirectory.FileName->Length); } ccb->has_wildcard = has_wildcard; ccb->specific_file = specific_file; } else { has_wildcard = ccb->has_wildcard; specific_file = ccb->specific_file; if (!(IrpSp->Flags & SL_RESTART_SCAN)) { initial = false; if (specific_file) return STATUS_NO_MORE_FILES; } } if (ccb->query_string.Buffer) { TRACE("query string = %.*S\n", (int)(ccb->query_string.Length / sizeof(WCHAR)), ccb->query_string.Buffer); } newoffset = ccb->query_dir_offset; ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceSharedLite(&fileref->fcb->nonpaged->dir_children_lock, true); Status = next_dir_entry(fileref, &newoffset, &de, &dc); if (!NT_SUCCESS(Status)) { if (Status == STATUS_NO_MORE_FILES && initial) Status = STATUS_NO_SUCH_FILE; goto end; } ccb->query_dir_offset = newoffset; buf = map_user_buffer(Irp, NormalPagePriority); if (Irp->MdlAddress && !buf) { ERR("MmGetSystemAddressForMdlSafe returned NULL\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } length = IrpSp->Parameters.QueryDirectory.Length; if (specific_file) { bool found = false; UNICODE_STRING us; LIST_ENTRY* le; uint32_t hash; uint8_t c; us.Buffer = NULL; if (!ccb->case_sensitive) { Status = RtlUpcaseUnicodeString(&us, &ccb->query_string, true); if (!NT_SUCCESS(Status)) { ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); goto end; } hash = calc_crc32c(0xffffffff, (uint8_t*)us.Buffer, us.Length); } else hash = calc_crc32c(0xffffffff, (uint8_t*)ccb->query_string.Buffer, ccb->query_string.Length); c = hash >> 24; if (ccb->case_sensitive) { if (fileref->fcb->hash_ptrs[c]) { le = fileref->fcb->hash_ptrs[c]; while (le != &fileref->fcb->dir_children_hash) { dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_hash); if (dc2->hash == hash) { if (dc2->name.Length == ccb->query_string.Length && RtlCompareMemory(dc2->name.Buffer, ccb->query_string.Buffer, ccb->query_string.Length) == ccb->query_string.Length) { found = true; de.key = dc2->key; de.name = dc2->name; de.type = dc2->type; de.dir_entry_type = DirEntryType_File; de.dc = dc2; break; } } else if (dc2->hash > hash) break; le = le->Flink; } } } else { if (fileref->fcb->hash_ptrs_uc[c]) { le = fileref->fcb->hash_ptrs_uc[c]; while (le != &fileref->fcb->dir_children_hash_uc) { dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_hash_uc); if (dc2->hash_uc == hash) { if (dc2->name_uc.Length == us.Length && RtlCompareMemory(dc2->name_uc.Buffer, us.Buffer, us.Length) == us.Length) { found = true; de.key = dc2->key; de.name = dc2->name; de.type = dc2->type; de.dir_entry_type = DirEntryType_File; de.dc = dc2; break; } } else if (dc2->hash_uc > hash) break; le = le->Flink; } } } if (us.Buffer) ExFreePool(us.Buffer); if (!found) { Status = STATUS_NO_SUCH_FILE; goto end; } } else if (has_wildcard) { while (!FsRtlIsNameInExpression(&ccb->query_string, &de.name, !ccb->case_sensitive, NULL)) { newoffset = ccb->query_dir_offset; Status = next_dir_entry(fileref, &newoffset, &de, &dc); if (NT_SUCCESS(Status)) ccb->query_dir_offset = newoffset; else { if (Status == STATUS_NO_MORE_FILES && initial) Status = STATUS_NO_SUCH_FILE; goto end; } } } TRACE("file(0) = %.*S\n", (int)(de.name.Length / sizeof(WCHAR)), de.name.Buffer); TRACE("offset = %I64u\n", ccb->query_dir_offset - 1); Status = query_dir_item(fcb, ccb, buf, &length, Irp, &de, fcb->subvol); count = 0; if (NT_SUCCESS(Status) && !(IrpSp->Flags & SL_RETURN_SINGLE_ENTRY) && !specific_file) { lastitem = (uint8_t*)buf; while (length > 0) { switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) { #ifndef _MSC_VER #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch" #endif case FileBothDirectoryInformation: case FileDirectoryInformation: case FileIdBothDirectoryInformation: case FileFullDirectoryInformation: case FileIdFullDirectoryInformation: case FileIdExtdDirectoryInformation: case FileIdExtdBothDirectoryInformation: length -= length % 8; break; #ifndef _MSC_VER #pragma GCC diagnostic pop #endif case FileNamesInformation: length -= length % 4; break; default: WARN("unhandled file information class %u\n", IrpSp->Parameters.QueryDirectory.FileInformationClass); break; } if (length > 0) { newoffset = ccb->query_dir_offset; Status = next_dir_entry(fileref, &newoffset, &de, &dc); if (NT_SUCCESS(Status)) { if (!has_wildcard || FsRtlIsNameInExpression(&ccb->query_string, &de.name, !ccb->case_sensitive, NULL)) { curitem = (uint8_t*)buf + IrpSp->Parameters.QueryDirectory.Length - length; count++; TRACE("file(%lu) %Iu = %.*S\n", count, curitem - (uint8_t*)buf, (int)(de.name.Length / sizeof(WCHAR)), de.name.Buffer); TRACE("offset = %I64u\n", ccb->query_dir_offset - 1); status2 = query_dir_item(fcb, ccb, curitem, &length, Irp, &de, fcb->subvol); if (NT_SUCCESS(status2)) { ULONG* lastoffset = (ULONG*)lastitem; *lastoffset = (ULONG)(curitem - lastitem); ccb->query_dir_offset = newoffset; lastitem = curitem; } else break; } else ccb->query_dir_offset = newoffset; } else { if (Status == STATUS_NO_MORE_FILES) Status = STATUS_SUCCESS; break; } } else break; } } Irp->IoStatus.Information = IrpSp->Parameters.QueryDirectory.Length - length; end: ExReleaseResourceLite(&fileref->fcb->nonpaged->dir_children_lock); ExReleaseResourceLite(&Vcb->tree_lock); TRACE("returning %08lx\n", Status); return Status; } static NTSTATUS notify_change_directory(device_extension* Vcb, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; fcb* fcb = FileObject->FsContext; ccb* ccb = FileObject->FsContext2; file_ref* fileref = ccb ? ccb->fileref : NULL; NTSTATUS Status; TRACE("IRP_MN_NOTIFY_CHANGE_DIRECTORY\n"); if (!ccb) { ERR("ccb was NULL\n"); return STATUS_INVALID_PARAMETER; } if (!fileref) { ERR("no fileref\n"); return STATUS_INVALID_PARAMETER; } if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_LIST_DIRECTORY)) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (fcb->type != BTRFS_TYPE_DIRECTORY) { Status = STATUS_INVALID_PARAMETER; goto end; } // FIXME - raise exception if FCB marked for deletion? TRACE("FileObject %p\n", FileObject); if (ccb->filename.Length == 0) { ULONG reqlen; ccb->filename.MaximumLength = ccb->filename.Length = 0; Status = fileref_get_filename(fileref, &ccb->filename, NULL, &reqlen); if (Status == STATUS_BUFFER_OVERFLOW) { ccb->filename.Buffer = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG); if (!ccb->filename.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } ccb->filename.MaximumLength = (uint16_t)reqlen; Status = fileref_get_filename(fileref, &ccb->filename, NULL, &reqlen); if (!NT_SUCCESS(Status)) { ERR("fileref_get_filename returned %08lx\n", Status); goto end; } } else { ERR("fileref_get_filename returned %08lx\n", Status); goto end; } } FsRtlNotifyFilterChangeDirectory(Vcb->NotifySync, &Vcb->DirNotifyList, FileObject->FsContext2, (PSTRING)&ccb->filename, IrpSp->Flags & SL_WATCH_TREE, false, IrpSp->Parameters.NotifyDirectory.CompletionFilter, Irp, NULL, NULL, NULL); Status = STATUS_PENDING; end: ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&fcb->Vcb->tree_lock); return Status; } _Dispatch_type_(IRP_MJ_DIRECTORY_CONTROL) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_directory_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { PIO_STACK_LOCATION IrpSp; NTSTATUS Status; ULONG func; bool top_level; device_extension* Vcb = DeviceObject->DeviceExtension; FsRtlEnterFileSystem(); TRACE("directory control\n"); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = STATUS_INVALID_DEVICE_REQUEST; goto end; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } IrpSp = IoGetCurrentIrpStackLocation(Irp); Irp->IoStatus.Information = 0; func = IrpSp->MinorFunction; switch (func) { case IRP_MN_NOTIFY_CHANGE_DIRECTORY: Status = notify_change_directory(Vcb, Irp); break; case IRP_MN_QUERY_DIRECTORY: Status = query_directory(Irp); break; default: WARN("unknown minor %lu\n", func); Status = STATUS_NOT_IMPLEMENTED; Irp->IoStatus.Status = Status; break; } if (Status == STATUS_PENDING) goto exit; end: Irp->IoStatus.Status = Status; IoCompleteRequest(Irp, IO_DISK_INCREMENT); exit: TRACE("returning %08lx\n", Status); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } ================================================ FILE: src/extent-tree.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include "crc32c.h" typedef struct { uint8_t type; union { EXTENT_DATA_REF edr; SHARED_DATA_REF sdr; TREE_BLOCK_REF tbr; SHARED_BLOCK_REF sbr; }; uint64_t hash; LIST_ENTRY list_entry; } extent_ref; uint64_t get_extent_data_ref_hash2(uint64_t root, uint64_t objid, uint64_t offset) { uint32_t high_crc = 0xffffffff, low_crc = 0xffffffff; high_crc = calc_crc32c(high_crc, (uint8_t*)&root, sizeof(uint64_t)); low_crc = calc_crc32c(low_crc, (uint8_t*)&objid, sizeof(uint64_t)); low_crc = calc_crc32c(low_crc, (uint8_t*)&offset, sizeof(uint64_t)); return ((uint64_t)high_crc << 31) ^ (uint64_t)low_crc; } static __inline uint64_t get_extent_data_ref_hash(EXTENT_DATA_REF* edr) { return get_extent_data_ref_hash2(edr->root, edr->objid, edr->offset); } static uint64_t get_extent_hash(uint8_t type, void* data) { if (type == TYPE_EXTENT_DATA_REF) { return get_extent_data_ref_hash((EXTENT_DATA_REF*)data); } else if (type == TYPE_SHARED_BLOCK_REF) { SHARED_BLOCK_REF* sbr = (SHARED_BLOCK_REF*)data; return sbr->offset; } else if (type == TYPE_SHARED_DATA_REF) { SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data; return sdr->offset; } else if (type == TYPE_TREE_BLOCK_REF) { TREE_BLOCK_REF* tbr = (TREE_BLOCK_REF*)data; return tbr->offset; } else { ERR("unhandled extent type %x\n", type); return 0; } } static void free_extent_refs(LIST_ENTRY* extent_refs) { while (!IsListEmpty(extent_refs)) { LIST_ENTRY* le = RemoveHeadList(extent_refs); extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry); ExFreePool(er); } } static NTSTATUS add_shared_data_extent_ref(LIST_ENTRY* extent_refs, uint64_t parent, uint32_t count) { extent_ref* er2; LIST_ENTRY* le; if (!IsListEmpty(extent_refs)) { le = extent_refs->Flink; while (le != extent_refs) { extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry); if (er->type == TYPE_SHARED_DATA_REF && er->sdr.offset == parent) { er->sdr.count += count; return STATUS_SUCCESS; } le = le->Flink; } } er2 = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG); if (!er2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } er2->type = TYPE_SHARED_DATA_REF; er2->sdr.offset = parent; er2->sdr.count = count; InsertTailList(extent_refs, &er2->list_entry); return STATUS_SUCCESS; } static NTSTATUS add_shared_block_extent_ref(LIST_ENTRY* extent_refs, uint64_t parent) { extent_ref* er2; LIST_ENTRY* le; if (!IsListEmpty(extent_refs)) { le = extent_refs->Flink; while (le != extent_refs) { extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry); if (er->type == TYPE_SHARED_BLOCK_REF && er->sbr.offset == parent) return STATUS_SUCCESS; le = le->Flink; } } er2 = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG); if (!er2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } er2->type = TYPE_SHARED_BLOCK_REF; er2->sbr.offset = parent; InsertTailList(extent_refs, &er2->list_entry); return STATUS_SUCCESS; } static NTSTATUS add_tree_block_extent_ref(LIST_ENTRY* extent_refs, uint64_t root) { extent_ref* er2; LIST_ENTRY* le; if (!IsListEmpty(extent_refs)) { le = extent_refs->Flink; while (le != extent_refs) { extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry); if (er->type == TYPE_TREE_BLOCK_REF && er->tbr.offset == root) return STATUS_SUCCESS; le = le->Flink; } } er2 = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG); if (!er2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } er2->type = TYPE_TREE_BLOCK_REF; er2->tbr.offset = root; InsertTailList(extent_refs, &er2->list_entry); return STATUS_SUCCESS; } static void sort_extent_refs(LIST_ENTRY* extent_refs) { LIST_ENTRY newlist; if (IsListEmpty(extent_refs)) return; // insertion sort InitializeListHead(&newlist); while (!IsListEmpty(extent_refs)) { extent_ref* er = CONTAINING_RECORD(RemoveHeadList(extent_refs), extent_ref, list_entry); LIST_ENTRY* le; bool inserted = false; le = newlist.Flink; while (le != &newlist) { extent_ref* er2 = CONTAINING_RECORD(le, extent_ref, list_entry); if (er->type < er2->type || (er->type == er2->type && er->hash > er2->hash)) { InsertHeadList(le->Blink, &er->list_entry); inserted = true; break; } le = le->Flink; } if (!inserted) InsertTailList(&newlist, &er->list_entry); } newlist.Flink->Blink = extent_refs; newlist.Blink->Flink = extent_refs; extent_refs->Flink = newlist.Flink; extent_refs->Blink = newlist.Blink; } static NTSTATUS construct_extent_item(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t flags, LIST_ENTRY* extent_refs, KEY* firstitem, uint8_t level, PIRP Irp) { NTSTATUS Status; LIST_ENTRY *le, *next_le; uint64_t refcount; uint16_t inline_len; bool all_inline = true; extent_ref* first_noninline = NULL; EXTENT_ITEM* ei; uint8_t* siptr; // FIXME - write skinny extents if is tree and incompat flag set if (IsListEmpty(extent_refs)) { WARN("no extent refs found\n"); return STATUS_SUCCESS; } refcount = 0; inline_len = sizeof(EXTENT_ITEM); if (flags & EXTENT_ITEM_TREE_BLOCK) inline_len += sizeof(EXTENT_ITEM2); le = extent_refs->Flink; while (le != extent_refs) { extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry); uint64_t rc; next_le = le->Flink; rc = get_extent_data_refcount(er->type, &er->edr); if (rc == 0) { RemoveEntryList(&er->list_entry); ExFreePool(er); } else { uint16_t extlen = get_extent_data_len(er->type); refcount += rc; er->hash = get_extent_hash(er->type, &er->edr); if (all_inline) { if ((uint16_t)(inline_len + 1 + extlen) > Vcb->superblock.node_size >> 2) { all_inline = false; first_noninline = er; } else inline_len += extlen + 1; } } le = next_le; } ei = ExAllocatePoolWithTag(PagedPool, inline_len, ALLOC_TAG); if (!ei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ei->refcount = refcount; ei->generation = Vcb->superblock.generation; ei->flags = flags; if (flags & EXTENT_ITEM_TREE_BLOCK) { EXTENT_ITEM2* ei2 = (EXTENT_ITEM2*)&ei[1]; if (firstitem) { ei2->firstitem.obj_id = firstitem->obj_id; ei2->firstitem.obj_type = firstitem->obj_type; ei2->firstitem.offset = firstitem->offset; } else { ei2->firstitem.obj_id = 0; ei2->firstitem.obj_type = 0; ei2->firstitem.offset = 0; } ei2->level = level; siptr = (uint8_t*)&ei2[1]; } else siptr = (uint8_t*)&ei[1]; sort_extent_refs(extent_refs); le = extent_refs->Flink; while (le != extent_refs) { extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry); ULONG extlen = get_extent_data_len(er->type); if (!all_inline && er == first_noninline) break; *siptr = er->type; siptr++; if (extlen > 0) { RtlCopyMemory(siptr, &er->edr, extlen); siptr += extlen; } le = le->Flink; } Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, size, ei, inline_len, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(ei); return Status; } if (!all_inline) { le = &first_noninline->list_entry; while (le != extent_refs) { extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry); uint16_t len; uint8_t* data; if (er->type == TYPE_EXTENT_DATA_REF) { len = sizeof(EXTENT_DATA_REF); data = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG); if (!data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(data, &er->edr, len); } else if (er->type == TYPE_SHARED_DATA_REF) { len = sizeof(uint32_t); data = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG); if (!data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } *((uint32_t*)data) = er->sdr.count; } else { len = 0; data = NULL; } Status = insert_tree_item(Vcb, Vcb->extent_root, address, er->type, er->hash, data, len, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); if (data) ExFreePool(data); return Status; } le = le->Flink; } } return STATUS_SUCCESS; } static NTSTATUS convert_old_extent(device_extension* Vcb, uint64_t address, bool tree, KEY* firstitem, uint8_t level, PIRP Irp) { NTSTATUS Status; KEY searchkey; traverse_ptr tp, next_tp; LIST_ENTRY extent_refs; uint64_t size; InitializeListHead(&extent_refs); searchkey.obj_id = address; searchkey.obj_type = TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { ERR("old-style extent %I64x not found\n", address); return STATUS_INTERNAL_ERROR; } size = tp.item->key.offset; Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } while (find_next_item(Vcb, &tp, &next_tp, false, Irp)) { tp = next_tp; if (tp.item->key.obj_id == address && tp.item->key.obj_type == TYPE_EXTENT_REF_V0 && tp.item->size >= sizeof(EXTENT_REF_V0)) { EXTENT_REF_V0* erv0 = (EXTENT_REF_V0*)tp.item->data; if (tree) { if (tp.item->key.offset == tp.item->key.obj_id) { // top of the tree Status = add_tree_block_extent_ref(&extent_refs, erv0->root); if (!NT_SUCCESS(Status)) { ERR("add_tree_block_extent_ref returned %08lx\n", Status); goto end; } } else { Status = add_shared_block_extent_ref(&extent_refs, tp.item->key.offset); if (!NT_SUCCESS(Status)) { ERR("add_shared_block_extent_ref returned %08lx\n", Status); goto end; } } } else { Status = add_shared_data_extent_ref(&extent_refs, tp.item->key.offset, erv0->count); if (!NT_SUCCESS(Status)) { ERR("add_shared_data_extent_ref returned %08lx\n", Status); goto end; } } Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); goto end; } } if (tp.item->key.obj_id > address || tp.item->key.obj_type > TYPE_EXTENT_REF_V0) break; } Status = construct_extent_item(Vcb, address, size, tree ? (EXTENT_ITEM_TREE_BLOCK | EXTENT_ITEM_SHARED_BACKREFS) : EXTENT_ITEM_DATA, &extent_refs, firstitem, level, Irp); if (!NT_SUCCESS(Status)) ERR("construct_extent_item returned %08lx\n", Status); end: free_extent_refs(&extent_refs); return Status; } NTSTATUS increase_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, uint8_t type, void* data, KEY* firstitem, uint8_t level, PIRP Irp) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; ULONG len, max_extent_item_size; uint16_t datalen = get_extent_data_len(type); EXTENT_ITEM* ei; uint8_t* ptr; uint64_t inline_rc, offset; uint8_t* data2; EXTENT_ITEM* newei; bool skinny; bool is_tree = type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF; if (datalen == 0) { ERR("unrecognized extent type %x\n", type); return STATUS_INTERNAL_ERROR; } searchkey.obj_id = address; searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } // If entry doesn't exist yet, create new inline extent item 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)) { uint16_t eisize; eisize = sizeof(EXTENT_ITEM); if (is_tree && !(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) eisize += sizeof(EXTENT_ITEM2); eisize += sizeof(uint8_t); eisize += datalen; ei = ExAllocatePoolWithTag(PagedPool, eisize, ALLOC_TAG); if (!ei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ei->refcount = get_extent_data_refcount(type, data); ei->generation = Vcb->superblock.generation; ei->flags = is_tree ? EXTENT_ITEM_TREE_BLOCK : EXTENT_ITEM_DATA; ptr = (uint8_t*)&ei[1]; if (is_tree && !(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) { EXTENT_ITEM2* ei2 = (EXTENT_ITEM2*)ptr; ei2->firstitem = *firstitem; ei2->level = level; ptr = (uint8_t*)&ei2[1]; } *ptr = type; RtlCopyMemory(ptr + 1, data, datalen); if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA && is_tree) Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_METADATA_ITEM, level, ei, eisize, NULL, Irp); else Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, size, ei, eisize, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } else if (tp.item->key.obj_id == address && tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->key.offset != size) { ERR("extent %I64x exists, but with size %I64x rather than %I64x expected\n", tp.item->key.obj_id, tp.item->key.offset, size); return STATUS_INTERNAL_ERROR; } skinny = tp.item->key.obj_type == TYPE_METADATA_ITEM; if (tp.item->size == sizeof(EXTENT_ITEM_V0) && !skinny) { Status = convert_old_extent(Vcb, address, is_tree, firstitem, level, Irp); if (!NT_SUCCESS(Status)) { ERR("convert_old_extent returned %08lx\n", Status); return Status; } return increase_extent_refcount(Vcb, address, size, type, data, firstitem, level, Irp); } if (tp.item->size < sizeof(EXTENT_ITEM)) { 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)); return STATUS_INTERNAL_ERROR; } ei = (EXTENT_ITEM*)tp.item->data; len = tp.item->size - sizeof(EXTENT_ITEM); ptr = (uint8_t*)&ei[1]; if (ei->flags & EXTENT_ITEM_TREE_BLOCK && !skinny) { if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) { 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)); return STATUS_INTERNAL_ERROR; } len -= sizeof(EXTENT_ITEM2); ptr += sizeof(EXTENT_ITEM2); } inline_rc = 0; // Loop through existing inline extent entries while (len > 0) { uint8_t secttype = *ptr; ULONG sectlen = get_extent_data_len(secttype); uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t)); len--; if (sectlen > len) { 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); return STATUS_INTERNAL_ERROR; } if (sectlen == 0) { ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype); return STATUS_INTERNAL_ERROR; } // If inline extent already present, increase refcount and return if (secttype == type) { if (type == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t)); EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data; if (sectedr->root == edr->root && sectedr->objid == edr->objid && sectedr->offset == edr->offset) { uint32_t rc = get_extent_data_refcount(type, data); EXTENT_DATA_REF* sectedr2; newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG); if (!newei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newei, tp.item->data, tp.item->size); newei->refcount += rc; sectedr2 = (EXTENT_DATA_REF*)((uint8_t*)newei + ((uint8_t*)sectedr - tp.item->data)); sectedr2->count += rc; Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } } else if (type == TYPE_TREE_BLOCK_REF) { TREE_BLOCK_REF* secttbr = (TREE_BLOCK_REF*)(ptr + sizeof(uint8_t)); TREE_BLOCK_REF* tbr = (TREE_BLOCK_REF*)data; if (secttbr->offset == tbr->offset) { TRACE("trying to increase refcount of non-shared tree extent\n"); return STATUS_SUCCESS; } } else if (type == TYPE_SHARED_BLOCK_REF) { SHARED_BLOCK_REF* sectsbr = (SHARED_BLOCK_REF*)(ptr + sizeof(uint8_t)); SHARED_BLOCK_REF* sbr = (SHARED_BLOCK_REF*)data; if (sectsbr->offset == sbr->offset) return STATUS_SUCCESS; } else if (type == TYPE_SHARED_DATA_REF) { SHARED_DATA_REF* sectsdr = (SHARED_DATA_REF*)(ptr + sizeof(uint8_t)); SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data; if (sectsdr->offset == sdr->offset) { uint32_t rc = get_extent_data_refcount(type, data); SHARED_DATA_REF* sectsdr2; newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG); if (!newei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newei, tp.item->data, tp.item->size); newei->refcount += rc; sectsdr2 = (SHARED_DATA_REF*)((uint8_t*)newei + ((uint8_t*)sectsdr - tp.item->data)); sectsdr2->count += rc; Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } } else { ERR("unhandled extent type %x\n", type); return STATUS_INTERNAL_ERROR; } } len -= sectlen; ptr += sizeof(uint8_t) + sectlen; inline_rc += sectcount; } offset = get_extent_hash(type, data); max_extent_item_size = (Vcb->superblock.node_size >> 4) - sizeof(leaf_node); // If we can, add entry as inline extent item if (inline_rc == ei->refcount && tp.item->size + sizeof(uint8_t) + datalen < max_extent_item_size) { len = tp.item->size - sizeof(EXTENT_ITEM); ptr = (uint8_t*)&ei[1]; if (ei->flags & EXTENT_ITEM_TREE_BLOCK && !skinny) { len -= sizeof(EXTENT_ITEM2); ptr += sizeof(EXTENT_ITEM2); } // Confusingly, it appears that references are sorted forward by type (i.e. EXTENT_DATA_REFs before // SHARED_DATA_REFs), but then backwards by hash... while (len > 0) { uint8_t secttype = *ptr; ULONG sectlen = get_extent_data_len(secttype); if (secttype > type) break; if (secttype == type) { uint64_t sectoff = get_extent_hash(secttype, ptr + 1); if (sectoff < offset) break; } len -= sectlen + sizeof(uint8_t); ptr += sizeof(uint8_t) + sectlen; } newei = ExAllocatePoolWithTag(PagedPool, tp.item->size + sizeof(uint8_t) + datalen, ALLOC_TAG); RtlCopyMemory(newei, tp.item->data, ptr - tp.item->data); newei->refcount += get_extent_data_refcount(type, data); if (len > 0) RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data) + sizeof(uint8_t) + datalen, ptr, len); ptr = (ptr - tp.item->data) + (uint8_t*)newei; *ptr = type; RtlCopyMemory(ptr + 1, data, datalen); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } // Look for existing non-inline entry, and increase refcount if found if (inline_rc != ei->refcount) { traverse_ptr tp2; searchkey.obj_id = address; searchkey.obj_type = type; searchkey.offset = offset; Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (!keycmp(tp2.item->key, searchkey)) { if (type == TYPE_SHARED_DATA_REF && tp2.item->size < sizeof(uint32_t)) { 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)); return STATUS_INTERNAL_ERROR; } else if (type != TYPE_SHARED_DATA_REF && tp2.item->size < datalen) { 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); return STATUS_INTERNAL_ERROR; } data2 = ExAllocatePoolWithTag(PagedPool, tp2.item->size, ALLOC_TAG); if (!data2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(data2, tp2.item->data, tp2.item->size); if (type == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data2; edr->count += get_extent_data_refcount(type, data); } else if (type == TYPE_TREE_BLOCK_REF) { TRACE("trying to increase refcount of non-shared tree extent\n"); return STATUS_SUCCESS; } else if (type == TYPE_SHARED_BLOCK_REF) return STATUS_SUCCESS; else if (type == TYPE_SHARED_DATA_REF) { uint32_t* sdr = (uint32_t*)data2; *sdr += get_extent_data_refcount(type, data); } else { ERR("unhandled extent type %x\n", type); return STATUS_INTERNAL_ERROR; } Status = delete_tree_item(Vcb, &tp2); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG); if (!newei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newei, tp.item->data, tp.item->size); newei->refcount += get_extent_data_refcount(type, data); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } } // Otherwise, add new non-inline entry if (type == TYPE_SHARED_DATA_REF) { SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data; data2 = ExAllocatePoolWithTag(PagedPool, sizeof(uint32_t), ALLOC_TAG); if (!data2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } datalen = sizeof(uint32_t); *((uint32_t*)data2) = sdr->count; } else if (type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF) { data2 = NULL; datalen = 0; } else { data2 = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG); if (!data2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(data2, data, datalen); } Status = insert_tree_item(Vcb, Vcb->extent_root, address, type, offset, data2, datalen, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG); if (!newei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newei, tp.item->data, tp.item->size); newei->refcount += get_extent_data_refcount(type, data); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } NTSTATUS 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) { EXTENT_DATA_REF edr; edr.root = root; edr.objid = inode; edr.offset = offset; edr.count = refcount; return increase_extent_refcount(Vcb, address, size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, Irp); } NTSTATUS decrease_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, uint8_t type, void* data, KEY* firstitem, uint8_t level, uint64_t parent, bool superseded, PIRP Irp) { KEY searchkey; NTSTATUS Status; traverse_ptr tp, tp2; EXTENT_ITEM* ei; ULONG len; uint64_t inline_rc; uint8_t* ptr; uint32_t rc = data ? get_extent_data_refcount(type, data) : 1; ULONG datalen = get_extent_data_len(type); bool is_tree = (type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF), skinny = false; if (is_tree && Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA) { searchkey.obj_id = address; searchkey.obj_type = TYPE_METADATA_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) skinny = true; } if (!skinny) { searchkey.obj_id = address; searchkey.obj_type = TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { ERR("could not find EXTENT_ITEM for address %I64x\n", address); return STATUS_INTERNAL_ERROR; } if (tp.item->key.offset != size) { ERR("extent %I64x had length %I64x, not %I64x as expected\n", address, tp.item->key.offset, size); return STATUS_INTERNAL_ERROR; } if (tp.item->size == sizeof(EXTENT_ITEM_V0)) { Status = convert_old_extent(Vcb, address, is_tree, firstitem, level, Irp); if (!NT_SUCCESS(Status)) { ERR("convert_old_extent returned %08lx\n", Status); return Status; } return decrease_extent_refcount(Vcb, address, size, type, data, firstitem, level, parent, superseded, Irp); } } if (tp.item->size < sizeof(EXTENT_ITEM)) { 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)); return STATUS_INTERNAL_ERROR; } ei = (EXTENT_ITEM*)tp.item->data; len = tp.item->size - sizeof(EXTENT_ITEM); ptr = (uint8_t*)&ei[1]; if (ei->flags & EXTENT_ITEM_TREE_BLOCK && !skinny) { if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) { 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)); return STATUS_INTERNAL_ERROR; } len -= sizeof(EXTENT_ITEM2); ptr += sizeof(EXTENT_ITEM2); } if (ei->refcount < rc) { ERR("error - extent has refcount %I64x, trying to reduce by %x\n", ei->refcount, rc); return STATUS_INTERNAL_ERROR; } inline_rc = 0; // Loop through inline extent entries while (len > 0) { uint8_t secttype = *ptr; uint16_t sectlen = get_extent_data_len(secttype); uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t)); len--; if (sectlen > len) { 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); return STATUS_INTERNAL_ERROR; } if (sectlen == 0) { ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype); return STATUS_INTERNAL_ERROR; } if (secttype == type) { if (type == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t)); EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data; if (sectedr->root == edr->root && sectedr->objid == edr->objid && sectedr->offset == edr->offset) { uint16_t neweilen; EXTENT_ITEM* newei; if (ei->refcount == edr->count) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } if (!superseded) add_checksum_entry(Vcb, address, (ULONG)(size >> Vcb->sector_shift), NULL, Irp); return STATUS_SUCCESS; } if (sectedr->count < edr->count) { ERR("error - extent section has refcount %x, trying to reduce by %x\n", sectedr->count, edr->count); return STATUS_INTERNAL_ERROR; } if (sectedr->count > edr->count) // reduce section refcount neweilen = tp.item->size; else // remove section entirely neweilen = tp.item->size - sizeof(uint8_t) - sectlen; newei = ExAllocatePoolWithTag(PagedPool, neweilen, ALLOC_TAG); if (!newei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } if (sectedr->count > edr->count) { EXTENT_DATA_REF* newedr = (EXTENT_DATA_REF*)((uint8_t*)newei + ((uint8_t*)sectedr - tp.item->data)); RtlCopyMemory(newei, ei, neweilen); newedr->count -= rc; } else { RtlCopyMemory(newei, ei, ptr - tp.item->data); if (len > sectlen) RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data), ptr + sectlen + sizeof(uint8_t), len - sectlen); } newei->refcount -= rc; Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } } else if (type == TYPE_SHARED_DATA_REF) { SHARED_DATA_REF* sectsdr = (SHARED_DATA_REF*)(ptr + sizeof(uint8_t)); SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data; if (sectsdr->offset == sdr->offset) { EXTENT_ITEM* newei; uint16_t neweilen; if (ei->refcount == sectsdr->count) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } if (!superseded) add_checksum_entry(Vcb, address, (ULONG)(size >> Vcb->sector_shift), NULL, Irp); return STATUS_SUCCESS; } if (sectsdr->count < sdr->count) { ERR("error - SHARED_DATA_REF has refcount %x, trying to reduce by %x\n", sectsdr->count, sdr->count); return STATUS_INTERNAL_ERROR; } if (sectsdr->count > sdr->count) // reduce section refcount neweilen = tp.item->size; else // remove section entirely neweilen = tp.item->size - sizeof(uint8_t) - sectlen; newei = ExAllocatePoolWithTag(PagedPool, neweilen, ALLOC_TAG); if (!newei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } if (sectsdr->count > sdr->count) { SHARED_DATA_REF* newsdr = (SHARED_DATA_REF*)((uint8_t*)newei + ((uint8_t*)sectsdr - tp.item->data)); RtlCopyMemory(newei, ei, neweilen); newsdr->count -= rc; } else { RtlCopyMemory(newei, ei, ptr - tp.item->data); if (len > sectlen) RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data), ptr + sectlen + sizeof(uint8_t), len - sectlen); } newei->refcount -= rc; Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } } else if (type == TYPE_TREE_BLOCK_REF) { TREE_BLOCK_REF* secttbr = (TREE_BLOCK_REF*)(ptr + sizeof(uint8_t)); TREE_BLOCK_REF* tbr = (TREE_BLOCK_REF*)data; if (secttbr->offset == tbr->offset) { EXTENT_ITEM* newei; uint16_t neweilen; if (ei->refcount == 1) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } neweilen = tp.item->size - sizeof(uint8_t) - sectlen; newei = ExAllocatePoolWithTag(PagedPool, neweilen, ALLOC_TAG); if (!newei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newei, ei, ptr - tp.item->data); if (len > sectlen) RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data), ptr + sectlen + sizeof(uint8_t), len - sectlen); newei->refcount--; Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } } else if (type == TYPE_SHARED_BLOCK_REF) { SHARED_BLOCK_REF* sectsbr = (SHARED_BLOCK_REF*)(ptr + sizeof(uint8_t)); SHARED_BLOCK_REF* sbr = (SHARED_BLOCK_REF*)data; if (sectsbr->offset == sbr->offset) { EXTENT_ITEM* newei; uint16_t neweilen; if (ei->refcount == 1) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } neweilen = tp.item->size - sizeof(uint8_t) - sectlen; newei = ExAllocatePoolWithTag(PagedPool, neweilen, ALLOC_TAG); if (!newei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newei, ei, ptr - tp.item->data); if (len > sectlen) RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data), ptr + sectlen + sizeof(uint8_t), len - sectlen); newei->refcount--; Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } } else { ERR("unhandled extent type %x\n", type); return STATUS_INTERNAL_ERROR; } } len -= sectlen; ptr += sizeof(uint8_t) + sectlen; inline_rc += sectcount; } if (inline_rc == ei->refcount) { ERR("entry not found in inline extent item for address %I64x\n", address); return STATUS_INTERNAL_ERROR; } if (type == TYPE_SHARED_DATA_REF) datalen = sizeof(uint32_t); else if (type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF) datalen = 0; searchkey.obj_id = address; searchkey.obj_type = type; searchkey.offset = (type == TYPE_SHARED_DATA_REF || type == TYPE_EXTENT_REF_V0) ? parent : get_extent_hash(type, data); Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (keycmp(tp2.item->key, searchkey)) { ERR("(%I64x,%x,%I64x) not found\n", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset); return STATUS_INTERNAL_ERROR; } if (tp2.item->size < datalen) { 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); return STATUS_INTERNAL_ERROR; } if (type == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)tp2.item->data; EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data; if (sectedr->root == edr->root && sectedr->objid == edr->objid && sectedr->offset == edr->offset) { EXTENT_ITEM* newei; if (ei->refcount == edr->count) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } Status = delete_tree_item(Vcb, &tp2); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } if (!superseded) add_checksum_entry(Vcb, address, (ULONG)(size >> Vcb->sector_shift), NULL, Irp); return STATUS_SUCCESS; } if (sectedr->count < edr->count) { ERR("error - extent section has refcount %x, trying to reduce by %x\n", sectedr->count, edr->count); return STATUS_INTERNAL_ERROR; } Status = delete_tree_item(Vcb, &tp2); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } if (sectedr->count > edr->count) { EXTENT_DATA_REF* newedr = ExAllocatePoolWithTag(PagedPool, tp2.item->size, ALLOC_TAG); if (!newedr) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newedr, sectedr, tp2.item->size); newedr->count -= edr->count; 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } } newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG); if (!newei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newei, tp.item->data, tp.item->size); newei->refcount -= rc; Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } else { ERR("error - hash collision?\n"); return STATUS_INTERNAL_ERROR; } } else if (type == TYPE_SHARED_DATA_REF) { SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data; if (tp2.item->key.offset == sdr->offset) { uint32_t* sectsdrcount = (uint32_t*)tp2.item->data; EXTENT_ITEM* newei; if (ei->refcount == sdr->count) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } Status = delete_tree_item(Vcb, &tp2); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } if (!superseded) add_checksum_entry(Vcb, address, (ULONG)(size >> Vcb->sector_shift), NULL, Irp); return STATUS_SUCCESS; } if (*sectsdrcount < sdr->count) { ERR("error - extent section has refcount %x, trying to reduce by %x\n", *sectsdrcount, sdr->count); return STATUS_INTERNAL_ERROR; } Status = delete_tree_item(Vcb, &tp2); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } if (*sectsdrcount > sdr->count) { uint32_t* newsdr = ExAllocatePoolWithTag(PagedPool, tp2.item->size, ALLOC_TAG); if (!newsdr) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } *newsdr = *sectsdrcount - sdr->count; 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } } newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG); if (!newei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newei, tp.item->data, tp.item->size); newei->refcount -= rc; Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } else { ERR("error - collision?\n"); return STATUS_INTERNAL_ERROR; } } else if (type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF) { EXTENT_ITEM* newei; if (ei->refcount == 1) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } Status = delete_tree_item(Vcb, &tp2); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } Status = delete_tree_item(Vcb, &tp2); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG); if (!newei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newei, tp.item->data, tp.item->size); newei->refcount -= rc; Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } else if (type == TYPE_EXTENT_REF_V0) { EXTENT_REF_V0* erv0 = (EXTENT_REF_V0*)tp2.item->data; EXTENT_ITEM* newei; if (ei->refcount == erv0->count) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } Status = delete_tree_item(Vcb, &tp2); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } if (!superseded) add_checksum_entry(Vcb, address, (ULONG)(size >> Vcb->sector_shift), NULL, Irp); return STATUS_SUCCESS; } Status = delete_tree_item(Vcb, &tp2); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG); if (!newei) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newei, tp.item->data, tp.item->size); newei->refcount -= rc; Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } else { ERR("unhandled extent type %x\n", type); return STATUS_INTERNAL_ERROR; } } NTSTATUS decrease_extent_refcount_data(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root, uint64_t inode, uint64_t offset, uint32_t refcount, bool superseded, PIRP Irp) { EXTENT_DATA_REF edr; edr.root = root; edr.objid = inode; edr.offset = offset; edr.count = refcount; return decrease_extent_refcount(Vcb, address, size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, 0, superseded, Irp); } NTSTATUS decrease_extent_refcount_tree(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root, uint8_t level, PIRP Irp) { TREE_BLOCK_REF tbr; tbr.offset = root; return decrease_extent_refcount(Vcb, address, size, TYPE_TREE_BLOCK_REF, &tbr, NULL/*FIXME*/, level, 0, false, Irp); } static 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) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; searchkey.obj_id = address; searchkey.obj_type = TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return 0; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { TRACE("could not find address %I64x in extent tree\n", address); return 0; } if (tp.item->key.offset != size) { ERR("extent %I64x had size %I64x, not %I64x as expected\n", address, tp.item->key.offset, size); return 0; } if (tp.item->size >= sizeof(EXTENT_ITEM)) { EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data; uint32_t len = tp.item->size - sizeof(EXTENT_ITEM); uint8_t* ptr = (uint8_t*)&ei[1]; while (len > 0) { uint8_t secttype = *ptr; ULONG sectlen = get_extent_data_len(secttype); uint32_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t)); len--; if (sectlen > len) { 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); return 0; } if (sectlen == 0) { ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype); return 0; } if (secttype == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t)); if (sectedr->root == root && sectedr->objid == objid && sectedr->offset == offset) return sectcount; } len -= sectlen; ptr += sizeof(uint8_t) + sectlen; } } searchkey.obj_id = address; searchkey.obj_type = TYPE_EXTENT_DATA_REF; searchkey.offset = get_extent_data_ref_hash2(root, objid, offset); Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return 0; } if (!keycmp(searchkey, tp.item->key)) { if (tp.item->size < sizeof(EXTENT_DATA_REF)) 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)); else { EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)tp.item->data; return edr->count; } } return 0; } uint64_t get_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, PIRP Irp) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; EXTENT_ITEM* ei; searchkey.obj_id = address; searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return 0; } if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA && tp.item->key.obj_id == address && tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) { ei = (EXTENT_ITEM*)tp.item->data; return ei->refcount; } if (tp.item->key.obj_id != address || tp.item->key.obj_type != TYPE_EXTENT_ITEM) { ERR("couldn't find (%I64x,%x,%I64x) in extent tree\n", address, TYPE_EXTENT_ITEM, size); return 0; } else if (tp.item->key.offset != size) { ERR("extent %I64x had size %I64x, not %I64x as expected\n", address, tp.item->key.offset, size); return 0; } if (tp.item->size == sizeof(EXTENT_ITEM_V0)) { EXTENT_ITEM_V0* eiv0 = (EXTENT_ITEM_V0*)tp.item->data; return eiv0->refcount; } else if (tp.item->size < sizeof(EXTENT_ITEM)) { ERR("(%I64x,%x,%I64x) was %x bytes, expected at least %Ix\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM)); return 0; } ei = (EXTENT_ITEM*)tp.item->data; return ei->refcount; } bool is_extent_unique(device_extension* Vcb, uint64_t address, uint64_t size, PIRP Irp) { KEY searchkey; traverse_ptr tp, next_tp; NTSTATUS Status; uint64_t rc, rcrun, root = 0, inode = 0, offset = 0; uint32_t len; EXTENT_ITEM* ei; uint8_t* ptr; bool b; rc = get_extent_refcount(Vcb, address, size, Irp); if (rc == 1) return true; if (rc == 0) return false; searchkey.obj_id = address; searchkey.obj_type = TYPE_EXTENT_ITEM; searchkey.offset = size; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { WARN("error - find_item returned %08lx\n", Status); return false; } if (keycmp(tp.item->key, searchkey)) { WARN("could not find (%I64x,%x,%I64x)\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); return false; } if (tp.item->size == sizeof(EXTENT_ITEM_V0)) return false; if (tp.item->size < sizeof(EXTENT_ITEM)) { 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)); return false; } ei = (EXTENT_ITEM*)tp.item->data; len = tp.item->size - sizeof(EXTENT_ITEM); ptr = (uint8_t*)&ei[1]; if (ei->flags & EXTENT_ITEM_TREE_BLOCK) { if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) { 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)); return false; } len -= sizeof(EXTENT_ITEM2); ptr += sizeof(EXTENT_ITEM2); } rcrun = 0; // Loop through inline extent entries while (len > 0) { uint8_t secttype = *ptr; ULONG sectlen = get_extent_data_len(secttype); uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t)); len--; if (sectlen > len) { 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); return false; } if (sectlen == 0) { WARN("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype); return false; } if (secttype == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t)); if (root == 0 && inode == 0) { root = sectedr->root; inode = sectedr->objid; offset = sectedr->offset; } else if (root != sectedr->root || inode != sectedr->objid || offset != sectedr->offset) return false; } else return false; len -= sectlen; ptr += sizeof(uint8_t) + sectlen; rcrun += sectcount; } if (rcrun == rc) return true; // Loop through non-inlines if some refs still unaccounted for do { b = find_next_item(Vcb, &tp, &next_tp, false, Irp); if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)tp.item->data; if (tp.item->size < sizeof(EXTENT_DATA_REF)) { 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)); return false; } if (root == 0 && inode == 0) { root = edr->root; inode = edr->objid; offset = edr->offset; } else if (root != edr->root || inode != edr->objid || offset != edr->offset) return false; rcrun += edr->count; } if (rcrun == rc) return true; if (b) { tp = next_tp; if (tp.item->key.obj_id > searchkey.obj_id) break; } } while (b); // If we reach this point, there's still some refs unaccounted for somewhere. // Return false in case we mess things up elsewhere. return false; } uint64_t get_extent_flags(device_extension* Vcb, uint64_t address, PIRP Irp) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; EXTENT_ITEM* ei; searchkey.obj_id = address; searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return 0; } if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA && tp.item->key.obj_id == address && tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) { ei = (EXTENT_ITEM*)tp.item->data; return ei->flags; } if (tp.item->key.obj_id != address || tp.item->key.obj_type != TYPE_EXTENT_ITEM) { ERR("couldn't find %I64x in extent tree\n", address); return 0; } if (tp.item->size == sizeof(EXTENT_ITEM_V0)) return 0; else if (tp.item->size < sizeof(EXTENT_ITEM)) { ERR("(%I64x,%x,%I64x) was %x bytes, expected at least %Ix\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM)); return 0; } ei = (EXTENT_ITEM*)tp.item->data; return ei->flags; } void update_extent_flags(device_extension* Vcb, uint64_t address, uint64_t flags, PIRP Irp) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; EXTENT_ITEM* ei; searchkey.obj_id = address; searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return; } if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA && tp.item->key.obj_id == address && tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) { ei = (EXTENT_ITEM*)tp.item->data; ei->flags = flags; return; } if (tp.item->key.obj_id != address || tp.item->key.obj_type != TYPE_EXTENT_ITEM) { ERR("couldn't find %I64x in extent tree\n", address); return; } if (tp.item->size == sizeof(EXTENT_ITEM_V0)) return; else if (tp.item->size < sizeof(EXTENT_ITEM)) { ERR("(%I64x,%x,%I64x) was %x bytes, expected at least %Ix\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM)); return; } ei = (EXTENT_ITEM*)tp.item->data; ei->flags = flags; } static changed_extent* get_changed_extent_item(chunk* c, uint64_t address, uint64_t size, bool no_csum) { LIST_ENTRY* le; changed_extent* ce; le = c->changed_extents.Flink; while (le != &c->changed_extents) { ce = CONTAINING_RECORD(le, changed_extent, list_entry); if (ce->address == address && ce->size == size) return ce; le = le->Flink; } ce = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent), ALLOC_TAG); if (!ce) { ERR("out of memory\n"); return NULL; } ce->address = address; ce->size = size; ce->old_size = size; ce->count = 0; ce->old_count = 0; ce->no_csum = no_csum; ce->superseded = false; InitializeListHead(&ce->refs); InitializeListHead(&ce->old_refs); InsertTailList(&c->changed_extents, &ce->list_entry); return ce; } NTSTATUS 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, bool no_csum, bool superseded, PIRP Irp) { LIST_ENTRY* le; changed_extent* ce; changed_extent_ref* cer; NTSTATUS Status; KEY searchkey; traverse_ptr tp; uint32_t old_count; ExAcquireResourceExclusiveLite(&c->changed_extents_lock, true); ce = get_changed_extent_item(c, address, size, no_csum); if (!ce) { ERR("get_changed_extent_item failed\n"); Status = STATUS_INTERNAL_ERROR; goto end; } if (IsListEmpty(&ce->refs) && IsListEmpty(&ce->old_refs)) { // new entry searchkey.obj_id = address; searchkey.obj_type = TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); goto end; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { ERR("could not find address %I64x in extent tree\n", address); Status = STATUS_INTERNAL_ERROR; goto end; } if (tp.item->key.offset != size) { ERR("extent %I64x had size %I64x, not %I64x as expected\n", address, tp.item->key.offset, size); Status = STATUS_INTERNAL_ERROR; goto end; } if (tp.item->size == sizeof(EXTENT_ITEM_V0)) { EXTENT_ITEM_V0* eiv0 = (EXTENT_ITEM_V0*)tp.item->data; ce->count = ce->old_count = eiv0->refcount; } else if (tp.item->size >= sizeof(EXTENT_ITEM)) { EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data; ce->count = ce->old_count = ei->refcount; } else { 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)); Status = STATUS_INTERNAL_ERROR; goto end; } } le = ce->refs.Flink; while (le != &ce->refs) { cer = CONTAINING_RECORD(le, changed_extent_ref, list_entry); if (cer->type == TYPE_EXTENT_DATA_REF && cer->edr.root == root && cer->edr.objid == objid && cer->edr.offset == offset) { ce->count += count; cer->edr.count += count; Status = STATUS_SUCCESS; if (superseded) ce->superseded = true; goto end; } le = le->Flink; } old_count = find_extent_data_refcount(Vcb, address, size, root, objid, offset, Irp); if (old_count > 0) { cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG); if (!cer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } cer->type = TYPE_EXTENT_DATA_REF; cer->edr.root = root; cer->edr.objid = objid; cer->edr.offset = offset; cer->edr.count = old_count; InsertTailList(&ce->old_refs, &cer->list_entry); } cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG); if (!cer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } cer->type = TYPE_EXTENT_DATA_REF; cer->edr.root = root; cer->edr.objid = objid; cer->edr.offset = offset; cer->edr.count = old_count + count; InsertTailList(&ce->refs, &cer->list_entry); ce->count += count; if (superseded) ce->superseded = true; Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&c->changed_extents_lock); return Status; } void 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) { changed_extent* ce; changed_extent_ref* cer; LIST_ENTRY* le; ce = get_changed_extent_item(c, address, size, no_csum); if (!ce) { ERR("get_changed_extent_item failed\n"); return; } le = ce->refs.Flink; while (le != &ce->refs) { cer = CONTAINING_RECORD(le, changed_extent_ref, list_entry); if (cer->type == TYPE_EXTENT_DATA_REF && cer->edr.root == root && cer->edr.objid == objid && cer->edr.offset == offset) { ce->count += count; cer->edr.count += count; return; } le = le->Flink; } cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG); if (!cer) { ERR("out of memory\n"); return; } cer->type = TYPE_EXTENT_DATA_REF; cer->edr.root = root; cer->edr.objid = objid; cer->edr.offset = offset; cer->edr.count = count; InsertTailList(&ce->refs, &cer->list_entry); ce->count += count; } uint64_t find_extent_shared_tree_refcount(device_extension* Vcb, uint64_t address, uint64_t parent, PIRP Irp) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; uint64_t inline_rc; EXTENT_ITEM* ei; uint32_t len; uint8_t* ptr; searchkey.obj_id = address; searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return 0; } 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)) { TRACE("could not find address %I64x in extent tree\n", address); return 0; } if (tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->key.offset != Vcb->superblock.node_size) { ERR("extent %I64x had size %I64x, not %x as expected\n", address, tp.item->key.offset, Vcb->superblock.node_size); return 0; } if (tp.item->size < sizeof(EXTENT_ITEM)) { 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)); return 0; } ei = (EXTENT_ITEM*)tp.item->data; inline_rc = 0; len = tp.item->size - sizeof(EXTENT_ITEM); ptr = (uint8_t*)&ei[1]; if (searchkey.obj_type == TYPE_EXTENT_ITEM && ei->flags & EXTENT_ITEM_TREE_BLOCK) { if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) { 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) + sizeof(EXTENT_ITEM2)); return 0; } len -= sizeof(EXTENT_ITEM2); ptr += sizeof(EXTENT_ITEM2); } while (len > 0) { uint8_t secttype = *ptr; ULONG sectlen = get_extent_data_len(secttype); uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t)); len--; if (sectlen > len) { 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); return 0; } if (sectlen == 0) { ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype); return 0; } if (secttype == TYPE_SHARED_BLOCK_REF) { SHARED_BLOCK_REF* sectsbr = (SHARED_BLOCK_REF*)(ptr + sizeof(uint8_t)); if (sectsbr->offset == parent) return 1; } len -= sectlen; ptr += sizeof(uint8_t) + sectlen; inline_rc += sectcount; } // FIXME - what if old? if (inline_rc == ei->refcount) return 0; searchkey.obj_id = address; searchkey.obj_type = TYPE_SHARED_BLOCK_REF; searchkey.offset = parent; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return 0; } if (!keycmp(searchkey, tp.item->key)) return 1; return 0; } uint32_t find_extent_shared_data_refcount(device_extension* Vcb, uint64_t address, uint64_t parent, PIRP Irp) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; uint64_t inline_rc; EXTENT_ITEM* ei; uint32_t len; uint8_t* ptr; searchkey.obj_id = address; searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return 0; } 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)) { TRACE("could not find address %I64x in extent tree\n", address); return 0; } if (tp.item->size < sizeof(EXTENT_ITEM)) { 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)); return 0; } ei = (EXTENT_ITEM*)tp.item->data; inline_rc = 0; len = tp.item->size - sizeof(EXTENT_ITEM); ptr = (uint8_t*)&ei[1]; while (len > 0) { uint8_t secttype = *ptr; ULONG sectlen = get_extent_data_len(secttype); uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t)); len--; if (sectlen > len) { 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); return 0; } if (sectlen == 0) { ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype); return 0; } if (secttype == TYPE_SHARED_DATA_REF) { SHARED_DATA_REF* sectsdr = (SHARED_DATA_REF*)(ptr + sizeof(uint8_t)); if (sectsdr->offset == parent) return sectsdr->count; } len -= sectlen; ptr += sizeof(uint8_t) + sectlen; inline_rc += sectcount; } // FIXME - what if old? if (inline_rc == ei->refcount) return 0; searchkey.obj_id = address; searchkey.obj_type = TYPE_SHARED_DATA_REF; searchkey.offset = parent; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return 0; } if (!keycmp(searchkey, tp.item->key)) { if (tp.item->size < sizeof(uint32_t)) 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)); else { uint32_t* count = (uint32_t*)tp.item->data; return *count; } } return 0; } ================================================ FILE: src/fastio.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" FAST_IO_DISPATCH FastIoDispatch; _Function_class_(FAST_IO_QUERY_BASIC_INFO) static BOOLEAN __stdcall fast_query_basic_info(PFILE_OBJECT FileObject, BOOLEAN wait, PFILE_BASIC_INFORMATION fbi, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) { fcb* fcb; ccb* ccb; UNUSED(DeviceObject); FsRtlEnterFileSystem(); TRACE("(%p, %u, %p, %p, %p)\n", FileObject, wait, fbi, IoStatus, DeviceObject); if (!FileObject) { FsRtlExitFileSystem(); return false; } fcb = FileObject->FsContext; if (!fcb) { FsRtlExitFileSystem(); return false; } ccb = FileObject->FsContext2; if (!ccb) { FsRtlExitFileSystem(); return false; } if (!(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) { FsRtlExitFileSystem(); return false; } if (fcb->ads) { if (!ccb->fileref || !ccb->fileref->parent || !ccb->fileref->parent->fcb) { FsRtlExitFileSystem(); return false; } fcb = ccb->fileref->parent->fcb; } if (!ExAcquireResourceSharedLite(fcb->Header.Resource, wait)) { FsRtlExitFileSystem(); return false; } if (fcb == fcb->Vcb->dummy_fcb) { LARGE_INTEGER time; KeQuerySystemTime(&time); fbi->CreationTime = fbi->LastAccessTime = fbi->LastWriteTime = fbi->ChangeTime = time; } else { fbi->CreationTime.QuadPart = unix_time_to_win(&fcb->inode_item.otime); fbi->LastAccessTime.QuadPart = unix_time_to_win(&fcb->inode_item.st_atime); fbi->LastWriteTime.QuadPart = unix_time_to_win(&fcb->inode_item.st_mtime); fbi->ChangeTime.QuadPart = unix_time_to_win(&fcb->inode_item.st_ctime); } fbi->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts; IoStatus->Status = STATUS_SUCCESS; IoStatus->Information = sizeof(FILE_BASIC_INFORMATION); ExReleaseResourceLite(fcb->Header.Resource); FsRtlExitFileSystem(); return true; } _Function_class_(FAST_IO_QUERY_STANDARD_INFO) static BOOLEAN __stdcall fast_query_standard_info(PFILE_OBJECT FileObject, BOOLEAN wait, PFILE_STANDARD_INFORMATION fsi, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) { fcb* fcb; ccb* ccb; bool ads; ULONG adssize; UNUSED(DeviceObject); FsRtlEnterFileSystem(); TRACE("(%p, %u, %p, %p, %p)\n", FileObject, wait, fsi, IoStatus, DeviceObject); if (!FileObject) { FsRtlExitFileSystem(); return false; } fcb = FileObject->FsContext; ccb = FileObject->FsContext2; if (!fcb) { FsRtlExitFileSystem(); return false; } if (!ExAcquireResourceSharedLite(fcb->Header.Resource, wait)) { FsRtlExitFileSystem(); return false; } ads = fcb->ads; if (ads) { struct _fcb* fcb2; if (!ccb || !ccb->fileref || !ccb->fileref->parent || !ccb->fileref->parent->fcb) { ExReleaseResourceLite(fcb->Header.Resource); FsRtlExitFileSystem(); return false; } adssize = fcb->adsdata.Length; fcb2 = ccb->fileref->parent->fcb; ExReleaseResourceLite(fcb->Header.Resource); fcb = fcb2; if (!ExAcquireResourceSharedLite(fcb->Header.Resource, wait)) { FsRtlExitFileSystem(); return false; } fsi->AllocationSize.QuadPart = fsi->EndOfFile.QuadPart = adssize; fsi->NumberOfLinks = fcb->inode_item.st_nlink; fsi->Directory = false; } else { fsi->AllocationSize.QuadPart = fcb_alloc_size(fcb); fsi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size; fsi->NumberOfLinks = fcb->inode_item.st_nlink; fsi->Directory = S_ISDIR(fcb->inode_item.st_mode); } fsi->DeletePending = ccb->fileref ? ccb->fileref->delete_on_close : false; IoStatus->Status = STATUS_SUCCESS; IoStatus->Information = sizeof(FILE_STANDARD_INFORMATION); ExReleaseResourceLite(fcb->Header.Resource); FsRtlExitFileSystem(); return true; } _Function_class_(FAST_IO_CHECK_IF_POSSIBLE) static BOOLEAN __stdcall fast_io_check_if_possible(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, ULONG Length, BOOLEAN Wait, ULONG LockKey, BOOLEAN CheckForReadOperation, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) { fcb* fcb = FileObject->FsContext; LARGE_INTEGER len2; UNUSED(Wait); UNUSED(IoStatus); UNUSED(DeviceObject); len2.QuadPart = Length; if (CheckForReadOperation) { if (FsRtlFastCheckLockForRead(&fcb->lock, FileOffset, &len2, LockKey, FileObject, PsGetCurrentProcess())) return true; } else { if (!fcb->Vcb->readonly && !is_subvol_readonly(fcb->subvol, NULL) && FsRtlFastCheckLockForWrite(&fcb->lock, FileOffset, &len2, LockKey, FileObject, PsGetCurrentProcess())) return true; } return false; } _Function_class_(FAST_IO_QUERY_NETWORK_OPEN_INFO) static BOOLEAN __stdcall fast_io_query_network_open_info(PFILE_OBJECT FileObject, BOOLEAN Wait, FILE_NETWORK_OPEN_INFORMATION* fnoi, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) { fcb* fcb; ccb* ccb; file_ref* fileref; UNUSED(Wait); UNUSED(IoStatus); // FIXME - really? What about IoStatus->Information? UNUSED(DeviceObject); FsRtlEnterFileSystem(); TRACE("(%p, %u, %p, %p, %p)\n", FileObject, Wait, fnoi, IoStatus, DeviceObject); RtlZeroMemory(fnoi, sizeof(FILE_NETWORK_OPEN_INFORMATION)); fcb = FileObject->FsContext; if (!fcb || fcb == fcb->Vcb->volume_fcb) { FsRtlExitFileSystem(); return false; } ccb = FileObject->FsContext2; if (!ccb) { FsRtlExitFileSystem(); return false; } fileref = ccb->fileref; if (fcb == fcb->Vcb->dummy_fcb) { LARGE_INTEGER time; KeQuerySystemTime(&time); fnoi->CreationTime = fnoi->LastAccessTime = fnoi->LastWriteTime = fnoi->ChangeTime = time; } else { INODE_ITEM* ii; if (fcb->ads) { if (!fileref || !fileref->parent) { ERR("no fileref for stream\n"); FsRtlExitFileSystem(); return false; } ii = &fileref->parent->fcb->inode_item; } else ii = &fcb->inode_item; fnoi->CreationTime.QuadPart = unix_time_to_win(&ii->otime); fnoi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime); fnoi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime); fnoi->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime); } if (fcb->ads) { fnoi->AllocationSize.QuadPart = fnoi->EndOfFile.QuadPart = fcb->adsdata.Length; fnoi->FileAttributes = fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fileref->parent->fcb->atts; } else { fnoi->AllocationSize.QuadPart = fcb_alloc_size(fcb); fnoi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size; fnoi->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts; } FsRtlExitFileSystem(); return true; } _Function_class_(FAST_IO_ACQUIRE_FOR_MOD_WRITE) static NTSTATUS __stdcall fast_io_acquire_for_mod_write(PFILE_OBJECT FileObject, PLARGE_INTEGER EndingOffset, struct _ERESOURCE **ResourceToRelease, PDEVICE_OBJECT DeviceObject) { fcb* fcb; TRACE("(%p, %I64x, %p, %p)\n", FileObject, EndingOffset ? EndingOffset->QuadPart : 0, ResourceToRelease, DeviceObject); UNUSED(EndingOffset); UNUSED(DeviceObject); fcb = FileObject->FsContext; if (!fcb) return STATUS_INVALID_PARAMETER; // Make sure we don't get interrupted by the flush thread, which can cause a deadlock if (!ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, false)) return STATUS_CANT_WAIT; if (!ExAcquireResourceExclusiveLite(fcb->Header.Resource, false)) { ExReleaseResourceLite(&fcb->Vcb->tree_lock); TRACE("returning STATUS_CANT_WAIT\n"); return STATUS_CANT_WAIT; } // Ideally this would be PagingIoResource, but that doesn't play well with copy-on-write, // as we can't guarantee that we won't need to do any reallocations. *ResourceToRelease = fcb->Header.Resource; TRACE("returning STATUS_SUCCESS\n"); return STATUS_SUCCESS; } _Function_class_(FAST_IO_RELEASE_FOR_MOD_WRITE) static NTSTATUS __stdcall fast_io_release_for_mod_write(PFILE_OBJECT FileObject, struct _ERESOURCE *ResourceToRelease, PDEVICE_OBJECT DeviceObject) { fcb* fcb; TRACE("(%p, %p, %p)\n", FileObject, ResourceToRelease, DeviceObject); UNUSED(DeviceObject); fcb = FileObject->FsContext; ExReleaseResourceLite(ResourceToRelease); ExReleaseResourceLite(&fcb->Vcb->tree_lock); return STATUS_SUCCESS; } _Function_class_(FAST_IO_ACQUIRE_FOR_CCFLUSH) static NTSTATUS __stdcall fast_io_acquire_for_ccflush(PFILE_OBJECT FileObject, PDEVICE_OBJECT DeviceObject) { UNUSED(FileObject); UNUSED(DeviceObject); IoSetTopLevelIrp((PIRP)FSRTL_CACHE_TOP_LEVEL_IRP); return STATUS_SUCCESS; } _Function_class_(FAST_IO_RELEASE_FOR_CCFLUSH) static NTSTATUS __stdcall fast_io_release_for_ccflush(PFILE_OBJECT FileObject, PDEVICE_OBJECT DeviceObject) { UNUSED(FileObject); UNUSED(DeviceObject); if (IoGetTopLevelIrp() == (PIRP)FSRTL_CACHE_TOP_LEVEL_IRP) IoSetTopLevelIrp(NULL); return STATUS_SUCCESS; } _Function_class_(FAST_IO_WRITE) static 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) { fcb* fcb = FileObject->FsContext; bool ret; FsRtlEnterFileSystem(); if (!ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, Wait)) { FsRtlExitFileSystem(); return false; } ret = FsRtlCopyWrite(FileObject, FileOffset, Length, Wait, LockKey, Buffer, IoStatus, DeviceObject); if (ret) fcb->inode_item.st_size = fcb->Header.FileSize.QuadPart; ExReleaseResourceLite(&fcb->Vcb->tree_lock); FsRtlExitFileSystem(); return ret; } _Function_class_(FAST_IO_LOCK) static BOOLEAN __stdcall fast_io_lock(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, PLARGE_INTEGER Length, PEPROCESS ProcessId, ULONG Key, BOOLEAN FailImmediately, BOOLEAN ExclusiveLock, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) { BOOLEAN ret; fcb* fcb = FileObject->FsContext; UNUSED(DeviceObject); TRACE("(%p, %I64x, %I64x, %p, %lx, %u, %u, %p, %p)\n", FileObject, FileOffset ? FileOffset->QuadPart : 0, Length ? Length->QuadPart : 0, ProcessId, Key, FailImmediately, ExclusiveLock, IoStatus, DeviceObject); if (fcb->type != BTRFS_TYPE_FILE) { WARN("can only lock files\n"); IoStatus->Status = STATUS_INVALID_PARAMETER; IoStatus->Information = 0; return true; } FsRtlEnterFileSystem(); ExAcquireResourceSharedLite(fcb->Header.Resource, true); ret = FsRtlFastLock(&fcb->lock, FileObject, FileOffset, Length, ProcessId, Key, FailImmediately, ExclusiveLock, IoStatus, NULL, false); if (ret) fcb->Header.IsFastIoPossible = fast_io_possible(fcb); ExReleaseResourceLite(fcb->Header.Resource); FsRtlExitFileSystem(); return ret; } _Function_class_(FAST_IO_UNLOCK_SINGLE) static BOOLEAN __stdcall fast_io_unlock_single(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, PLARGE_INTEGER Length, PEPROCESS ProcessId, ULONG Key, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) { fcb* fcb = FileObject->FsContext; UNUSED(DeviceObject); TRACE("(%p, %I64x, %I64x, %p, %lx, %p, %p)\n", FileObject, FileOffset ? FileOffset->QuadPart : 0, Length ? Length->QuadPart : 0, ProcessId, Key, IoStatus, DeviceObject); IoStatus->Information = 0; if (fcb->type != BTRFS_TYPE_FILE) { WARN("can only lock files\n"); IoStatus->Status = STATUS_INVALID_PARAMETER; return true; } FsRtlEnterFileSystem(); IoStatus->Status = FsRtlFastUnlockSingle(&fcb->lock, FileObject, FileOffset, Length, ProcessId, Key, NULL, false); fcb->Header.IsFastIoPossible = fast_io_possible(fcb); FsRtlExitFileSystem(); return true; } _Function_class_(FAST_IO_UNLOCK_ALL) static BOOLEAN __stdcall fast_io_unlock_all(PFILE_OBJECT FileObject, PEPROCESS ProcessId, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) { fcb* fcb = FileObject->FsContext; UNUSED(DeviceObject); TRACE("(%p, %p, %p, %p)\n", FileObject, ProcessId, IoStatus, DeviceObject); IoStatus->Information = 0; if (fcb->type != BTRFS_TYPE_FILE) { WARN("can only lock files\n"); IoStatus->Status = STATUS_INVALID_PARAMETER; return true; } FsRtlEnterFileSystem(); ExAcquireResourceSharedLite(fcb->Header.Resource, true); IoStatus->Status = FsRtlFastUnlockAll(&fcb->lock, FileObject, ProcessId, NULL); fcb->Header.IsFastIoPossible = fast_io_possible(fcb); ExReleaseResourceLite(fcb->Header.Resource); FsRtlExitFileSystem(); return true; } _Function_class_(FAST_IO_UNLOCK_ALL_BY_KEY) static BOOLEAN __stdcall fast_io_unlock_all_by_key(PFILE_OBJECT FileObject, PVOID ProcessId, ULONG Key, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) { fcb* fcb = FileObject->FsContext; UNUSED(DeviceObject); TRACE("(%p, %p, %lx, %p, %p)\n", FileObject, ProcessId, Key, IoStatus, DeviceObject); IoStatus->Information = 0; if (fcb->type != BTRFS_TYPE_FILE) { WARN("can only lock files\n"); IoStatus->Status = STATUS_INVALID_PARAMETER; return true; } FsRtlEnterFileSystem(); ExAcquireResourceSharedLite(fcb->Header.Resource, true); IoStatus->Status = FsRtlFastUnlockAllByKey(&fcb->lock, FileObject, ProcessId, Key, NULL); fcb->Header.IsFastIoPossible = fast_io_possible(fcb); ExReleaseResourceLite(fcb->Header.Resource); FsRtlExitFileSystem(); return true; } static void __stdcall fast_io_acquire_for_create_section(_In_ PFILE_OBJECT FileObject) { fcb* fcb; TRACE("(%p)\n", FileObject); if (!FileObject) return; fcb = FileObject->FsContext; if (!fcb) return; ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); } static void __stdcall fast_io_release_for_create_section(_In_ PFILE_OBJECT FileObject) { fcb* fcb; TRACE("(%p)\n", FileObject); if (!FileObject) return; fcb = FileObject->FsContext; if (!fcb) return; ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&fcb->Vcb->tree_lock); } void init_fast_io_dispatch(FAST_IO_DISPATCH** fiod) { RtlZeroMemory(&FastIoDispatch, sizeof(FastIoDispatch)); FastIoDispatch.SizeOfFastIoDispatch = sizeof(FAST_IO_DISPATCH); FastIoDispatch.FastIoCheckIfPossible = fast_io_check_if_possible; FastIoDispatch.FastIoRead = FsRtlCopyRead; FastIoDispatch.FastIoWrite = fast_io_write; FastIoDispatch.FastIoQueryBasicInfo = fast_query_basic_info; FastIoDispatch.FastIoQueryStandardInfo = fast_query_standard_info; FastIoDispatch.FastIoLock = fast_io_lock; FastIoDispatch.FastIoUnlockSingle = fast_io_unlock_single; FastIoDispatch.FastIoUnlockAll = fast_io_unlock_all; FastIoDispatch.FastIoUnlockAllByKey = fast_io_unlock_all_by_key; FastIoDispatch.AcquireFileForNtCreateSection = fast_io_acquire_for_create_section; FastIoDispatch.ReleaseFileForNtCreateSection = fast_io_release_for_create_section; FastIoDispatch.FastIoQueryNetworkOpenInfo = fast_io_query_network_open_info; FastIoDispatch.AcquireForModWrite = fast_io_acquire_for_mod_write; FastIoDispatch.MdlRead = FsRtlMdlReadDev; FastIoDispatch.MdlReadComplete = FsRtlMdlReadCompleteDev; FastIoDispatch.PrepareMdlWrite = FsRtlPrepareMdlWriteDev; FastIoDispatch.MdlWriteComplete = FsRtlMdlWriteCompleteDev; FastIoDispatch.ReleaseForModWrite = fast_io_release_for_mod_write; FastIoDispatch.AcquireForCcFlush = fast_io_acquire_for_ccflush; FastIoDispatch.ReleaseForCcFlush = fast_io_release_for_ccflush; *fiod = &FastIoDispatch; } ================================================ FILE: src/fileinfo.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include "crc32c.h" // not currently in mingw - introduced with Windows 10 #ifndef _MSC_VER #define FileIdInformation (enum _FILE_INFORMATION_CLASS)59 #define FileHardLinkFullIdInformation (enum _FILE_INFORMATION_CLASS)62 #define FileDispositionInformationEx (enum _FILE_INFORMATION_CLASS)64 #define FileRenameInformationEx (enum _FILE_INFORMATION_CLASS)65 #define FileStatInformation (enum _FILE_INFORMATION_CLASS)68 #define FileStatLxInformation (enum _FILE_INFORMATION_CLASS)70 #define FileCaseSensitiveInformation (enum _FILE_INFORMATION_CLASS)71 #define FileLinkInformationEx (enum _FILE_INFORMATION_CLASS)72 #define FileStorageReserveIdInformation (enum _FILE_INFORMATION_CLASS)74 typedef struct _FILE_ID_INFORMATION { ULONGLONG VolumeSerialNumber; FILE_ID_128 FileId; } FILE_ID_INFORMATION, *PFILE_ID_INFORMATION; typedef struct _FILE_STAT_INFORMATION { LARGE_INTEGER FileId; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER AllocationSize; LARGE_INTEGER EndOfFile; ULONG FileAttributes; ULONG ReparseTag; ULONG NumberOfLinks; ACCESS_MASK EffectiveAccess; } FILE_STAT_INFORMATION, *PFILE_STAT_INFORMATION; typedef struct _FILE_STAT_LX_INFORMATION { LARGE_INTEGER FileId; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER AllocationSize; LARGE_INTEGER EndOfFile; ULONG FileAttributes; ULONG ReparseTag; ULONG NumberOfLinks; ACCESS_MASK EffectiveAccess; ULONG LxFlags; ULONG LxUid; ULONG LxGid; ULONG LxMode; ULONG LxDeviceIdMajor; ULONG LxDeviceIdMinor; } FILE_STAT_LX_INFORMATION, *PFILE_STAT_LX_INFORMATION; #define LX_FILE_METADATA_HAS_UID 0x01 #define LX_FILE_METADATA_HAS_GID 0x02 #define LX_FILE_METADATA_HAS_MODE 0x04 #define LX_FILE_METADATA_HAS_DEVICE_ID 0x08 #define LX_FILE_CASE_SENSITIVE_DIR 0x10 typedef struct _FILE_RENAME_INFORMATION_EX { union { BOOLEAN ReplaceIfExists; ULONG Flags; }; HANDLE RootDirectory; ULONG FileNameLength; WCHAR FileName[1]; } FILE_RENAME_INFORMATION_EX, *PFILE_RENAME_INFORMATION_EX; typedef struct _FILE_DISPOSITION_INFORMATION_EX { ULONG Flags; } FILE_DISPOSITION_INFORMATION_EX, *PFILE_DISPOSITION_INFORMATION_EX; typedef struct _FILE_LINK_INFORMATION_EX { union { BOOLEAN ReplaceIfExists; ULONG Flags; }; HANDLE RootDirectory; ULONG FileNameLength; WCHAR FileName[1]; } FILE_LINK_INFORMATION_EX, *PFILE_LINK_INFORMATION_EX; typedef struct _FILE_CASE_SENSITIVE_INFORMATION { ULONG Flags; } FILE_CASE_SENSITIVE_INFORMATION, *PFILE_CASE_SENSITIVE_INFORMATION; typedef struct _FILE_LINK_ENTRY_FULL_ID_INFORMATION { ULONG NextEntryOffset; FILE_ID_128 ParentFileId; ULONG FileNameLength; WCHAR FileName[1]; } FILE_LINK_ENTRY_FULL_ID_INFORMATION, *PFILE_LINK_ENTRY_FULL_ID_INFORMATION; typedef struct _FILE_LINKS_FULL_ID_INFORMATION { ULONG BytesNeeded; ULONG EntriesReturned; FILE_LINK_ENTRY_FULL_ID_INFORMATION Entry; } FILE_LINKS_FULL_ID_INFORMATION, *PFILE_LINKS_FULL_ID_INFORMATION; #define FILE_RENAME_REPLACE_IF_EXISTS 0x001 #define FILE_RENAME_POSIX_SEMANTICS 0x002 #define FILE_RENAME_SUPPRESS_PIN_STATE_INHERITANCE 0x004 #define FILE_RENAME_SUPPRESS_STORAGE_RESERVE_INHERITANCE 0x008 #define FILE_RENAME_NO_INCREASE_AVAILABLE_SPACE 0x010 #define FILE_RENAME_NO_DECREASE_AVAILABLE_SPACE 0x020 #define FILE_RENAME_IGNORE_READONLY_ATTRIBUTE 0x040 #define FILE_RENAME_FORCE_RESIZE_TARGET_SR 0x080 #define FILE_RENAME_FORCE_RESIZE_SOURCE_SR 0x100 #define FILE_DISPOSITION_DELETE 0x1 #define FILE_DISPOSITION_POSIX_SEMANTICS 0x2 #define FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK 0x4 #define FILE_DISPOSITION_ON_CLOSE 0x8 #define FILE_LINK_REPLACE_IF_EXISTS 0x001 #define FILE_LINK_POSIX_SEMANTICS 0x002 #define FILE_LINK_SUPPRESS_STORAGE_RESERVE_INHERITANCE 0x008 #define FILE_LINK_NO_INCREASE_AVAILABLE_SPACE 0x010 #define FILE_LINK_NO_DECREASE_AVAILABLE_SPACE 0x020 #define FILE_LINK_IGNORE_READONLY_ATTRIBUTE 0x040 #define FILE_LINK_FORCE_RESIZE_TARGET_SR 0x080 #define FILE_LINK_FORCE_RESIZE_SOURCE_SR 0x100 #else #define FILE_RENAME_INFORMATION_EX FILE_RENAME_INFORMATION #define FILE_LINK_INFORMATION_EX FILE_LINK_INFORMATION #endif static NTSTATUS set_basic_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) { FILE_BASIC_INFORMATION* fbi = Irp->AssociatedIrp.SystemBuffer; fcb* fcb = FileObject->FsContext; ccb* ccb = FileObject->FsContext2; file_ref* fileref = ccb ? ccb->fileref : NULL; ULONG defda, filter = 0; bool inode_item_changed = false; NTSTATUS Status; if (fcb->ads) { if (fileref && fileref->parent) fcb = fileref->parent->fcb; else { ERR("stream did not have fileref\n"); return STATUS_INTERNAL_ERROR; } } if (!ccb) { ERR("ccb was NULL\n"); return STATUS_INVALID_PARAMETER; } TRACE("file = %p, attributes = %lx\n", FileObject, fbi->FileAttributes); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (fbi->FileAttributes & FILE_ATTRIBUTE_DIRECTORY && fcb->type != BTRFS_TYPE_DIRECTORY) { WARN("attempted to set FILE_ATTRIBUTE_DIRECTORY on non-directory\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (fcb->inode == SUBVOL_ROOT_INODE && is_subvol_readonly(fcb->subvol, Irp) && (fbi->FileAttributes == 0 || fbi->FileAttributes & FILE_ATTRIBUTE_READONLY)) { Status = STATUS_ACCESS_DENIED; goto end; } // don't allow readonly subvol to be made r/w if send operation running on it if (fcb->inode == SUBVOL_ROOT_INODE && fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY && fcb->subvol->send_ops > 0) { Status = STATUS_DEVICE_NOT_READY; goto end; } // times of -2 are some sort of undocumented behaviour to do with LXSS if (fbi->CreationTime.QuadPart == -2) fbi->CreationTime.QuadPart = 0; if (fbi->LastAccessTime.QuadPart == -2) fbi->LastAccessTime.QuadPart = 0; if (fbi->LastWriteTime.QuadPart == -2) fbi->LastWriteTime.QuadPart = 0; if (fbi->ChangeTime.QuadPart == -2) fbi->ChangeTime.QuadPart = 0; if (fbi->CreationTime.QuadPart == -1) ccb->user_set_creation_time = true; else if (fbi->CreationTime.QuadPart != 0) { win_time_to_unix(fbi->CreationTime, &fcb->inode_item.otime); inode_item_changed = true; filter |= FILE_NOTIFY_CHANGE_CREATION; ccb->user_set_creation_time = true; } if (fbi->LastAccessTime.QuadPart == -1) ccb->user_set_access_time = true; else if (fbi->LastAccessTime.QuadPart != 0) { win_time_to_unix(fbi->LastAccessTime, &fcb->inode_item.st_atime); inode_item_changed = true; filter |= FILE_NOTIFY_CHANGE_LAST_ACCESS; ccb->user_set_access_time = true; } if (fbi->LastWriteTime.QuadPart == -1) ccb->user_set_write_time = true; else if (fbi->LastWriteTime.QuadPart != 0) { win_time_to_unix(fbi->LastWriteTime, &fcb->inode_item.st_mtime); inode_item_changed = true; filter |= FILE_NOTIFY_CHANGE_LAST_WRITE; ccb->user_set_write_time = true; } if (fbi->ChangeTime.QuadPart == -1) ccb->user_set_change_time = true; else if (fbi->ChangeTime.QuadPart != 0) { win_time_to_unix(fbi->ChangeTime, &fcb->inode_item.st_ctime); inode_item_changed = true; // no filter for this ccb->user_set_change_time = true; } // FileAttributes == 0 means don't set - undocumented, but seen in fastfat if (fbi->FileAttributes != 0) { LARGE_INTEGER time; BTRFS_TIME now; fbi->FileAttributes &= ~FILE_ATTRIBUTE_NORMAL; 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] == '.', true, Irp); if (fcb->type == BTRFS_TYPE_DIRECTORY) fbi->FileAttributes |= FILE_ATTRIBUTE_DIRECTORY; else if (fcb->type == BTRFS_TYPE_SYMLINK) fbi->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT; fcb->atts_changed = true; if (fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) fbi->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT; if (defda == fbi->FileAttributes) fcb->atts_deleted = true; else if (fcb->inode == SUBVOL_ROOT_INODE && (defda | FILE_ATTRIBUTE_READONLY) == (fbi->FileAttributes | FILE_ATTRIBUTE_READONLY)) fcb->atts_deleted = true; fcb->atts = fbi->FileAttributes; KeQuerySystemTime(&time); win_time_to_unix(time, &now); if (!ccb->user_set_change_time) fcb->inode_item.st_ctime = now; fcb->subvol->root_item.ctransid = Vcb->superblock.generation; fcb->subvol->root_item.ctime = now; if (fcb->inode == SUBVOL_ROOT_INODE) { if (fbi->FileAttributes & FILE_ATTRIBUTE_READONLY) fcb->subvol->root_item.flags |= BTRFS_SUBVOL_READONLY; else fcb->subvol->root_item.flags &= ~BTRFS_SUBVOL_READONLY; } inode_item_changed = true; filter |= FILE_NOTIFY_CHANGE_ATTRIBUTES; } if (inode_item_changed) { fcb->inode_item.transid = Vcb->superblock.generation; fcb->inode_item.sequence++; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); } if (filter != 0) queue_notification_fcb(fileref, filter, FILE_ACTION_MODIFIED, NULL); Status = STATUS_SUCCESS; end: ExReleaseResourceLite(fcb->Header.Resource); return Status; } static NTSTATUS set_disposition_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, bool ex) { fcb* fcb = FileObject->FsContext; ccb* ccb = FileObject->FsContext2; file_ref* fileref = ccb ? ccb->fileref : NULL; ULONG atts, flags; NTSTATUS Status; if (!fileref) return STATUS_INVALID_PARAMETER; if (ex) { FILE_DISPOSITION_INFORMATION_EX* fdi = Irp->AssociatedIrp.SystemBuffer; flags = fdi->Flags; } else { FILE_DISPOSITION_INFORMATION* fdi = Irp->AssociatedIrp.SystemBuffer; flags = fdi->DeleteFile ? FILE_DISPOSITION_DELETE : 0; } ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); TRACE("changing delete_on_close to %s for fcb %p\n", flags & FILE_DISPOSITION_DELETE ? "true" : "false", fcb); if (fcb->ads) { if (fileref->parent) atts = fileref->parent->fcb->atts; else { ERR("no fileref for stream\n"); Status = STATUS_INTERNAL_ERROR; goto end; } } else atts = fcb->atts; TRACE("atts = %lx\n", atts); if (atts & FILE_ATTRIBUTE_READONLY) { TRACE("not allowing readonly file to be deleted\n"); Status = STATUS_CANNOT_DELETE; goto end; } if (fcb->inode == SUBVOL_ROOT_INODE && fcb->subvol->id == BTRFS_ROOT_FSTREE) { WARN("not allowing \\$Root to be deleted\n"); Status = STATUS_ACCESS_DENIED; goto end; } // FIXME - can we skip this bit for subvols? if (fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0 && (!fileref || fileref->fcb != Vcb->dummy_fcb)) { TRACE("directory not empty\n"); Status = STATUS_DIRECTORY_NOT_EMPTY; goto end; } if (!MmFlushImageSection(&fcb->nonpaged->segment_object, MmFlushForDelete)) { if (!ex || flags & FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK) { TRACE("trying to delete file which is being mapped as an image\n"); Status = STATUS_CANNOT_DELETE; goto end; } } ccb->fileref->delete_on_close = flags & FILE_DISPOSITION_DELETE; FileObject->DeletePending = flags & FILE_DISPOSITION_DELETE; if (flags & FILE_DISPOSITION_DELETE && flags & FILE_DISPOSITION_POSIX_SEMANTICS) ccb->fileref->posix_delete = true; Status = STATUS_SUCCESS; end: ExReleaseResourceLite(fcb->Header.Resource); // send notification that directory is about to be deleted if (NT_SUCCESS(Status) && flags & FILE_DISPOSITION_DELETE && fcb->type == BTRFS_TYPE_DIRECTORY) { FsRtlNotifyFullChangeDirectory(Vcb->NotifySync, &Vcb->DirNotifyList, FileObject->FsContext, NULL, false, false, 0, NULL, NULL, NULL); } return Status; } bool has_open_children(file_ref* fileref) { LIST_ENTRY* le = fileref->children.Flink; if (IsListEmpty(&fileref->children)) return false; while (le != &fileref->children) { file_ref* c = CONTAINING_RECORD(le, file_ref, list_entry); if (c->open_count > 0) return true; if (has_open_children(c)) return true; le = le->Flink; } return false; } static NTSTATUS duplicate_fcb(fcb* oldfcb, fcb** pfcb) { device_extension* Vcb = oldfcb->Vcb; fcb* fcb; LIST_ENTRY* le; // FIXME - we can skip a lot of this if the inode is about to be deleted fcb = create_fcb(Vcb, PagedPool); // FIXME - what if we duplicate the paging file? if (!fcb) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } fcb->Vcb = Vcb; fcb->Header.IsFastIoPossible = fast_io_possible(fcb); fcb->Header.AllocationSize = oldfcb->Header.AllocationSize; fcb->Header.FileSize = oldfcb->Header.FileSize; fcb->Header.ValidDataLength = oldfcb->Header.ValidDataLength; fcb->type = oldfcb->type; if (oldfcb->ads) { fcb->ads = true; fcb->adshash = oldfcb->adshash; fcb->adsmaxlen = oldfcb->adsmaxlen; if (oldfcb->adsxattr.Buffer && oldfcb->adsxattr.Length > 0) { fcb->adsxattr.Length = oldfcb->adsxattr.Length; fcb->adsxattr.MaximumLength = fcb->adsxattr.Length + 1; fcb->adsxattr.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->adsxattr.MaximumLength, ALLOC_TAG); if (!fcb->adsxattr.Buffer) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(fcb->adsxattr.Buffer, oldfcb->adsxattr.Buffer, fcb->adsxattr.Length); fcb->adsxattr.Buffer[fcb->adsxattr.Length] = 0; } if (oldfcb->adsdata.Buffer && oldfcb->adsdata.Length > 0) { fcb->adsdata.Length = fcb->adsdata.MaximumLength = oldfcb->adsdata.Length; fcb->adsdata.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->adsdata.MaximumLength, ALLOC_TAG); if (!fcb->adsdata.Buffer) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(fcb->adsdata.Buffer, oldfcb->adsdata.Buffer, fcb->adsdata.Length); } goto end; } RtlCopyMemory(&fcb->inode_item, &oldfcb->inode_item, sizeof(INODE_ITEM)); fcb->inode_item_changed = true; if (oldfcb->sd && RtlLengthSecurityDescriptor(oldfcb->sd) > 0) { fcb->sd = ExAllocatePoolWithTag(PagedPool, RtlLengthSecurityDescriptor(oldfcb->sd), ALLOC_TAG); if (!fcb->sd) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(fcb->sd, oldfcb->sd, RtlLengthSecurityDescriptor(oldfcb->sd)); } fcb->atts = oldfcb->atts; le = oldfcb->extents.Flink; while (le != &oldfcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (!ext->ignore) { extent* ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG); if (!ext2) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } ext2->offset = ext->offset; ext2->datalen = ext->datalen; if (ext2->datalen > 0) RtlCopyMemory(&ext2->extent_data, &ext->extent_data, ext2->datalen); ext2->unique = false; ext2->ignore = false; ext2->inserted = true; if (ext->csum) { ULONG len; EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data; if (ext->extent_data.compression == BTRFS_COMPRESSION_NONE) len = (ULONG)ed2->num_bytes; else len = (ULONG)ed2->size; len = (len * sizeof(uint32_t)) >> Vcb->sector_shift; ext2->csum = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG); if (!ext2->csum) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(ext2->csum, ext->csum, len); } else ext2->csum = NULL; InsertTailList(&fcb->extents, &ext2->list_entry); } le = le->Flink; } le = oldfcb->hardlinks.Flink; while (le != &oldfcb->hardlinks) { hardlink *hl = CONTAINING_RECORD(le, hardlink, list_entry), *hl2; hl2 = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG); if (!hl2) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } hl2->parent = hl->parent; hl2->index = hl->index; hl2->name.Length = hl2->name.MaximumLength = hl->name.Length; hl2->name.Buffer = ExAllocatePoolWithTag(PagedPool, hl2->name.MaximumLength, ALLOC_TAG); if (!hl2->name.Buffer) { ERR("out of memory\n"); ExFreePool(hl2); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(hl2->name.Buffer, hl->name.Buffer, hl->name.Length); hl2->utf8.Length = hl2->utf8.MaximumLength = hl->utf8.Length; hl2->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl2->utf8.MaximumLength, ALLOC_TAG); if (!hl2->utf8.Buffer) { ERR("out of memory\n"); ExFreePool(hl2->name.Buffer); ExFreePool(hl2); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(hl2->utf8.Buffer, hl->utf8.Buffer, hl->utf8.Length); InsertTailList(&fcb->hardlinks, &hl2->list_entry); le = le->Flink; } if (oldfcb->reparse_xattr.Buffer && oldfcb->reparse_xattr.Length > 0) { fcb->reparse_xattr.Length = fcb->reparse_xattr.MaximumLength = oldfcb->reparse_xattr.Length; fcb->reparse_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->reparse_xattr.MaximumLength, ALLOC_TAG); if (!fcb->reparse_xattr.Buffer) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(fcb->reparse_xattr.Buffer, oldfcb->reparse_xattr.Buffer, fcb->reparse_xattr.Length); } if (oldfcb->ea_xattr.Buffer && oldfcb->ea_xattr.Length > 0) { fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = oldfcb->ea_xattr.Length; fcb->ea_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->ea_xattr.MaximumLength, ALLOC_TAG); if (!fcb->ea_xattr.Buffer) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(fcb->ea_xattr.Buffer, oldfcb->ea_xattr.Buffer, fcb->ea_xattr.Length); } fcb->prop_compression = oldfcb->prop_compression; le = oldfcb->xattrs.Flink; while (le != &oldfcb->xattrs) { xattr* xa = CONTAINING_RECORD(le, xattr, list_entry); if (xa->valuelen > 0) { xattr* xa2; xa2 = ExAllocatePoolWithTag(PagedPool, offsetof(xattr, data[0]) + xa->namelen + xa->valuelen, ALLOC_TAG); if (!xa2) { ERR("out of memory\n"); free_fcb(fcb); return STATUS_INSUFFICIENT_RESOURCES; } xa2->namelen = xa->namelen; xa2->valuelen = xa->valuelen; xa2->dirty = xa->dirty; memcpy(xa2->data, xa->data, xa->namelen + xa->valuelen); InsertTailList(&fcb->xattrs, &xa2->list_entry); } le = le->Flink; } end: *pfcb = fcb; return STATUS_SUCCESS; } typedef struct _move_entry { file_ref* fileref; fcb* dummyfcb; file_ref* dummyfileref; struct _move_entry* parent; LIST_ENTRY list_entry; } move_entry; static NTSTATUS add_children_to_move_list(device_extension* Vcb, move_entry* me, PIRP Irp) { NTSTATUS Status; LIST_ENTRY* le; ExAcquireResourceSharedLite(&me->fileref->fcb->nonpaged->dir_children_lock, true); le = me->fileref->fcb->dir_children_index.Flink; while (le != &me->fileref->fcb->dir_children_index) { dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index); file_ref* fr; move_entry* me2; Status = open_fileref_child(Vcb, me->fileref, &dc->name, true, true, dc->index == 0 ? true : false, PagedPool, &fr, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fileref_child returned %08lx\n", Status); ExReleaseResourceLite(&me->fileref->fcb->nonpaged->dir_children_lock); return Status; } me2 = ExAllocatePoolWithTag(PagedPool, sizeof(move_entry), ALLOC_TAG); if (!me2) { ERR("out of memory\n"); ExReleaseResourceLite(&me->fileref->fcb->nonpaged->dir_children_lock); return STATUS_INSUFFICIENT_RESOURCES; } me2->fileref = fr; me2->dummyfcb = NULL; me2->dummyfileref = NULL; me2->parent = me; InsertHeadList(&me->list_entry, &me2->list_entry); le = le->Flink; } ExReleaseResourceLite(&me->fileref->fcb->nonpaged->dir_children_lock); return STATUS_SUCCESS; } void remove_dir_child_from_hash_lists(fcb* fcb, dir_child* dc) { uint8_t c; c = dc->hash >> 24; if (fcb->hash_ptrs[c] == &dc->list_entry_hash) { if (dc->list_entry_hash.Flink == &fcb->dir_children_hash) fcb->hash_ptrs[c] = NULL; else { dir_child* dc2 = CONTAINING_RECORD(dc->list_entry_hash.Flink, dir_child, list_entry_hash); if (dc2->hash >> 24 == c) fcb->hash_ptrs[c] = &dc2->list_entry_hash; else fcb->hash_ptrs[c] = NULL; } } RemoveEntryList(&dc->list_entry_hash); c = dc->hash_uc >> 24; if (fcb->hash_ptrs_uc[c] == &dc->list_entry_hash_uc) { if (dc->list_entry_hash_uc.Flink == &fcb->dir_children_hash_uc) fcb->hash_ptrs_uc[c] = NULL; else { dir_child* dc2 = CONTAINING_RECORD(dc->list_entry_hash_uc.Flink, dir_child, list_entry_hash_uc); if (dc2->hash_uc >> 24 == c) fcb->hash_ptrs_uc[c] = &dc2->list_entry_hash_uc; else fcb->hash_ptrs_uc[c] = NULL; } } RemoveEntryList(&dc->list_entry_hash_uc); } static NTSTATUS create_directory_fcb(device_extension* Vcb, root* r, fcb* parfcb, fcb** pfcb) { NTSTATUS Status; fcb* fcb; SECURITY_SUBJECT_CONTEXT subjcont; PSID owner; BOOLEAN defaulted; LARGE_INTEGER time; BTRFS_TIME now; fcb = create_fcb(Vcb, PagedPool); if (!fcb) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } KeQuerySystemTime(&time); win_time_to_unix(time, &now); fcb->Vcb = Vcb; fcb->subvol = r; fcb->inode = InterlockedIncrement64(&r->lastinode); fcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&fcb->inode, sizeof(uint64_t)); fcb->type = BTRFS_TYPE_DIRECTORY; fcb->inode_item.generation = Vcb->superblock.generation; fcb->inode_item.transid = Vcb->superblock.generation; fcb->inode_item.st_nlink = 1; fcb->inode_item.st_mode = __S_IFDIR | inherit_mode(parfcb, true); fcb->inode_item.st_atime = fcb->inode_item.st_ctime = fcb->inode_item.st_mtime = fcb->inode_item.otime = now; fcb->inode_item.st_gid = GID_NOBODY; fcb->atts = get_file_attributes(Vcb, fcb->subvol, fcb->inode, fcb->type, false, true, NULL); SeCaptureSubjectContext(&subjcont); Status = SeAssignSecurity(parfcb->sd, NULL, (void**)&fcb->sd, true, &subjcont, IoGetFileObjectGenericMapping(), PagedPool); if (!NT_SUCCESS(Status)) { reap_fcb(fcb); ERR("SeAssignSecurity returned %08lx\n", Status); return Status; } if (!fcb->sd) { reap_fcb(fcb); ERR("SeAssignSecurity returned NULL security descriptor\n"); return STATUS_INTERNAL_ERROR; } Status = RtlGetOwnerSecurityDescriptor(fcb->sd, &owner, &defaulted); if (!NT_SUCCESS(Status)) { ERR("RtlGetOwnerSecurityDescriptor returned %08lx\n", Status); fcb->inode_item.st_uid = UID_NOBODY; fcb->sd_dirty = true; } else { fcb->inode_item.st_uid = sid_to_uid(owner); fcb->sd_dirty = fcb->inode_item.st_uid == UID_NOBODY; } find_gid(fcb, parfcb, &subjcont); fcb->inode_item_changed = true; fcb->Header.IsFastIoPossible = fast_io_possible(fcb); fcb->Header.AllocationSize.QuadPart = 0; fcb->Header.FileSize.QuadPart = 0; fcb->Header.ValidDataLength.QuadPart = 0; fcb->created = true; if (parfcb->inode_item.flags & BTRFS_INODE_COMPRESS) fcb->inode_item.flags |= BTRFS_INODE_COMPRESS; fcb->prop_compression = parfcb->prop_compression; fcb->prop_compression_changed = fcb->prop_compression != PropCompression_None; fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG); if (!fcb->hash_ptrs) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256); fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG); if (!fcb->hash_ptrs_uc) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256); acquire_fcb_lock_exclusive(Vcb); add_fcb_to_subvol(fcb); InsertTailList(&Vcb->all_fcbs, &fcb->list_entry_all); r->fcbs_version++; release_fcb_lock(Vcb); mark_fcb_dirty(fcb); *pfcb = fcb; return STATUS_SUCCESS; } void add_fcb_to_subvol(_In_ _Requires_exclusive_lock_held_(_Curr_->Vcb->fcb_lock) fcb* fcb) { LIST_ENTRY* lastle = NULL; uint32_t hash = fcb->hash; if (fcb->subvol->fcbs_ptrs[hash >> 24]) { LIST_ENTRY* le = fcb->subvol->fcbs_ptrs[hash >> 24]; while (le != &fcb->subvol->fcbs) { struct _fcb* fcb2 = CONTAINING_RECORD(le, struct _fcb, list_entry); if (fcb2->hash > hash) { lastle = le->Blink; break; } le = le->Flink; } } if (!lastle) { uint8_t c = hash >> 24; if (c != 0xff) { uint8_t d = c + 1; do { if (fcb->subvol->fcbs_ptrs[d]) { lastle = fcb->subvol->fcbs_ptrs[d]->Blink; break; } d++; } while (d != 0); } } if (lastle) { InsertHeadList(lastle, &fcb->list_entry); if (lastle == &fcb->subvol->fcbs || (CONTAINING_RECORD(lastle, struct _fcb, list_entry)->hash >> 24) != (hash >> 24)) fcb->subvol->fcbs_ptrs[hash >> 24] = &fcb->list_entry; } else { InsertTailList(&fcb->subvol->fcbs, &fcb->list_entry); if (fcb->list_entry.Blink == &fcb->subvol->fcbs || (CONTAINING_RECORD(fcb->list_entry.Blink, struct _fcb, list_entry)->hash >> 24) != (hash >> 24)) fcb->subvol->fcbs_ptrs[hash >> 24] = &fcb->list_entry; } } void remove_fcb_from_subvol(_In_ _Requires_exclusive_lock_held_(_Curr_->Vcb->fcb_lock) fcb* fcb) { uint8_t c = fcb->hash >> 24; if (fcb->subvol->fcbs_ptrs[c] == &fcb->list_entry) { if (fcb->list_entry.Flink != &fcb->subvol->fcbs && (CONTAINING_RECORD(fcb->list_entry.Flink, struct _fcb, list_entry)->hash >> 24) == c) fcb->subvol->fcbs_ptrs[c] = fcb->list_entry.Flink; else fcb->subvol->fcbs_ptrs[c] = NULL; } RemoveEntryList(&fcb->list_entry); } static NTSTATUS move_across_subvols(file_ref* fileref, ccb* ccb, file_ref* destdir, PANSI_STRING utf8, PUNICODE_STRING fnus, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; LIST_ENTRY move_list, *le; move_entry* me; LARGE_INTEGER time; BTRFS_TIME now; file_ref* origparent; // FIXME - make sure me->dummyfileref and me->dummyfcb get freed properly InitializeListHead(&move_list); KeQuerySystemTime(&time); win_time_to_unix(time, &now); acquire_fcb_lock_exclusive(fileref->fcb->Vcb); me = ExAllocatePoolWithTag(PagedPool, sizeof(move_entry), ALLOC_TAG); if (!me) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } origparent = fileref->parent; me->fileref = fileref; increase_fileref_refcount(me->fileref); me->dummyfcb = NULL; me->dummyfileref = NULL; me->parent = NULL; InsertTailList(&move_list, &me->list_entry); le = move_list.Flink; while (le != &move_list) { me = CONTAINING_RECORD(le, move_entry, list_entry); ExAcquireResourceSharedLite(me->fileref->fcb->Header.Resource, true); if (!me->fileref->fcb->ads && me->fileref->fcb->subvol == origparent->fcb->subvol) { Status = add_children_to_move_list(fileref->fcb->Vcb, me, Irp); if (!NT_SUCCESS(Status)) { ERR("add_children_to_move_list returned %08lx\n", Status); ExReleaseResourceLite(me->fileref->fcb->Header.Resource); goto end; } } ExReleaseResourceLite(me->fileref->fcb->Header.Resource); le = le->Flink; } send_notification_fileref(fileref, fileref->fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_REMOVED, NULL); // loop through list and create new inodes le = move_list.Flink; while (le != &move_list) { me = CONTAINING_RECORD(le, move_entry, list_entry); if (me->fileref->fcb->inode != SUBVOL_ROOT_INODE && me->fileref->fcb != fileref->fcb->Vcb->dummy_fcb) { if (!me->dummyfcb) { ULONG defda; ExAcquireResourceExclusiveLite(me->fileref->fcb->Header.Resource, true); Status = duplicate_fcb(me->fileref->fcb, &me->dummyfcb); if (!NT_SUCCESS(Status)) { ERR("duplicate_fcb returned %08lx\n", Status); ExReleaseResourceLite(me->fileref->fcb->Header.Resource); goto end; } me->dummyfcb->subvol = me->fileref->fcb->subvol; me->dummyfcb->inode = me->fileref->fcb->inode; me->dummyfcb->hash = me->fileref->fcb->hash; if (!me->dummyfcb->ads) { me->dummyfcb->sd_dirty = me->fileref->fcb->sd_dirty; me->dummyfcb->atts_changed = me->fileref->fcb->atts_changed; me->dummyfcb->atts_deleted = me->fileref->fcb->atts_deleted; me->dummyfcb->extents_changed = me->fileref->fcb->extents_changed; me->dummyfcb->reparse_xattr_changed = me->fileref->fcb->reparse_xattr_changed; me->dummyfcb->ea_changed = me->fileref->fcb->ea_changed; } me->dummyfcb->created = me->fileref->fcb->created; me->dummyfcb->deleted = me->fileref->fcb->deleted; mark_fcb_dirty(me->dummyfcb); if (!me->fileref->fcb->ads) { LIST_ENTRY* le2; me->fileref->fcb->subvol = destdir->fcb->subvol; me->fileref->fcb->inode = InterlockedIncrement64(&destdir->fcb->subvol->lastinode); me->fileref->fcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&me->fileref->fcb->inode, sizeof(uint64_t)); me->fileref->fcb->inode_item.st_nlink = 1; defda = get_file_attributes(me->fileref->fcb->Vcb, me->fileref->fcb->subvol, me->fileref->fcb->inode, me->fileref->fcb->type, me->fileref->dc && me->fileref->dc->name.Length >= sizeof(WCHAR) && me->fileref->dc->name.Buffer[0] == '.', true, Irp); me->fileref->fcb->sd_dirty = !!me->fileref->fcb->sd; me->fileref->fcb->atts_changed = defda != me->fileref->fcb->atts; me->fileref->fcb->extents_changed = !IsListEmpty(&me->fileref->fcb->extents); me->fileref->fcb->reparse_xattr_changed = !!me->fileref->fcb->reparse_xattr.Buffer; me->fileref->fcb->ea_changed = !!me->fileref->fcb->ea_xattr.Buffer; me->fileref->fcb->xattrs_changed = !IsListEmpty(&me->fileref->fcb->xattrs); me->fileref->fcb->inode_item_changed = true; le2 = me->fileref->fcb->xattrs.Flink; while (le2 != &me->fileref->fcb->xattrs) { xattr* xa = CONTAINING_RECORD(le2, xattr, list_entry); xa->dirty = true; le2 = le2->Flink; } if (le == move_list.Flink) { // first entry me->fileref->fcb->inode_item.transid = me->fileref->fcb->Vcb->superblock.generation; me->fileref->fcb->inode_item.sequence++; if (!ccb->user_set_change_time) me->fileref->fcb->inode_item.st_ctime = now; } le2 = me->fileref->fcb->extents.Flink; while (le2 != &me->fileref->fcb->extents) { extent* ext = CONTAINING_RECORD(le2, extent, list_entry); if (!ext->ignore && (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC)) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data; if (ed2->size != 0) { chunk* c = get_chunk_from_address(me->fileref->fcb->Vcb, ed2->address); if (!c) { ERR("get_chunk_from_address(%I64x) failed\n", ed2->address); } else { Status = update_changed_extent_ref(me->fileref->fcb->Vcb, c, ed2->address, ed2->size, me->fileref->fcb->subvol->id, me->fileref->fcb->inode, ext->offset - ed2->offset, 1, me->fileref->fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp); if (!NT_SUCCESS(Status)) { ERR("update_changed_extent_ref returned %08lx\n", Status); ExReleaseResourceLite(me->fileref->fcb->Header.Resource); goto end; } } } } le2 = le2->Flink; } add_fcb_to_subvol(me->dummyfcb); remove_fcb_from_subvol(me->fileref->fcb); add_fcb_to_subvol(me->fileref->fcb); } else { me->fileref->fcb->subvol = me->parent->fileref->fcb->subvol; me->fileref->fcb->inode = me->parent->fileref->fcb->inode; me->fileref->fcb->hash = me->parent->fileref->fcb->hash; // put stream after parent in FCB list InsertHeadList(&me->parent->fileref->fcb->list_entry, &me->fileref->fcb->list_entry); } me->fileref->fcb->created = true; InsertTailList(&me->fileref->fcb->Vcb->all_fcbs, &me->dummyfcb->list_entry_all); while (!IsListEmpty(&me->fileref->fcb->hardlinks)) { hardlink* hl = CONTAINING_RECORD(RemoveHeadList(&me->fileref->fcb->hardlinks), hardlink, list_entry); if (hl->name.Buffer) ExFreePool(hl->name.Buffer); if (hl->utf8.Buffer) ExFreePool(hl->utf8.Buffer); ExFreePool(hl); } me->fileref->fcb->inode_item_changed = true; mark_fcb_dirty(me->fileref->fcb); if ((!me->dummyfcb->ads && me->dummyfcb->inode_item.st_nlink > 1) || (me->dummyfcb->ads && me->parent->dummyfcb->inode_item.st_nlink > 1)) { LIST_ENTRY* le2 = le->Flink; while (le2 != &move_list) { move_entry* me2 = CONTAINING_RECORD(le2, move_entry, list_entry); if (me2->fileref->fcb == me->fileref->fcb && !me2->fileref->fcb->ads) { me2->dummyfcb = me->dummyfcb; InterlockedIncrement(&me->dummyfcb->refcount); } le2 = le2->Flink; } } ExReleaseResourceLite(me->fileref->fcb->Header.Resource); } else { ExAcquireResourceExclusiveLite(me->fileref->fcb->Header.Resource, true); me->fileref->fcb->inode_item.st_nlink++; me->fileref->fcb->inode_item_changed = true; ExReleaseResourceLite(me->fileref->fcb->Header.Resource); } } le = le->Flink; } fileref->fcb->subvol->root_item.ctransid = fileref->fcb->Vcb->superblock.generation; fileref->fcb->subvol->root_item.ctime = now; // loop through list and create new filerefs le = move_list.Flink; while (le != &move_list) { hardlink* hl; bool name_changed = false; me = CONTAINING_RECORD(le, move_entry, list_entry); me->dummyfileref = create_fileref(fileref->fcb->Vcb); if (!me->dummyfileref) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } if (me->fileref->fcb == me->fileref->fcb->Vcb->dummy_fcb) { root* r = me->parent ? me->parent->fileref->fcb->subvol : destdir->fcb->subvol; Status = create_directory_fcb(me->fileref->fcb->Vcb, r, me->fileref->parent->fcb, &me->fileref->fcb); if (!NT_SUCCESS(Status)) { ERR("create_directory_fcb returned %08lx\n", Status); goto end; } me->fileref->dc->key.obj_id = me->fileref->fcb->inode; me->fileref->dc->key.obj_type = TYPE_INODE_ITEM; me->dummyfileref->fcb = me->fileref->fcb->Vcb->dummy_fcb; } else if (me->fileref->fcb->inode == SUBVOL_ROOT_INODE) { me->dummyfileref->fcb = me->fileref->fcb; me->fileref->fcb->subvol->parent = le == move_list.Flink ? destdir->fcb->subvol->id : me->parent->fileref->fcb->subvol->id; } else me->dummyfileref->fcb = me->dummyfcb; InterlockedIncrement(&me->dummyfileref->fcb->refcount); me->dummyfileref->oldutf8 = me->fileref->oldutf8; me->dummyfileref->oldindex = me->fileref->dc->index; if (le == move_list.Flink && (me->fileref->dc->utf8.Length != utf8->Length || RtlCompareMemory(me->fileref->dc->utf8.Buffer, utf8->Buffer, utf8->Length) != utf8->Length)) name_changed = true; if (!me->dummyfileref->oldutf8.Buffer) { me->dummyfileref->oldutf8.Buffer = ExAllocatePoolWithTag(PagedPool, me->fileref->dc->utf8.Length, ALLOC_TAG); if (!me->dummyfileref->oldutf8.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(me->dummyfileref->oldutf8.Buffer, me->fileref->dc->utf8.Buffer, me->fileref->dc->utf8.Length); me->dummyfileref->oldutf8.Length = me->dummyfileref->oldutf8.MaximumLength = me->fileref->dc->utf8.Length; } me->dummyfileref->delete_on_close = me->fileref->delete_on_close; me->dummyfileref->deleted = me->fileref->deleted; me->dummyfileref->created = me->fileref->created; me->fileref->created = true; me->dummyfileref->parent = me->parent ? me->parent->dummyfileref : origparent; increase_fileref_refcount(me->dummyfileref->parent); ExAcquireResourceExclusiveLite(&me->dummyfileref->parent->fcb->nonpaged->dir_children_lock, true); InsertTailList(&me->dummyfileref->parent->children, &me->dummyfileref->list_entry); ExReleaseResourceLite(&me->dummyfileref->parent->fcb->nonpaged->dir_children_lock); if (me->dummyfileref->fcb->type == BTRFS_TYPE_DIRECTORY) me->dummyfileref->fcb->fileref = me->dummyfileref; if (!me->parent) { RemoveEntryList(&me->fileref->list_entry); increase_fileref_refcount(destdir); if (me->fileref->dc) { // remove from old parent ExAcquireResourceExclusiveLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock, true); RemoveEntryList(&me->fileref->dc->list_entry_index); remove_dir_child_from_hash_lists(me->fileref->parent->fcb, me->fileref->dc); ExReleaseResourceLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock); me->fileref->parent->fcb->inode_item.st_size -= me->fileref->dc->utf8.Length * 2; me->fileref->parent->fcb->inode_item.transid = me->fileref->fcb->Vcb->superblock.generation; me->fileref->parent->fcb->inode_item.sequence++; me->fileref->parent->fcb->inode_item.st_ctime = now; me->fileref->parent->fcb->inode_item.st_mtime = now; me->fileref->parent->fcb->inode_item_changed = true; mark_fcb_dirty(me->fileref->parent->fcb); if (name_changed) { ExFreePool(me->fileref->dc->utf8.Buffer); ExFreePool(me->fileref->dc->name.Buffer); ExFreePool(me->fileref->dc->name_uc.Buffer); me->fileref->dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8->Length, ALLOC_TAG); if (!me->fileref->dc->utf8.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } me->fileref->dc->utf8.Length = me->fileref->dc->utf8.MaximumLength = utf8->Length; RtlCopyMemory(me->fileref->dc->utf8.Buffer, utf8->Buffer, utf8->Length); me->fileref->dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, fnus->Length, ALLOC_TAG); if (!me->fileref->dc->name.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } me->fileref->dc->name.Length = me->fileref->dc->name.MaximumLength = fnus->Length; RtlCopyMemory(me->fileref->dc->name.Buffer, fnus->Buffer, fnus->Length); Status = RtlUpcaseUnicodeString(&fileref->dc->name_uc, &fileref->dc->name, true); if (!NT_SUCCESS(Status)) { ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); goto end; } me->fileref->dc->hash = calc_crc32c(0xffffffff, (uint8_t*)me->fileref->dc->name.Buffer, me->fileref->dc->name.Length); me->fileref->dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)me->fileref->dc->name_uc.Buffer, me->fileref->dc->name_uc.Length); } if (me->fileref->dc->key.obj_type == TYPE_INODE_ITEM) me->fileref->dc->key.obj_id = me->fileref->fcb->inode; // add to new parent ExAcquireResourceExclusiveLite(&destdir->fcb->nonpaged->dir_children_lock, true); if (IsListEmpty(&destdir->fcb->dir_children_index)) me->fileref->dc->index = 2; else { dir_child* dc2 = CONTAINING_RECORD(destdir->fcb->dir_children_index.Blink, dir_child, list_entry_index); me->fileref->dc->index = max(2, dc2->index + 1); } InsertTailList(&destdir->fcb->dir_children_index, &me->fileref->dc->list_entry_index); insert_dir_child_into_hash_lists(destdir->fcb, me->fileref->dc); ExReleaseResourceLite(&destdir->fcb->nonpaged->dir_children_lock); } free_fileref(me->fileref->parent); me->fileref->parent = destdir; ExAcquireResourceExclusiveLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock, true); InsertTailList(&me->fileref->parent->children, &me->fileref->list_entry); ExReleaseResourceLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock); 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); me->fileref->parent->fcb->inode_item.st_size += me->fileref->dc->utf8.Length * 2; 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); me->fileref->parent->fcb->inode_item.transid = me->fileref->fcb->Vcb->superblock.generation; me->fileref->parent->fcb->inode_item.sequence++; me->fileref->parent->fcb->inode_item.st_ctime = now; me->fileref->parent->fcb->inode_item.st_mtime = now; me->fileref->parent->fcb->inode_item_changed = true; mark_fcb_dirty(me->fileref->parent->fcb); } else { if (me->fileref->dc) { ExAcquireResourceExclusiveLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock, true); RemoveEntryList(&me->fileref->dc->list_entry_index); if (!me->fileref->fcb->ads) remove_dir_child_from_hash_lists(me->fileref->parent->fcb, me->fileref->dc); ExReleaseResourceLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock); ExAcquireResourceExclusiveLite(&me->parent->fileref->fcb->nonpaged->dir_children_lock, true); if (me->fileref->fcb->ads) InsertHeadList(&me->parent->fileref->fcb->dir_children_index, &me->fileref->dc->list_entry_index); else { if (me->fileref->fcb->inode != SUBVOL_ROOT_INODE) me->fileref->dc->key.obj_id = me->fileref->fcb->inode; if (IsListEmpty(&me->parent->fileref->fcb->dir_children_index)) me->fileref->dc->index = 2; else { dir_child* dc2 = CONTAINING_RECORD(me->parent->fileref->fcb->dir_children_index.Blink, dir_child, list_entry_index); me->fileref->dc->index = max(2, dc2->index + 1); } InsertTailList(&me->parent->fileref->fcb->dir_children_index, &me->fileref->dc->list_entry_index); insert_dir_child_into_hash_lists(me->parent->fileref->fcb, me->fileref->dc); } ExReleaseResourceLite(&me->parent->fileref->fcb->nonpaged->dir_children_lock); } } if (!me->dummyfileref->fcb->ads) { Status = delete_fileref(me->dummyfileref, NULL, false, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("delete_fileref returned %08lx\n", Status); goto end; } } if (me->fileref->fcb->inode_item.st_nlink > 1) { hl = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG); if (!hl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } hl->parent = me->fileref->parent->fcb->inode; hl->index = me->fileref->dc->index; hl->utf8.Length = hl->utf8.MaximumLength = me->fileref->dc->utf8.Length; hl->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl->utf8.MaximumLength, ALLOC_TAG); if (!hl->utf8.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(hl); goto end; } RtlCopyMemory(hl->utf8.Buffer, me->fileref->dc->utf8.Buffer, me->fileref->dc->utf8.Length); hl->name.Length = hl->name.MaximumLength = me->fileref->dc->name.Length; hl->name.Buffer = ExAllocatePoolWithTag(PagedPool, hl->name.MaximumLength, ALLOC_TAG); if (!hl->name.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(hl->utf8.Buffer); ExFreePool(hl); goto end; } RtlCopyMemory(hl->name.Buffer, me->fileref->dc->name.Buffer, me->fileref->dc->name.Length); InsertTailList(&me->fileref->fcb->hardlinks, &hl->list_entry); } mark_fileref_dirty(me->fileref); le = le->Flink; } // loop through, and only mark streams as deleted if their parent inodes are also deleted le = move_list.Flink; while (le != &move_list) { me = CONTAINING_RECORD(le, move_entry, list_entry); if (me->dummyfileref->fcb->ads && me->parent->dummyfileref->fcb->deleted) { Status = delete_fileref(me->dummyfileref, NULL, false, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("delete_fileref returned %08lx\n", Status); goto end; } } le = le->Flink; } destdir->fcb->subvol->root_item.ctransid = destdir->fcb->Vcb->superblock.generation; destdir->fcb->subvol->root_item.ctime = now; me = CONTAINING_RECORD(move_list.Flink, move_entry, list_entry); send_notification_fileref(fileref, fileref->fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL); send_notification_fileref(me->dummyfileref->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL); send_notification_fileref(fileref->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL); Status = STATUS_SUCCESS; end: while (!IsListEmpty(&move_list)) { le = RemoveHeadList(&move_list); me = CONTAINING_RECORD(le, move_entry, list_entry); if (me->dummyfcb) free_fcb(me->dummyfcb); if (me->dummyfileref) free_fileref(me->dummyfileref); free_fileref(me->fileref); ExFreePool(me); } destdir->fcb->subvol->fcbs_version++; fileref->fcb->subvol->fcbs_version++; release_fcb_lock(fileref->fcb->Vcb); return Status; } void insert_dir_child_into_hash_lists(fcb* fcb, dir_child* dc) { bool inserted; LIST_ENTRY* le; uint8_t c, d; c = dc->hash >> 24; inserted = false; d = c; do { le = fcb->hash_ptrs[d]; if (d == 0) break; d--; } while (!le); if (!le) le = fcb->dir_children_hash.Flink; while (le != &fcb->dir_children_hash) { dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_hash); if (dc2->hash > dc->hash) { InsertHeadList(le->Blink, &dc->list_entry_hash); inserted = true; break; } le = le->Flink; } if (!inserted) InsertTailList(&fcb->dir_children_hash, &dc->list_entry_hash); if (!fcb->hash_ptrs[c]) fcb->hash_ptrs[c] = &dc->list_entry_hash; else { dir_child* dc2 = CONTAINING_RECORD(fcb->hash_ptrs[c], dir_child, list_entry_hash); if (dc2->hash > dc->hash) fcb->hash_ptrs[c] = &dc->list_entry_hash; } c = dc->hash_uc >> 24; inserted = false; d = c; do { le = fcb->hash_ptrs_uc[d]; if (d == 0) break; d--; } while (!le); if (!le) le = fcb->dir_children_hash_uc.Flink; while (le != &fcb->dir_children_hash_uc) { dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_hash_uc); if (dc2->hash_uc > dc->hash_uc) { InsertHeadList(le->Blink, &dc->list_entry_hash_uc); inserted = true; break; } le = le->Flink; } if (!inserted) InsertTailList(&fcb->dir_children_hash_uc, &dc->list_entry_hash_uc); if (!fcb->hash_ptrs_uc[c]) fcb->hash_ptrs_uc[c] = &dc->list_entry_hash_uc; else { dir_child* dc2 = CONTAINING_RECORD(fcb->hash_ptrs_uc[c], dir_child, list_entry_hash_uc); if (dc2->hash_uc > dc->hash_uc) fcb->hash_ptrs_uc[c] = &dc->list_entry_hash_uc; } } static NTSTATUS rename_stream_to_file(device_extension* Vcb, file_ref* fileref, ccb* ccb, ULONG flags, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; file_ref* ofr; ANSI_STRING adsdata; dir_child* dc; fcb* dummyfcb; if (fileref->fcb->type != BTRFS_TYPE_FILE) return STATUS_INVALID_PARAMETER; if (!(flags & FILE_RENAME_IGNORE_READONLY_ATTRIBUTE) && fileref->parent->fcb->atts & FILE_ATTRIBUTE_READONLY) { WARN("trying to rename stream on readonly file\n"); return STATUS_ACCESS_DENIED; } if (Irp->RequestorMode == UserMode && ccb && !(ccb->access & DELETE)) { WARN("insufficient permissions\n"); return STATUS_ACCESS_DENIED; } if (!(flags & FILE_RENAME_REPLACE_IF_EXISTS)) // file will always exist return STATUS_OBJECT_NAME_COLLISION; // FIXME - POSIX overwrites of stream? ofr = fileref->parent; if (ofr->open_count > 0) { WARN("trying to overwrite open file\n"); return STATUS_ACCESS_DENIED; } if (ofr->fcb->inode_item.st_size > 0) { WARN("can only overwrite existing stream if it is zero-length\n"); return STATUS_INVALID_PARAMETER; } dummyfcb = create_fcb(Vcb, PagedPool); if (!dummyfcb) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } // copy parent fcb onto this one fileref->fcb->subvol = ofr->fcb->subvol; fileref->fcb->inode = ofr->fcb->inode; fileref->fcb->hash = ofr->fcb->hash; fileref->fcb->type = ofr->fcb->type; fileref->fcb->inode_item = ofr->fcb->inode_item; fileref->fcb->sd = ofr->fcb->sd; ofr->fcb->sd = NULL; fileref->fcb->deleted = ofr->fcb->deleted; fileref->fcb->atts = ofr->fcb->atts; fileref->fcb->reparse_xattr = ofr->fcb->reparse_xattr; ofr->fcb->reparse_xattr.Buffer = NULL; ofr->fcb->reparse_xattr.Length = ofr->fcb->reparse_xattr.MaximumLength = 0; fileref->fcb->ea_xattr = ofr->fcb->ea_xattr; ofr->fcb->ea_xattr.Buffer = NULL; ofr->fcb->ea_xattr.Length = ofr->fcb->ea_xattr.MaximumLength = 0; fileref->fcb->ealen = ofr->fcb->ealen; while (!IsListEmpty(&ofr->fcb->hardlinks)) { InsertTailList(&fileref->fcb->hardlinks, RemoveHeadList(&ofr->fcb->hardlinks)); } fileref->fcb->inode_item_changed = true; fileref->fcb->prop_compression = ofr->fcb->prop_compression; while (!IsListEmpty(&ofr->fcb->xattrs)) { InsertTailList(&fileref->fcb->xattrs, RemoveHeadList(&ofr->fcb->xattrs)); } fileref->fcb->marked_as_orphan = ofr->fcb->marked_as_orphan; fileref->fcb->case_sensitive = ofr->fcb->case_sensitive; fileref->fcb->case_sensitive_set = ofr->fcb->case_sensitive_set; while (!IsListEmpty(&ofr->fcb->dir_children_index)) { InsertTailList(&fileref->fcb->dir_children_index, RemoveHeadList(&ofr->fcb->dir_children_index)); } while (!IsListEmpty(&ofr->fcb->dir_children_hash)) { InsertTailList(&fileref->fcb->dir_children_hash, RemoveHeadList(&ofr->fcb->dir_children_hash)); } while (!IsListEmpty(&ofr->fcb->dir_children_hash_uc)) { InsertTailList(&fileref->fcb->dir_children_hash_uc, RemoveHeadList(&ofr->fcb->dir_children_hash_uc)); } fileref->fcb->hash_ptrs = ofr->fcb->hash_ptrs; fileref->fcb->hash_ptrs_uc = ofr->fcb->hash_ptrs_uc; ofr->fcb->hash_ptrs = NULL; ofr->fcb->hash_ptrs_uc = NULL; fileref->fcb->sd_dirty = ofr->fcb->sd_dirty; fileref->fcb->sd_deleted = ofr->fcb->sd_deleted; fileref->fcb->atts_changed = ofr->fcb->atts_changed; fileref->fcb->atts_deleted = ofr->fcb->atts_deleted; fileref->fcb->extents_changed = true; fileref->fcb->reparse_xattr_changed = ofr->fcb->reparse_xattr_changed; fileref->fcb->ea_changed = ofr->fcb->ea_changed; fileref->fcb->prop_compression_changed = ofr->fcb->prop_compression_changed; fileref->fcb->xattrs_changed = ofr->fcb->xattrs_changed; fileref->fcb->created = ofr->fcb->created; fileref->fcb->ads = false; if (fileref->fcb->adsxattr.Buffer) { ExFreePool(fileref->fcb->adsxattr.Buffer); fileref->fcb->adsxattr.Length = fileref->fcb->adsxattr.MaximumLength = 0; fileref->fcb->adsxattr.Buffer = NULL; } adsdata = fileref->fcb->adsdata; fileref->fcb->adsdata.Buffer = NULL; fileref->fcb->adsdata.Length = fileref->fcb->adsdata.MaximumLength = 0; acquire_fcb_lock_exclusive(Vcb); RemoveEntryList(&fileref->fcb->list_entry); InsertHeadList(ofr->fcb->list_entry.Blink, &fileref->fcb->list_entry); if (fileref->fcb->subvol->fcbs_ptrs[fileref->fcb->hash >> 24] == &ofr->fcb->list_entry) fileref->fcb->subvol->fcbs_ptrs[fileref->fcb->hash >> 24] = &fileref->fcb->list_entry; RemoveEntryList(&ofr->fcb->list_entry); release_fcb_lock(Vcb); ofr->fcb->list_entry.Flink = ofr->fcb->list_entry.Blink = NULL; mark_fcb_dirty(fileref->fcb); // mark old parent fcb so it gets ignored by flush_fcb ofr->fcb->created = true; ofr->fcb->deleted = true; mark_fcb_dirty(ofr->fcb); // copy parent fileref onto this one fileref->oldutf8 = ofr->oldutf8; ofr->oldutf8.Buffer = NULL; ofr->oldutf8.Length = ofr->oldutf8.MaximumLength = 0; fileref->oldindex = ofr->oldindex; fileref->delete_on_close = ofr->delete_on_close; fileref->posix_delete = ofr->posix_delete; fileref->deleted = ofr->deleted; fileref->created = ofr->created; fileref->parent = ofr->parent; RemoveEntryList(&fileref->list_entry); InsertHeadList(ofr->list_entry.Blink, &fileref->list_entry); RemoveEntryList(&ofr->list_entry); ofr->list_entry.Flink = ofr->list_entry.Blink = NULL; while (!IsListEmpty(&ofr->children)) { file_ref* fr = CONTAINING_RECORD(RemoveHeadList(&ofr->children), file_ref, list_entry); free_fileref(fr->parent); fr->parent = fileref; InterlockedIncrement(&fileref->refcount); InsertTailList(&fileref->children, &fr->list_entry); } dc = fileref->dc; fileref->dc = ofr->dc; fileref->dc->fileref = fileref; mark_fileref_dirty(fileref); // mark old parent fileref so it gets ignored by flush_fileref ofr->created = true; ofr->deleted = true; // write file data fileref->fcb->inode_item.st_size = adsdata.Length; if (adsdata.Length > 0) { bool make_inline = adsdata.Length <= Vcb->options.max_inline; if (make_inline) { EXTENT_DATA* ed = ExAllocatePoolWithTag(PagedPool, (uint16_t)(offsetof(EXTENT_DATA, data[0]) + adsdata.Length), ALLOC_TAG); if (!ed) { ERR("out of memory\n"); ExFreePool(adsdata.Buffer); reap_fcb(dummyfcb); return STATUS_INSUFFICIENT_RESOURCES; } ed->generation = Vcb->superblock.generation; ed->decoded_size = adsdata.Length; ed->compression = BTRFS_COMPRESSION_NONE; ed->encryption = BTRFS_ENCRYPTION_NONE; ed->encoding = BTRFS_ENCODING_NONE; ed->type = EXTENT_TYPE_INLINE; RtlCopyMemory(ed->data, adsdata.Buffer, adsdata.Length); ExFreePool(adsdata.Buffer); Status = add_extent_to_fcb(fileref->fcb, 0, ed, (uint16_t)(offsetof(EXTENT_DATA, data[0]) + adsdata.Length), false, NULL, rollback); if (!NT_SUCCESS(Status)) { ERR("add_extent_to_fcb returned %08lx\n", Status); ExFreePool(ed); reap_fcb(dummyfcb); return Status; } ExFreePool(ed); } else if (adsdata.Length & (Vcb->superblock.sector_size - 1)) { char* newbuf = ExAllocatePoolWithTag(PagedPool, (uint16_t)sector_align(adsdata.Length, Vcb->superblock.sector_size), ALLOC_TAG); if (!newbuf) { ERR("out of memory\n"); ExFreePool(adsdata.Buffer); reap_fcb(dummyfcb); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newbuf, adsdata.Buffer, adsdata.Length); RtlZeroMemory(newbuf + adsdata.Length, (uint16_t)(sector_align(adsdata.Length, Vcb->superblock.sector_size) - adsdata.Length)); ExFreePool(adsdata.Buffer); adsdata.Buffer = newbuf; adsdata.Length = adsdata.MaximumLength = (uint16_t)sector_align(adsdata.Length, Vcb->superblock.sector_size); } if (!make_inline) { Status = do_write_file(fileref->fcb, 0, adsdata.Length, adsdata.Buffer, Irp, false, 0, rollback); if (!NT_SUCCESS(Status)) { ERR("do_write_file returned %08lx\n", Status); ExFreePool(adsdata.Buffer); reap_fcb(dummyfcb); return Status; } ExFreePool(adsdata.Buffer); } fileref->fcb->inode_item.st_blocks = adsdata.Length; fileref->fcb->inode_item_changed = true; } RemoveEntryList(&dc->list_entry_index); if (dc->utf8.Buffer) ExFreePool(dc->utf8.Buffer); if (dc->name.Buffer) ExFreePool(dc->name.Buffer); if (dc->name_uc.Buffer) ExFreePool(dc->name_uc.Buffer); ExFreePool(dc); // FIXME - csums? // add dummy deleted xattr with old name dummyfcb->Vcb = Vcb; dummyfcb->subvol = fileref->fcb->subvol; dummyfcb->inode = fileref->fcb->inode; dummyfcb->hash = fileref->fcb->hash; dummyfcb->adsxattr = fileref->fcb->adsxattr; dummyfcb->adshash = fileref->fcb->adshash; dummyfcb->ads = true; dummyfcb->deleted = true; acquire_fcb_lock_exclusive(Vcb); add_fcb_to_subvol(dummyfcb); InsertTailList(&Vcb->all_fcbs, &dummyfcb->list_entry_all); dummyfcb->subvol->fcbs_version++; release_fcb_lock(Vcb); // FIXME - dummyfileref as well? mark_fcb_dirty(dummyfcb); free_fcb(dummyfcb); return STATUS_SUCCESS; } static NTSTATUS rename_stream(device_extension* Vcb, file_ref* fileref, ccb* ccb, FILE_RENAME_INFORMATION_EX* fri, ULONG flags, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; UNICODE_STRING fn; file_ref* sf = NULL; uint16_t newmaxlen; ULONG utf8len; ANSI_STRING utf8; UNICODE_STRING utf16, utf16uc; ANSI_STRING adsxattr; uint32_t crc32; fcb* dummyfcb; static const WCHAR datasuf[] = L":$DATA"; static const char xapref[] = "user."; if (!fileref) { ERR("fileref not set\n"); return STATUS_INVALID_PARAMETER; } if (!fileref->parent) { ERR("fileref->parent not set\n"); return STATUS_INVALID_PARAMETER; } if (fri->FileNameLength < sizeof(WCHAR)) { WARN("filename too short\n"); return STATUS_OBJECT_NAME_INVALID; } if (fri->FileName[0] != ':') { WARN("destination filename must begin with a colon\n"); return STATUS_INVALID_PARAMETER; } if (Irp->RequestorMode == UserMode && ccb && !(ccb->access & DELETE)) { WARN("insufficient permissions\n"); return STATUS_ACCESS_DENIED; } fn.Buffer = &fri->FileName[1]; fn.Length = fn.MaximumLength = (USHORT)(fri->FileNameLength - sizeof(WCHAR)); // remove :$DATA suffix if (fn.Length >= sizeof(datasuf) - sizeof(WCHAR) && RtlCompareMemory(&fn.Buffer[(fn.Length - sizeof(datasuf) + sizeof(WCHAR))/sizeof(WCHAR)], datasuf, sizeof(datasuf) - sizeof(WCHAR)) == sizeof(datasuf) - sizeof(WCHAR)) fn.Length -= sizeof(datasuf) - sizeof(WCHAR); if (fn.Length == 0) return rename_stream_to_file(Vcb, fileref, ccb, flags, Irp, rollback); Status = check_file_name_valid(&fn, false, true); if (!NT_SUCCESS(Status)) { WARN("invalid stream name %.*S\n", (int)(fn.Length / sizeof(WCHAR)), fn.Buffer); return Status; } if (!(flags & FILE_RENAME_IGNORE_READONLY_ATTRIBUTE) && fileref->parent->fcb->atts & FILE_ATTRIBUTE_READONLY) { WARN("trying to rename stream on readonly file\n"); return STATUS_ACCESS_DENIED; } Status = open_fileref_child(Vcb, fileref->parent, &fn, fileref->parent->fcb->case_sensitive, true, true, PagedPool, &sf, Irp); if (Status != STATUS_OBJECT_NAME_NOT_FOUND) { if (Status == STATUS_SUCCESS) { if (fileref == sf || sf->deleted) { free_fileref(sf); sf = NULL; } else { if (!(flags & FILE_RENAME_REPLACE_IF_EXISTS)) { Status = STATUS_OBJECT_NAME_COLLISION; goto end; } // FIXME - POSIX overwrites of stream? if (sf->open_count > 0) { WARN("trying to overwrite open file\n"); Status = STATUS_ACCESS_DENIED; goto end; } if (sf->fcb->adsdata.Length > 0) { WARN("can only overwrite existing stream if it is zero-length\n"); Status = STATUS_INVALID_PARAMETER; goto end; } Status = delete_fileref(sf, NULL, false, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("delete_fileref returned %08lx\n", Status); goto end; } } } else { ERR("open_fileref_child returned %08lx\n", Status); goto end; } } Status = utf16_to_utf8(NULL, 0, &utf8len, fn.Buffer, fn.Length); if (!NT_SUCCESS(Status)) goto end; utf8.MaximumLength = utf8.Length = (uint16_t)utf8len; utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG); if (!utf8.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } Status = utf16_to_utf8(utf8.Buffer, utf8len, &utf8len, fn.Buffer, fn.Length); if (!NT_SUCCESS(Status)) { ExFreePool(utf8.Buffer); goto end; } adsxattr.Length = adsxattr.MaximumLength = sizeof(xapref) - 1 + utf8.Length; adsxattr.Buffer = ExAllocatePoolWithTag(PagedPool, adsxattr.MaximumLength, ALLOC_TAG); if (!adsxattr.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(utf8.Buffer); goto end; } RtlCopyMemory(adsxattr.Buffer, xapref, sizeof(xapref) - 1); RtlCopyMemory(&adsxattr.Buffer[sizeof(xapref) - 1], utf8.Buffer, utf8.Length); // don't allow if it's one of our reserved names if ((adsxattr.Length == sizeof(EA_DOSATTRIB) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_DOSATTRIB, adsxattr.Length) == adsxattr.Length) || (adsxattr.Length == sizeof(EA_EA) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_EA, adsxattr.Length) == adsxattr.Length) || (adsxattr.Length == sizeof(EA_REPARSE) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_REPARSE, adsxattr.Length) == adsxattr.Length) || (adsxattr.Length == sizeof(EA_CASE_SENSITIVE) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_CASE_SENSITIVE, adsxattr.Length) == adsxattr.Length)) { Status = STATUS_OBJECT_NAME_INVALID; ExFreePool(utf8.Buffer); ExFreePool(adsxattr.Buffer); goto end; } utf16.Length = utf16.MaximumLength = fn.Length; utf16.Buffer = ExAllocatePoolWithTag(PagedPool, utf16.MaximumLength, ALLOC_TAG); if (!utf16.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(utf8.Buffer); ExFreePool(adsxattr.Buffer); goto end; } RtlCopyMemory(utf16.Buffer, fn.Buffer, fn.Length); newmaxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) - offsetof(DIR_ITEM, name[0]); if (newmaxlen < adsxattr.Length) { WARN("cannot rename as data too long\n"); Status = STATUS_INVALID_PARAMETER; ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(adsxattr.Buffer); goto end; } newmaxlen -= adsxattr.Length; if (newmaxlen < fileref->fcb->adsdata.Length) { WARN("cannot rename as data too long\n"); Status = STATUS_INVALID_PARAMETER; ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(adsxattr.Buffer); goto end; } Status = RtlUpcaseUnicodeString(&utf16uc, &fn, true); if (!NT_SUCCESS(Status)) { ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(adsxattr.Buffer); goto end; } // add dummy deleted xattr with old name dummyfcb = create_fcb(Vcb, PagedPool); if (!dummyfcb) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(utf16uc.Buffer); ExFreePool(adsxattr.Buffer); goto end; } dummyfcb->Vcb = Vcb; dummyfcb->subvol = fileref->fcb->subvol; dummyfcb->inode = fileref->fcb->inode; dummyfcb->hash = fileref->fcb->hash; dummyfcb->adsxattr = fileref->fcb->adsxattr; dummyfcb->adshash = fileref->fcb->adshash; dummyfcb->ads = true; dummyfcb->deleted = true; acquire_fcb_lock_exclusive(Vcb); add_fcb_to_subvol(dummyfcb); InsertTailList(&Vcb->all_fcbs, &dummyfcb->list_entry_all); dummyfcb->subvol->fcbs_version++; release_fcb_lock(Vcb); mark_fcb_dirty(dummyfcb); free_fcb(dummyfcb); // change fcb values fileref->dc->utf8 = utf8; fileref->dc->name = utf16; fileref->dc->name_uc = utf16uc; crc32 = calc_crc32c(0xfffffffe, (uint8_t*)adsxattr.Buffer, adsxattr.Length); fileref->fcb->adsxattr = adsxattr; fileref->fcb->adshash = crc32; fileref->fcb->adsmaxlen = newmaxlen; fileref->fcb->created = true; mark_fcb_dirty(fileref->fcb); Status = STATUS_SUCCESS; end: if (sf) free_fileref(sf); return Status; } static NTSTATUS rename_file_to_stream(device_extension* Vcb, file_ref* fileref, ccb* ccb, FILE_RENAME_INFORMATION_EX* fri, ULONG flags, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; UNICODE_STRING fn; file_ref* sf = NULL; uint16_t newmaxlen; ULONG utf8len; ANSI_STRING utf8; UNICODE_STRING utf16, utf16uc; ANSI_STRING adsxattr, adsdata; uint32_t crc32; fcb* dummyfcb; file_ref* dummyfileref; dir_child* dc; LIST_ENTRY* le; static const WCHAR datasuf[] = L":$DATA"; static const char xapref[] = "user."; if (!fileref) { ERR("fileref not set\n"); return STATUS_INVALID_PARAMETER; } if (fri->FileNameLength < sizeof(WCHAR)) { WARN("filename too short\n"); return STATUS_OBJECT_NAME_INVALID; } if (fri->FileName[0] != ':') { WARN("destination filename must begin with a colon\n"); return STATUS_INVALID_PARAMETER; } if (Irp->RequestorMode == UserMode && ccb && !(ccb->access & DELETE)) { WARN("insufficient permissions\n"); return STATUS_ACCESS_DENIED; } if (fileref->fcb->type != BTRFS_TYPE_FILE) return STATUS_INVALID_PARAMETER; fn.Buffer = &fri->FileName[1]; fn.Length = fn.MaximumLength = (USHORT)(fri->FileNameLength - sizeof(WCHAR)); // remove :$DATA suffix if (fn.Length >= sizeof(datasuf) - sizeof(WCHAR) && RtlCompareMemory(&fn.Buffer[(fn.Length - sizeof(datasuf) + sizeof(WCHAR))/sizeof(WCHAR)], datasuf, sizeof(datasuf) - sizeof(WCHAR)) == sizeof(datasuf) - sizeof(WCHAR)) fn.Length -= sizeof(datasuf) - sizeof(WCHAR); if (fn.Length == 0) { WARN("not allowing overwriting file with itself\n"); return STATUS_INVALID_PARAMETER; } Status = check_file_name_valid(&fn, false, true); if (!NT_SUCCESS(Status)) { WARN("invalid stream name %.*S\n", (int)(fn.Length / sizeof(WCHAR)), fn.Buffer); return Status; } if (!(flags & FILE_RENAME_IGNORE_READONLY_ATTRIBUTE) && fileref->fcb->atts & FILE_ATTRIBUTE_READONLY) { WARN("trying to rename stream on readonly file\n"); return STATUS_ACCESS_DENIED; } Status = open_fileref_child(Vcb, fileref, &fn, fileref->fcb->case_sensitive, true, true, PagedPool, &sf, Irp); if (Status != STATUS_OBJECT_NAME_NOT_FOUND) { if (Status == STATUS_SUCCESS) { if (fileref == sf || sf->deleted) { free_fileref(sf); sf = NULL; } else { if (!(flags & FILE_RENAME_REPLACE_IF_EXISTS)) { Status = STATUS_OBJECT_NAME_COLLISION; goto end; } // FIXME - POSIX overwrites of stream? if (sf->open_count > 0) { WARN("trying to overwrite open file\n"); Status = STATUS_ACCESS_DENIED; goto end; } if (sf->fcb->adsdata.Length > 0) { WARN("can only overwrite existing stream if it is zero-length\n"); Status = STATUS_INVALID_PARAMETER; goto end; } Status = delete_fileref(sf, NULL, false, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("delete_fileref returned %08lx\n", Status); goto end; } } } else { ERR("open_fileref_child returned %08lx\n", Status); goto end; } } Status = utf16_to_utf8(NULL, 0, &utf8len, fn.Buffer, fn.Length); if (!NT_SUCCESS(Status)) goto end; utf8.MaximumLength = utf8.Length = (uint16_t)utf8len; utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG); if (!utf8.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } Status = utf16_to_utf8(utf8.Buffer, utf8len, &utf8len, fn.Buffer, fn.Length); if (!NT_SUCCESS(Status)) { ExFreePool(utf8.Buffer); goto end; } adsxattr.Length = adsxattr.MaximumLength = sizeof(xapref) - 1 + utf8.Length; adsxattr.Buffer = ExAllocatePoolWithTag(PagedPool, adsxattr.MaximumLength, ALLOC_TAG); if (!adsxattr.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(utf8.Buffer); goto end; } RtlCopyMemory(adsxattr.Buffer, xapref, sizeof(xapref) - 1); RtlCopyMemory(&adsxattr.Buffer[sizeof(xapref) - 1], utf8.Buffer, utf8.Length); // don't allow if it's one of our reserved names if ((adsxattr.Length == sizeof(EA_DOSATTRIB) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_DOSATTRIB, adsxattr.Length) == adsxattr.Length) || (adsxattr.Length == sizeof(EA_EA) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_EA, adsxattr.Length) == adsxattr.Length) || (adsxattr.Length == sizeof(EA_REPARSE) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_REPARSE, adsxattr.Length) == adsxattr.Length) || (adsxattr.Length == sizeof(EA_CASE_SENSITIVE) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_CASE_SENSITIVE, adsxattr.Length) == adsxattr.Length)) { Status = STATUS_OBJECT_NAME_INVALID; ExFreePool(utf8.Buffer); ExFreePool(adsxattr.Buffer); goto end; } utf16.Length = utf16.MaximumLength = fn.Length; utf16.Buffer = ExAllocatePoolWithTag(PagedPool, utf16.MaximumLength, ALLOC_TAG); if (!utf16.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(utf8.Buffer); ExFreePool(adsxattr.Buffer); goto end; } RtlCopyMemory(utf16.Buffer, fn.Buffer, fn.Length); newmaxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) - offsetof(DIR_ITEM, name[0]); if (newmaxlen < adsxattr.Length) { WARN("cannot rename as data too long\n"); Status = STATUS_INVALID_PARAMETER; ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(adsxattr.Buffer); goto end; } newmaxlen -= adsxattr.Length; if (newmaxlen < fileref->fcb->inode_item.st_size) { WARN("cannot rename as data too long\n"); Status = STATUS_INVALID_PARAMETER; ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(adsxattr.Buffer); goto end; } Status = RtlUpcaseUnicodeString(&utf16uc, &fn, true); if (!NT_SUCCESS(Status)) { ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(adsxattr.Buffer); goto end; } // read existing file data if (fileref->fcb->inode_item.st_size > 0) { ULONG bytes_read; adsdata.Length = adsdata.MaximumLength = (uint16_t)fileref->fcb->inode_item.st_size; adsdata.Buffer = ExAllocatePoolWithTag(PagedPool, adsdata.MaximumLength, ALLOC_TAG); if (!adsdata.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(utf16uc.Buffer); ExFreePool(adsxattr.Buffer); goto end; } Status = read_file(fileref->fcb, (uint8_t*)adsdata.Buffer, 0, adsdata.Length, &bytes_read, Irp); if (!NT_SUCCESS(Status)) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(utf16uc.Buffer); ExFreePool(adsxattr.Buffer); ExFreePool(adsdata.Buffer); goto end; } if (bytes_read < fileref->fcb->inode_item.st_size) { ERR("short read\n"); Status = STATUS_INTERNAL_ERROR; ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(utf16uc.Buffer); ExFreePool(adsxattr.Buffer); ExFreePool(adsdata.Buffer); goto end; } } else adsdata.Buffer = NULL; dc = ExAllocatePoolWithTag(PagedPool, sizeof(dir_child), ALLOC_TAG); if (!dc) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES;; ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(utf16uc.Buffer); ExFreePool(adsxattr.Buffer); if (adsdata.Buffer) ExFreePool(adsdata.Buffer); goto end; } // add dummy deleted fcb with old name Status = duplicate_fcb(fileref->fcb, &dummyfcb); if (!NT_SUCCESS(Status)) { ERR("duplicate_fcb returned %08lx\n", Status); ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(utf16uc.Buffer); ExFreePool(adsxattr.Buffer); if (adsdata.Buffer) ExFreePool(adsdata.Buffer); ExFreePool(dc); goto end; } dummyfileref = create_fileref(Vcb); if (!dummyfileref) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(utf16uc.Buffer); ExFreePool(adsxattr.Buffer); if (adsdata.Buffer) ExFreePool(adsdata.Buffer); ExFreePool(dc); reap_fcb(dummyfcb); goto end; } dummyfileref->fcb = dummyfcb; dummyfcb->Vcb = Vcb; dummyfcb->subvol = fileref->fcb->subvol; dummyfcb->inode = fileref->fcb->inode; dummyfcb->hash = fileref->fcb->hash; if (fileref->fcb->inode_item.st_size > 0) { Status = excise_extents(Vcb, dummyfcb, 0, sector_align(fileref->fcb->inode_item.st_size, Vcb->superblock.sector_size), Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); ExFreePool(utf8.Buffer); ExFreePool(utf16.Buffer); ExFreePool(utf16uc.Buffer); ExFreePool(adsxattr.Buffer); ExFreePool(adsdata.Buffer); ExFreePool(dc); reap_fileref(Vcb, dummyfileref); reap_fcb(dummyfcb); goto end; } dummyfcb->inode_item.st_size = 0; dummyfcb->Header.AllocationSize.QuadPart = 0; dummyfcb->Header.FileSize.QuadPart = 0; dummyfcb->Header.ValidDataLength.QuadPart = 0; } dummyfcb->hash_ptrs = fileref->fcb->hash_ptrs; dummyfcb->hash_ptrs_uc = fileref->fcb->hash_ptrs_uc; dummyfcb->created = fileref->fcb->created; le = fileref->fcb->extents.Flink; while (le != &fileref->fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); ext->ignore = true; le = le->Flink; } while (!IsListEmpty(&fileref->fcb->dir_children_index)) { InsertTailList(&dummyfcb->dir_children_index, RemoveHeadList(&fileref->fcb->dir_children_index)); } while (!IsListEmpty(&fileref->fcb->dir_children_hash)) { InsertTailList(&dummyfcb->dir_children_hash, RemoveHeadList(&fileref->fcb->dir_children_hash)); } while (!IsListEmpty(&fileref->fcb->dir_children_hash_uc)) { InsertTailList(&dummyfcb->dir_children_hash_uc, RemoveHeadList(&fileref->fcb->dir_children_hash_uc)); } InsertTailList(&Vcb->all_fcbs, &dummyfcb->list_entry_all); InsertHeadList(fileref->fcb->list_entry.Blink, &dummyfcb->list_entry); if (fileref->fcb->subvol->fcbs_ptrs[dummyfcb->hash >> 24] == &fileref->fcb->list_entry) fileref->fcb->subvol->fcbs_ptrs[dummyfcb->hash >> 24] = &dummyfcb->list_entry; RemoveEntryList(&fileref->fcb->list_entry); fileref->fcb->list_entry.Flink = fileref->fcb->list_entry.Blink = NULL; mark_fcb_dirty(dummyfcb); // create dummy fileref dummyfileref->oldutf8 = fileref->oldutf8; dummyfileref->oldindex = fileref->oldindex; dummyfileref->delete_on_close = fileref->delete_on_close; dummyfileref->posix_delete = fileref->posix_delete; dummyfileref->deleted = fileref->deleted; dummyfileref->created = fileref->created; dummyfileref->parent = fileref->parent; while (!IsListEmpty(&fileref->children)) { file_ref* fr = CONTAINING_RECORD(RemoveHeadList(&fileref->children), file_ref, list_entry); free_fileref(fr->parent); fr->parent = dummyfileref; InterlockedIncrement(&dummyfileref->refcount); InsertTailList(&dummyfileref->children, &fr->list_entry); } InsertTailList(fileref->list_entry.Blink, &dummyfileref->list_entry); RemoveEntryList(&fileref->list_entry); InsertTailList(&dummyfileref->children, &fileref->list_entry); dummyfileref->dc = fileref->dc; dummyfileref->dc->fileref = dummyfileref; mark_fileref_dirty(dummyfileref); // change fcb values fileref->fcb->hash_ptrs = NULL; fileref->fcb->hash_ptrs_uc = NULL; fileref->fcb->ads = true; fileref->oldutf8.Length = fileref->oldutf8.MaximumLength = 0; fileref->oldutf8.Buffer = NULL; RtlZeroMemory(dc, sizeof(dir_child)); dc->utf8 = utf8; dc->name = utf16; dc->hash = calc_crc32c(0xffffffff, (uint8_t*)dc->name.Buffer, dc->name.Length); dc->name_uc = utf16uc; dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)dc->name_uc.Buffer, dc->name_uc.Length); dc->fileref = fileref; InsertTailList(&dummyfcb->dir_children_index, &dc->list_entry_index); fileref->dc = dc; fileref->parent = dummyfileref; crc32 = calc_crc32c(0xfffffffe, (uint8_t*)adsxattr.Buffer, adsxattr.Length); fileref->fcb->adsxattr = adsxattr; fileref->fcb->adshash = crc32; fileref->fcb->adsmaxlen = newmaxlen; fileref->fcb->adsdata = adsdata; fileref->fcb->created = true; mark_fcb_dirty(fileref->fcb); Status = STATUS_SUCCESS; end: if (sf) free_fileref(sf); return Status; } static NTSTATUS set_rename_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, PFILE_OBJECT tfo, bool ex) { FILE_RENAME_INFORMATION_EX* fri = Irp->AssociatedIrp.SystemBuffer; fcb* fcb = FileObject->FsContext; ccb* ccb = FileObject->FsContext2; file_ref *fileref = ccb ? ccb->fileref : NULL, *oldfileref = NULL, *related = NULL, *fr2 = NULL; WCHAR* fn; ULONG fnlen, utf8len, origutf8len; UNICODE_STRING fnus; ANSI_STRING utf8; NTSTATUS Status; LARGE_INTEGER time; BTRFS_TIME now; LIST_ENTRY rollback, *le; hardlink* hl; SECURITY_SUBJECT_CONTEXT subjcont; ACCESS_MASK access; ULONG flags; InitializeListHead(&rollback); if (ex) flags = fri->Flags; else flags = fri->ReplaceIfExists ? FILE_RENAME_REPLACE_IF_EXISTS : 0; TRACE("tfo = %p\n", tfo); TRACE("Flags = %lx\n", flags); TRACE("RootDirectory = %p\n", fri->RootDirectory); TRACE("FileName = %.*S\n", (int)(fri->FileNameLength / sizeof(WCHAR)), fri->FileName); fn = fri->FileName; fnlen = fri->FileNameLength / sizeof(WCHAR); if (!tfo) { if (!fileref || !fileref->parent) { ERR("no fileref set and no directory given\n"); return STATUS_INVALID_PARAMETER; } } else { LONG i; while (fnlen > 0 && (fri->FileName[fnlen - 1] == '/' || fri->FileName[fnlen - 1] == '\\')) { fnlen--; } if (fnlen == 0) return STATUS_INVALID_PARAMETER; for (i = fnlen - 1; i >= 0; i--) { if (fri->FileName[i] == '\\' || fri->FileName[i] == '/') { fn = &fri->FileName[i+1]; fnlen -= i + 1; break; } } } ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(&Vcb->fileref_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (fcb->inode == SUBVOL_ROOT_INODE && fcb->subvol->id == BTRFS_ROOT_FSTREE) { WARN("not allowing \\$Root to be renamed\n"); Status = STATUS_ACCESS_DENIED; goto end; } if (fcb->ads) { if (FileObject->SectionObjectPointer && FileObject->SectionObjectPointer->DataSectionObject) { IO_STATUS_BLOCK iosb; CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &iosb); if (!NT_SUCCESS(iosb.Status)) { ERR("CcFlushCache returned %08lx\n", iosb.Status); Status = iosb.Status; goto end; } } Status = rename_stream(Vcb, fileref, ccb, fri, flags, Irp, &rollback); goto end; } else if (fnlen >= 1 && fn[0] == ':') { if (FileObject->SectionObjectPointer && FileObject->SectionObjectPointer->DataSectionObject) { IO_STATUS_BLOCK iosb; CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &iosb); if (!NT_SUCCESS(iosb.Status)) { ERR("CcFlushCache returned %08lx\n", iosb.Status); Status = iosb.Status; goto end; } } Status = rename_file_to_stream(Vcb, fileref, ccb, fri, flags, Irp, &rollback); goto end; } fnus.Buffer = fn; fnus.Length = fnus.MaximumLength = (uint16_t)(fnlen * sizeof(WCHAR)); TRACE("fnus = %.*S\n", (int)(fnus.Length / sizeof(WCHAR)), fnus.Buffer); Status = check_file_name_valid(&fnus, false, false); if (!NT_SUCCESS(Status)) goto end; origutf8len = fileref->dc->utf8.Length; Status = utf16_to_utf8(NULL, 0, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR)); if (!NT_SUCCESS(Status)) goto end; utf8.MaximumLength = utf8.Length = (uint16_t)utf8len; utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG); if (!utf8.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } Status = utf16_to_utf8(utf8.Buffer, utf8len, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR)); if (!NT_SUCCESS(Status)) goto end; if (tfo && tfo->FsContext2) { struct _ccb* relatedccb = tfo->FsContext2; related = relatedccb->fileref; increase_fileref_refcount(related); } else if (fnus.Length >= sizeof(WCHAR) && fnus.Buffer[0] != '\\') { related = fileref->parent; increase_fileref_refcount(related); } Status = open_fileref(Vcb, &oldfileref, &fnus, related, false, NULL, NULL, PagedPool, ccb->case_sensitive, Irp); if (NT_SUCCESS(Status)) { TRACE("destination file already exists\n"); if (fileref != oldfileref && !oldfileref->deleted) { if (!(flags & FILE_RENAME_REPLACE_IF_EXISTS)) { Status = STATUS_OBJECT_NAME_COLLISION; goto end; } else if (fileref == oldfileref) { Status = STATUS_ACCESS_DENIED; goto end; } else if (!(flags & FILE_RENAME_POSIX_SEMANTICS) && (oldfileref->open_count > 0 || has_open_children(oldfileref)) && !oldfileref->deleted) { WARN("trying to overwrite open file\n"); Status = STATUS_ACCESS_DENIED; goto end; } else if (!(flags & FILE_RENAME_IGNORE_READONLY_ATTRIBUTE) && oldfileref->fcb->atts & FILE_ATTRIBUTE_READONLY) { WARN("trying to overwrite readonly file\n"); Status = STATUS_ACCESS_DENIED; goto end; } else if (oldfileref->fcb->type == BTRFS_TYPE_DIRECTORY) { WARN("trying to overwrite directory\n"); Status = STATUS_ACCESS_DENIED; goto end; } } if (fileref == oldfileref || oldfileref->deleted) { free_fileref(oldfileref); oldfileref = NULL; } } if (!related) { Status = open_fileref(Vcb, &related, &fnus, NULL, true, NULL, NULL, PagedPool, ccb->case_sensitive, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fileref returned %08lx\n", Status); goto end; } } if (related->fcb == Vcb->dummy_fcb) { Status = STATUS_ACCESS_DENIED; goto end; } SeCaptureSubjectContext(&subjcont); if (!SeAccessCheck(related->fcb->sd, &subjcont, false, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_ADD_SUBDIRECTORY : FILE_ADD_FILE, 0, NULL, IoGetFileObjectGenericMapping(), Irp->RequestorMode, &access, &Status)) { SeReleaseSubjectContext(&subjcont); TRACE("SeAccessCheck failed, returning %08lx\n", Status); goto end; } SeReleaseSubjectContext(&subjcont); if (has_open_children(fileref)) { WARN("trying to rename file with open children\n"); Status = STATUS_ACCESS_DENIED; goto end; } if (oldfileref) { SeCaptureSubjectContext(&subjcont); if (!SeAccessCheck(oldfileref->fcb->sd, &subjcont, false, DELETE, 0, NULL, IoGetFileObjectGenericMapping(), Irp->RequestorMode, &access, &Status)) { SeReleaseSubjectContext(&subjcont); TRACE("SeAccessCheck failed, returning %08lx\n", Status); goto end; } SeReleaseSubjectContext(&subjcont); if (oldfileref->open_count > 0 && flags & FILE_RENAME_POSIX_SEMANTICS) { oldfileref->delete_on_close = true; oldfileref->posix_delete = true; } Status = delete_fileref(oldfileref, NULL, oldfileref->open_count > 0 && flags & FILE_RENAME_POSIX_SEMANTICS, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("delete_fileref returned %08lx\n", Status); goto end; } } if (fileref->parent->fcb->subvol != related->fcb->subvol && (fileref->fcb->subvol == fileref->parent->fcb->subvol || fileref->fcb == Vcb->dummy_fcb)) { Status = move_across_subvols(fileref, ccb, related, &utf8, &fnus, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("move_across_subvols returned %08lx\n", Status); } goto end; } if (related == fileref->parent) { // keeping file in same directory UNICODE_STRING oldfn, newfn; USHORT name_offset; ULONG reqlen, oldutf8len; oldfn.Length = oldfn.MaximumLength = 0; Status = fileref_get_filename(fileref, &oldfn, &name_offset, &reqlen); if (Status != STATUS_BUFFER_OVERFLOW) { ERR("fileref_get_filename returned %08lx\n", Status); goto end; } oldfn.Buffer = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG); if (!oldfn.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } oldfn.MaximumLength = (uint16_t)reqlen; Status = fileref_get_filename(fileref, &oldfn, &name_offset, &reqlen); if (!NT_SUCCESS(Status)) { ERR("fileref_get_filename returned %08lx\n", Status); ExFreePool(oldfn.Buffer); goto end; } oldutf8len = fileref->dc->utf8.Length; if (!fileref->created && !fileref->oldutf8.Buffer) { fileref->oldutf8.Buffer = ExAllocatePoolWithTag(PagedPool, fileref->dc->utf8.Length, ALLOC_TAG); if (!fileref->oldutf8.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } fileref->oldutf8.Length = fileref->oldutf8.MaximumLength = fileref->dc->utf8.Length; RtlCopyMemory(fileref->oldutf8.Buffer, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length); } TRACE("renaming %.*S to %.*S\n", (int)(fileref->dc->name.Length / sizeof(WCHAR)), fileref->dc->name.Buffer, (int)(fnus.Length / sizeof(WCHAR)), fnus.Buffer); mark_fileref_dirty(fileref); if (fileref->dc) { ExAcquireResourceExclusiveLite(&fileref->parent->fcb->nonpaged->dir_children_lock, true); ExFreePool(fileref->dc->utf8.Buffer); ExFreePool(fileref->dc->name.Buffer); ExFreePool(fileref->dc->name_uc.Buffer); fileref->dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.Length, ALLOC_TAG); if (!fileref->dc->utf8.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock); ExFreePool(oldfn.Buffer); goto end; } fileref->dc->utf8.Length = fileref->dc->utf8.MaximumLength = utf8.Length; RtlCopyMemory(fileref->dc->utf8.Buffer, utf8.Buffer, utf8.Length); fileref->dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, fnus.Length, ALLOC_TAG); if (!fileref->dc->name.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock); ExFreePool(oldfn.Buffer); goto end; } fileref->dc->name.Length = fileref->dc->name.MaximumLength = fnus.Length; RtlCopyMemory(fileref->dc->name.Buffer, fnus.Buffer, fnus.Length); Status = RtlUpcaseUnicodeString(&fileref->dc->name_uc, &fileref->dc->name, true); if (!NT_SUCCESS(Status)) { ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock); ExFreePool(oldfn.Buffer); goto end; } remove_dir_child_from_hash_lists(fileref->parent->fcb, fileref->dc); fileref->dc->hash = calc_crc32c(0xffffffff, (uint8_t*)fileref->dc->name.Buffer, fileref->dc->name.Length); fileref->dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)fileref->dc->name_uc.Buffer, fileref->dc->name_uc.Length); insert_dir_child_into_hash_lists(fileref->parent->fcb, fileref->dc); ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock); } newfn.Length = newfn.MaximumLength = 0; Status = fileref_get_filename(fileref, &newfn, &name_offset, &reqlen); if (Status != STATUS_BUFFER_OVERFLOW) { ERR("fileref_get_filename returned %08lx\n", Status); ExFreePool(oldfn.Buffer); goto end; } newfn.Buffer = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG); if (!newfn.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(oldfn.Buffer); goto end; } newfn.MaximumLength = (uint16_t)reqlen; Status = fileref_get_filename(fileref, &newfn, &name_offset, &reqlen); if (!NT_SUCCESS(Status)) { ERR("fileref_get_filename returned %08lx\n", Status); ExFreePool(oldfn.Buffer); ExFreePool(newfn.Buffer); goto end; } KeQuerySystemTime(&time); win_time_to_unix(time, &now); if (fcb != Vcb->dummy_fcb && (fileref->parent->fcb->subvol == fcb->subvol || !is_subvol_readonly(fcb->subvol, Irp))) { fcb->inode_item.transid = Vcb->superblock.generation; fcb->inode_item.sequence++; if (!ccb->user_set_change_time) fcb->inode_item.st_ctime = now; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); } // update parent's INODE_ITEM related->fcb->inode_item.transid = Vcb->superblock.generation; TRACE("related->fcb->inode_item.st_size (inode %I64x) was %I64x\n", related->fcb->inode, related->fcb->inode_item.st_size); related->fcb->inode_item.st_size = related->fcb->inode_item.st_size + (2 * utf8.Length) - (2* oldutf8len); TRACE("related->fcb->inode_item.st_size (inode %I64x) now %I64x\n", related->fcb->inode, related->fcb->inode_item.st_size); related->fcb->inode_item.sequence++; related->fcb->inode_item.st_ctime = now; related->fcb->inode_item.st_mtime = now; related->fcb->inode_item_changed = true; mark_fcb_dirty(related->fcb); send_notification_fileref(related, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL); FsRtlNotifyFilterReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&oldfn, name_offset, NULL, NULL, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_RENAMED_OLD_NAME, NULL, NULL); FsRtlNotifyFilterReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&newfn, name_offset, NULL, NULL, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_RENAMED_NEW_NAME, NULL, NULL); ExFreePool(oldfn.Buffer); ExFreePool(newfn.Buffer); Status = STATUS_SUCCESS; goto end; } // We move files by moving the existing fileref to the new directory, and // replacing it with a dummy fileref with the same original values, but marked as deleted. send_notification_fileref(fileref, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_REMOVED, NULL); fr2 = create_fileref(Vcb); fr2->fcb = fileref->fcb; fr2->fcb->refcount++; fr2->oldutf8 = fileref->oldutf8; fr2->oldindex = fileref->dc->index; fr2->delete_on_close = fileref->delete_on_close; fr2->deleted = true; fr2->created = fileref->created; fr2->parent = fileref->parent; fr2->dc = NULL; if (!fr2->oldutf8.Buffer) { fr2->oldutf8.Buffer = ExAllocatePoolWithTag(PagedPool, fileref->dc->utf8.Length, ALLOC_TAG); if (!fr2->oldutf8.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(fr2->oldutf8.Buffer, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length); fr2->oldutf8.Length = fr2->oldutf8.MaximumLength = fileref->dc->utf8.Length; } if (fr2->fcb->type == BTRFS_TYPE_DIRECTORY) fr2->fcb->fileref = fr2; if (fileref->fcb->inode == SUBVOL_ROOT_INODE) fileref->fcb->subvol->parent = related->fcb->subvol->id; fileref->oldutf8.Length = fileref->oldutf8.MaximumLength = 0; fileref->oldutf8.Buffer = NULL; fileref->deleted = false; fileref->created = true; fileref->parent = related; ExAcquireResourceExclusiveLite(&fileref->parent->fcb->nonpaged->dir_children_lock, true); InsertHeadList(&fileref->list_entry, &fr2->list_entry); RemoveEntryList(&fileref->list_entry); ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock); mark_fileref_dirty(fr2); mark_fileref_dirty(fileref); if (fileref->dc) { // remove from old parent ExAcquireResourceExclusiveLite(&fr2->parent->fcb->nonpaged->dir_children_lock, true); RemoveEntryList(&fileref->dc->list_entry_index); remove_dir_child_from_hash_lists(fr2->parent->fcb, fileref->dc); ExReleaseResourceLite(&fr2->parent->fcb->nonpaged->dir_children_lock); if (fileref->dc->utf8.Length != utf8.Length || RtlCompareMemory(fileref->dc->utf8.Buffer, utf8.Buffer, utf8.Length) != utf8.Length) { // handle changed name ExFreePool(fileref->dc->utf8.Buffer); ExFreePool(fileref->dc->name.Buffer); ExFreePool(fileref->dc->name_uc.Buffer); fileref->dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.Length, ALLOC_TAG); if (!fileref->dc->utf8.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } fileref->dc->utf8.Length = fileref->dc->utf8.MaximumLength = utf8.Length; RtlCopyMemory(fileref->dc->utf8.Buffer, utf8.Buffer, utf8.Length); fileref->dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, fnus.Length, ALLOC_TAG); if (!fileref->dc->name.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } fileref->dc->name.Length = fileref->dc->name.MaximumLength = fnus.Length; RtlCopyMemory(fileref->dc->name.Buffer, fnus.Buffer, fnus.Length); Status = RtlUpcaseUnicodeString(&fileref->dc->name_uc, &fileref->dc->name, true); if (!NT_SUCCESS(Status)) { ERR("RtlUpcaseUnicodeString returned %08lx\n", Status); goto end; } fileref->dc->hash = calc_crc32c(0xffffffff, (uint8_t*)fileref->dc->name.Buffer, fileref->dc->name.Length); fileref->dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)fileref->dc->name_uc.Buffer, fileref->dc->name_uc.Length); } // add to new parent ExAcquireResourceExclusiveLite(&related->fcb->nonpaged->dir_children_lock, true); if (IsListEmpty(&related->fcb->dir_children_index)) fileref->dc->index = 2; else { dir_child* dc2 = CONTAINING_RECORD(related->fcb->dir_children_index.Blink, dir_child, list_entry_index); fileref->dc->index = max(2, dc2->index + 1); } InsertTailList(&related->fcb->dir_children_index, &fileref->dc->list_entry_index); insert_dir_child_into_hash_lists(related->fcb, fileref->dc); ExReleaseResourceLite(&related->fcb->nonpaged->dir_children_lock); } ExAcquireResourceExclusiveLite(&related->fcb->nonpaged->dir_children_lock, true); InsertTailList(&related->children, &fileref->list_entry); ExReleaseResourceLite(&related->fcb->nonpaged->dir_children_lock); if (fcb->inode_item.st_nlink > 1) { // add new hardlink entry to fcb hl = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG); if (!hl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } hl->parent = related->fcb->inode; hl->index = fileref->dc->index; hl->name.Length = hl->name.MaximumLength = fnus.Length; hl->name.Buffer = ExAllocatePoolWithTag(PagedPool, hl->name.MaximumLength, ALLOC_TAG); if (!hl->name.Buffer) { ERR("out of memory\n"); ExFreePool(hl); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(hl->name.Buffer, fnus.Buffer, fnus.Length); hl->utf8.Length = hl->utf8.MaximumLength = fileref->dc->utf8.Length; hl->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl->utf8.MaximumLength, ALLOC_TAG); if (!hl->utf8.Buffer) { ERR("out of memory\n"); ExFreePool(hl->name.Buffer); ExFreePool(hl); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(hl->utf8.Buffer, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length); InsertTailList(&fcb->hardlinks, &hl->list_entry); } // delete old hardlink entry from fcb le = fcb->hardlinks.Flink; while (le != &fcb->hardlinks) { hl = CONTAINING_RECORD(le, hardlink, list_entry); if (hl->parent == fr2->parent->fcb->inode && hl->index == fr2->oldindex) { RemoveEntryList(&hl->list_entry); if (hl->utf8.Buffer) ExFreePool(hl->utf8.Buffer); if (hl->name.Buffer) ExFreePool(hl->name.Buffer); ExFreePool(hl); break; } le = le->Flink; } // update inode's INODE_ITEM KeQuerySystemTime(&time); win_time_to_unix(time, &now); if (fcb != Vcb->dummy_fcb && (fileref->parent->fcb->subvol == fcb->subvol || !is_subvol_readonly(fcb->subvol, Irp))) { fcb->inode_item.transid = Vcb->superblock.generation; fcb->inode_item.sequence++; if (!ccb->user_set_change_time) fcb->inode_item.st_ctime = now; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); } // update new parent's INODE_ITEM related->fcb->inode_item.transid = Vcb->superblock.generation; TRACE("related->fcb->inode_item.st_size (inode %I64x) was %I64x\n", related->fcb->inode, related->fcb->inode_item.st_size); related->fcb->inode_item.st_size += 2 * utf8len; TRACE("related->fcb->inode_item.st_size (inode %I64x) now %I64x\n", related->fcb->inode, related->fcb->inode_item.st_size); related->fcb->inode_item.sequence++; related->fcb->inode_item.st_ctime = now; related->fcb->inode_item.st_mtime = now; related->fcb->inode_item_changed = true; mark_fcb_dirty(related->fcb); // update old parent's INODE_ITEM fr2->parent->fcb->inode_item.transid = Vcb->superblock.generation; TRACE("fr2->parent->fcb->inode_item.st_size (inode %I64x) was %I64x\n", fr2->parent->fcb->inode, fr2->parent->fcb->inode_item.st_size); fr2->parent->fcb->inode_item.st_size -= 2 * origutf8len; TRACE("fr2->parent->fcb->inode_item.st_size (inode %I64x) now %I64x\n", fr2->parent->fcb->inode, fr2->parent->fcb->inode_item.st_size); fr2->parent->fcb->inode_item.sequence++; fr2->parent->fcb->inode_item.st_ctime = now; fr2->parent->fcb->inode_item.st_mtime = now; free_fileref(fr2); fr2->parent->fcb->inode_item_changed = true; mark_fcb_dirty(fr2->parent->fcb); send_notification_fileref(fileref, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL); send_notification_fileref(related, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL); send_notification_fileref(fr2->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL); Status = STATUS_SUCCESS; end: if (oldfileref) free_fileref(oldfileref); if (!NT_SUCCESS(Status) && related) free_fileref(related); if (!NT_SUCCESS(Status) && fr2) free_fileref(fr2); if (NT_SUCCESS(Status)) clear_rollback(&rollback); else do_rollback(Vcb, &rollback); ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&Vcb->fileref_lock); ExReleaseResourceLite(&Vcb->tree_lock); return Status; } NTSTATUS stream_set_end_of_file_information(device_extension* Vcb, uint16_t end, fcb* fcb, file_ref* fileref, bool advance_only) { LARGE_INTEGER time; BTRFS_TIME now; TRACE("setting new end to %x bytes (currently %x)\n", end, fcb->adsdata.Length); if (!fileref || !fileref->parent) { ERR("no fileref for stream\n"); return STATUS_INTERNAL_ERROR; } if (end < fcb->adsdata.Length) { if (advance_only) return STATUS_SUCCESS; TRACE("truncating stream to %x bytes\n", end); fcb->adsdata.Length = end; } else if (end > fcb->adsdata.Length) { TRACE("extending stream to %x bytes\n", end); if (end > fcb->adsmaxlen) { ERR("error - xattr too long (%u > %lu)\n", end, fcb->adsmaxlen); return STATUS_DISK_FULL; } if (end > fcb->adsdata.MaximumLength) { char* data = ExAllocatePoolWithTag(PagedPool, end, ALLOC_TAG); if (!data) { ERR("out of memory\n"); ExFreePool(data); return STATUS_INSUFFICIENT_RESOURCES; } if (fcb->adsdata.Buffer) { RtlCopyMemory(data, fcb->adsdata.Buffer, fcb->adsdata.Length); ExFreePool(fcb->adsdata.Buffer); } fcb->adsdata.Buffer = data; fcb->adsdata.MaximumLength = end; } RtlZeroMemory(&fcb->adsdata.Buffer[fcb->adsdata.Length], end - fcb->adsdata.Length); fcb->adsdata.Length = end; } mark_fcb_dirty(fcb); fcb->Header.AllocationSize.QuadPart = end; fcb->Header.FileSize.QuadPart = end; fcb->Header.ValidDataLength.QuadPart = end; KeQuerySystemTime(&time); win_time_to_unix(time, &now); fileref->parent->fcb->inode_item.transid = Vcb->superblock.generation; fileref->parent->fcb->inode_item.sequence++; fileref->parent->fcb->inode_item.st_ctime = now; fileref->parent->fcb->inode_item_changed = true; mark_fcb_dirty(fileref->parent->fcb); fileref->parent->fcb->subvol->root_item.ctransid = Vcb->superblock.generation; fileref->parent->fcb->subvol->root_item.ctime = now; return STATUS_SUCCESS; } static NTSTATUS set_end_of_file_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, bool advance_only) { FILE_END_OF_FILE_INFORMATION* feofi = Irp->AssociatedIrp.SystemBuffer; fcb* fcb = FileObject->FsContext; ccb* ccb = FileObject->FsContext2; file_ref* fileref = ccb ? ccb->fileref : NULL; NTSTATUS Status; LARGE_INTEGER time; CC_FILE_SIZES ccfs; LIST_ENTRY rollback; bool set_size = false; ULONG filter; uint64_t new_end_of_file; if (!fileref) { ERR("fileref is NULL\n"); return STATUS_INVALID_PARAMETER; } InitializeListHead(&rollback); ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (fileref ? fileref->deleted : fcb->deleted) { Status = STATUS_FILE_CLOSED; goto end; } if (fcb->ads) { if (feofi->EndOfFile.QuadPart > 0xffff) { Status = STATUS_DISK_FULL; goto end; } if (feofi->EndOfFile.QuadPart < 0) { Status = STATUS_INVALID_PARAMETER; goto end; } Status = stream_set_end_of_file_information(Vcb, (uint16_t)feofi->EndOfFile.QuadPart, fcb, fileref, advance_only); if (NT_SUCCESS(Status)) { ccfs.AllocationSize = fcb->Header.AllocationSize; ccfs.FileSize = fcb->Header.FileSize; ccfs.ValidDataLength = fcb->Header.ValidDataLength; set_size = true; } filter = FILE_NOTIFY_CHANGE_STREAM_SIZE; if (!ccb->user_set_write_time) { KeQuerySystemTime(&time); win_time_to_unix(time, &fileref->parent->fcb->inode_item.st_mtime); filter |= FILE_NOTIFY_CHANGE_LAST_WRITE; fileref->parent->fcb->inode_item_changed = true; mark_fcb_dirty(fileref->parent->fcb); } queue_notification_fcb(fileref->parent, filter, FILE_ACTION_MODIFIED_STREAM, &fileref->dc->name); goto end; } TRACE("file: %p\n", FileObject); TRACE("paging IO: %s\n", Irp->Flags & IRP_PAGING_IO ? "true" : "false"); TRACE("FileObject: AllocationSize = %I64x, FileSize = %I64x, ValidDataLength = %I64x\n", fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart); new_end_of_file = feofi->EndOfFile.QuadPart; /* The lazy writer sometimes tries to round files to the next page size through CcSetValidData - * ignore these. See fastfat!FatSetEndOfFileInfo, where Microsoft does the same as we're * doing below. */ if (advance_only && new_end_of_file >= (uint64_t)fcb->Header.FileSize.QuadPart) new_end_of_file = fcb->Header.FileSize.QuadPart; TRACE("setting new end to %I64x bytes (currently %I64x)\n", new_end_of_file, fcb->inode_item.st_size); if (new_end_of_file < fcb->inode_item.st_size) { if (advance_only) { Status = STATUS_SUCCESS; goto end; } TRACE("truncating file to %I64x bytes\n", new_end_of_file); if (!MmCanFileBeTruncated(&fcb->nonpaged->segment_object, &feofi->EndOfFile)) { Status = STATUS_USER_MAPPED_FILE; goto end; } Status = truncate_file(fcb, new_end_of_file, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("error - truncate_file failed\n"); goto end; } } else if (new_end_of_file > fcb->inode_item.st_size) { TRACE("extending file to %I64x bytes\n", new_end_of_file); Status = extend_file(fcb, fileref, new_end_of_file, false, NULL, &rollback); if (!NT_SUCCESS(Status)) { ERR("error - extend_file failed\n"); goto end; } } else if (new_end_of_file == fcb->inode_item.st_size && advance_only) { Status = STATUS_SUCCESS; goto end; } ccfs.AllocationSize = fcb->Header.AllocationSize; ccfs.FileSize = fcb->Header.FileSize; ccfs.ValidDataLength = fcb->Header.ValidDataLength; set_size = true; filter = FILE_NOTIFY_CHANGE_SIZE; if (!ccb->user_set_write_time) { KeQuerySystemTime(&time); win_time_to_unix(time, &fcb->inode_item.st_mtime); filter |= FILE_NOTIFY_CHANGE_LAST_WRITE; } fcb->inode_item_changed = true; mark_fcb_dirty(fcb); queue_notification_fcb(fileref, filter, FILE_ACTION_MODIFIED, NULL); Status = STATUS_SUCCESS; end: if (NT_SUCCESS(Status)) clear_rollback(&rollback); else do_rollback(Vcb, &rollback); ExReleaseResourceLite(fcb->Header.Resource); if (set_size) { try { CcSetFileSizes(FileObject, &ccfs); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) ERR("CcSetFileSizes threw exception %08lx\n", Status); } ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS set_allocation_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) { FILE_ALLOCATION_INFORMATION* fai = Irp->AssociatedIrp.SystemBuffer; fcb* fcb = FileObject->FsContext; ccb* ccb = FileObject->FsContext2; file_ref* fileref = ccb ? ccb->fileref : NULL; NTSTATUS Status; LARGE_INTEGER time; CC_FILE_SIZES ccfs; LIST_ENTRY rollback; bool set_size = false; ULONG filter; uint64_t new_allocation_size, old_allocation_size; if (!fileref) { ERR("fileref is NULL\n"); return STATUS_INVALID_PARAMETER; } InitializeListHead(&rollback); ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (fileref ? fileref->deleted : fcb->deleted) { Status = STATUS_FILE_CLOSED; goto end; } if (fcb->ads) { if (fai->AllocationSize.QuadPart > 0xffff) { Status = STATUS_DISK_FULL; goto end; } if (fai->AllocationSize.QuadPart < 0) { Status = STATUS_INVALID_PARAMETER; goto end; } Status = stream_set_end_of_file_information(Vcb, (uint16_t)fai->AllocationSize.QuadPart, fcb, fileref, false); if (NT_SUCCESS(Status)) { ccfs.AllocationSize = fcb->Header.AllocationSize; ccfs.FileSize = fcb->Header.FileSize; ccfs.ValidDataLength = fcb->Header.ValidDataLength; set_size = true; } filter = FILE_NOTIFY_CHANGE_STREAM_SIZE; if (!ccb->user_set_write_time) { KeQuerySystemTime(&time); win_time_to_unix(time, &fileref->parent->fcb->inode_item.st_mtime); filter |= FILE_NOTIFY_CHANGE_LAST_WRITE; fileref->parent->fcb->inode_item_changed = true; mark_fcb_dirty(fileref->parent->fcb); } queue_notification_fcb(fileref->parent, filter, FILE_ACTION_MODIFIED_STREAM, &fileref->dc->name); goto end; } TRACE("file: %p\n", FileObject); TRACE("paging IO: %s\n", Irp->Flags & IRP_PAGING_IO ? "true" : "false"); TRACE("FileObject: AllocationSize = %I64x, FileSize = %I64x, ValidDataLength = %I64x\n", fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart); new_allocation_size = sector_align(fai->AllocationSize.QuadPart, fcb->Vcb->superblock.sector_size); old_allocation_size = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size); TRACE("setting new allocation size to %I64x bytes (currently %I64x)\n", new_allocation_size, old_allocation_size); if (new_allocation_size < old_allocation_size) { TRACE("truncating file to %I64x bytes\n", new_allocation_size); if (!MmCanFileBeTruncated(&fcb->nonpaged->segment_object, &fai->AllocationSize)) { Status = STATUS_USER_MAPPED_FILE; goto end; } Status = truncate_file(fcb, new_allocation_size, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("error - truncate_file failed\n"); goto end; } } else if (new_allocation_size > old_allocation_size) { TRACE("extending file to %I64x bytes\n", new_allocation_size); Status = extend_file(fcb, fileref, new_allocation_size, true, NULL, &rollback); if (!NT_SUCCESS(Status)) { ERR("error - extend_file failed\n"); goto end; } } else { Status = STATUS_SUCCESS; goto end; } ccfs.AllocationSize = fcb->Header.AllocationSize; ccfs.FileSize = fcb->Header.FileSize; ccfs.ValidDataLength = fcb->Header.ValidDataLength; set_size = true; filter = FILE_NOTIFY_CHANGE_SIZE; if (!ccb->user_set_write_time) { KeQuerySystemTime(&time); win_time_to_unix(time, &fcb->inode_item.st_mtime); filter |= FILE_NOTIFY_CHANGE_LAST_WRITE; } fcb->inode_item_changed = true; mark_fcb_dirty(fcb); queue_notification_fcb(fileref, filter, FILE_ACTION_MODIFIED, NULL); Status = STATUS_SUCCESS; end: if (NT_SUCCESS(Status)) clear_rollback(&rollback); else do_rollback(Vcb, &rollback); ExReleaseResourceLite(fcb->Header.Resource); if (set_size) { try { CcSetFileSizes(FileObject, &ccfs); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) ERR("CcSetFileSizes threw exception %08lx\n", Status); } ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS set_position_information(PFILE_OBJECT FileObject, PIRP Irp) { FILE_POSITION_INFORMATION* fpi = (FILE_POSITION_INFORMATION*)Irp->AssociatedIrp.SystemBuffer; TRACE("setting the position on %p to %I64x\n", FileObject, fpi->CurrentByteOffset.QuadPart); // FIXME - make sure aligned for FO_NO_INTERMEDIATE_BUFFERING FileObject->CurrentByteOffset = fpi->CurrentByteOffset; return STATUS_SUCCESS; } static NTSTATUS set_link_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, PFILE_OBJECT tfo, bool ex) { FILE_LINK_INFORMATION_EX* fli = Irp->AssociatedIrp.SystemBuffer; fcb *fcb = FileObject->FsContext, *tfofcb, *parfcb; ccb* ccb = FileObject->FsContext2; file_ref *fileref = ccb ? ccb->fileref : NULL, *oldfileref = NULL, *related = NULL, *fr2 = NULL; WCHAR* fn; ULONG fnlen, utf8len; UNICODE_STRING fnus; ANSI_STRING utf8; NTSTATUS Status; LARGE_INTEGER time; BTRFS_TIME now; LIST_ENTRY rollback; hardlink* hl; ACCESS_MASK access; SECURITY_SUBJECT_CONTEXT subjcont; dir_child* dc = NULL; ULONG flags; InitializeListHead(&rollback); // FIXME - check fli length // FIXME - don't ignore fli->RootDirectory if (ex) flags = fli->Flags; else flags = fli->ReplaceIfExists ? FILE_LINK_REPLACE_IF_EXISTS : 0; TRACE("flags = %lx\n", flags); TRACE("RootDirectory = %p\n", fli->RootDirectory); TRACE("FileNameLength = %lx\n", fli->FileNameLength); TRACE("FileName = %.*S\n", (int)(fli->FileNameLength / sizeof(WCHAR)), fli->FileName); fn = fli->FileName; fnlen = fli->FileNameLength / sizeof(WCHAR); if (!tfo) { if (!fileref || !fileref->parent) { ERR("no fileref set and no directory given\n"); return STATUS_INVALID_PARAMETER; } parfcb = fileref->parent->fcb; tfofcb = NULL; } else { LONG i; tfofcb = tfo->FsContext; parfcb = tfofcb; while (fnlen > 0 && (fli->FileName[fnlen - 1] == '/' || fli->FileName[fnlen - 1] == '\\')) { fnlen--; } if (fnlen == 0) return STATUS_INVALID_PARAMETER; for (i = fnlen - 1; i >= 0; i--) { if (fli->FileName[i] == '\\' || fli->FileName[i] == '/') { fn = &fli->FileName[i+1]; fnlen = (fli->FileNameLength / sizeof(WCHAR)) - i - 1; break; } } } ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(&Vcb->fileref_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (fcb->type == BTRFS_TYPE_DIRECTORY) { WARN("tried to create hard link on directory\n"); Status = STATUS_FILE_IS_A_DIRECTORY; goto end; } if (fcb->ads) { WARN("tried to create hard link on stream\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (fcb->inode_item.st_nlink >= 65535) { Status = STATUS_TOO_MANY_LINKS; goto end; } fnus.Buffer = fn; fnus.Length = fnus.MaximumLength = (uint16_t)(fnlen * sizeof(WCHAR)); TRACE("fnus = %.*S\n", (int)(fnus.Length / sizeof(WCHAR)), fnus.Buffer); Status = check_file_name_valid(&fnus, false, false); if (!NT_SUCCESS(Status)) goto end; Status = utf16_to_utf8(NULL, 0, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR)); if (!NT_SUCCESS(Status)) goto end; utf8.MaximumLength = utf8.Length = (uint16_t)utf8len; utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG); if (!utf8.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } Status = utf16_to_utf8(utf8.Buffer, utf8len, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR)); if (!NT_SUCCESS(Status)) goto end; if (tfo && tfo->FsContext2) { struct _ccb* relatedccb = tfo->FsContext2; related = relatedccb->fileref; increase_fileref_refcount(related); } Status = open_fileref(Vcb, &oldfileref, &fnus, related, false, NULL, NULL, PagedPool, ccb->case_sensitive, Irp); if (NT_SUCCESS(Status)) { if (!oldfileref->deleted) { WARN("destination file already exists\n"); if (!(flags & FILE_LINK_REPLACE_IF_EXISTS)) { Status = STATUS_OBJECT_NAME_COLLISION; goto end; } else if (fileref == oldfileref) { Status = STATUS_ACCESS_DENIED; goto end; } else if (!(flags & FILE_LINK_POSIX_SEMANTICS) && (oldfileref->open_count > 0 || has_open_children(oldfileref)) && !oldfileref->deleted) { WARN("trying to overwrite open file\n"); Status = STATUS_ACCESS_DENIED; goto end; } else if (!(flags & FILE_LINK_IGNORE_READONLY_ATTRIBUTE) && oldfileref->fcb->atts & FILE_ATTRIBUTE_READONLY) { WARN("trying to overwrite readonly file\n"); Status = STATUS_ACCESS_DENIED; goto end; } else if (oldfileref->fcb->type == BTRFS_TYPE_DIRECTORY) { WARN("trying to overwrite directory\n"); Status = STATUS_ACCESS_DENIED; goto end; } } else { free_fileref(oldfileref); oldfileref = NULL; } } if (!related) { Status = open_fileref(Vcb, &related, &fnus, NULL, true, NULL, NULL, PagedPool, ccb->case_sensitive, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fileref returned %08lx\n", Status); goto end; } } SeCaptureSubjectContext(&subjcont); if (!SeAccessCheck(related->fcb->sd, &subjcont, false, FILE_ADD_FILE, 0, NULL, IoGetFileObjectGenericMapping(), Irp->RequestorMode, &access, &Status)) { SeReleaseSubjectContext(&subjcont); TRACE("SeAccessCheck failed, returning %08lx\n", Status); goto end; } SeReleaseSubjectContext(&subjcont); if (fcb->subvol != parfcb->subvol) { WARN("can't create hard link over subvolume boundary\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (oldfileref) { SeCaptureSubjectContext(&subjcont); if (!SeAccessCheck(oldfileref->fcb->sd, &subjcont, false, DELETE, 0, NULL, IoGetFileObjectGenericMapping(), Irp->RequestorMode, &access, &Status)) { SeReleaseSubjectContext(&subjcont); TRACE("SeAccessCheck failed, returning %08lx\n", Status); goto end; } SeReleaseSubjectContext(&subjcont); if (oldfileref->open_count > 0 && flags & FILE_RENAME_POSIX_SEMANTICS) { oldfileref->delete_on_close = true; oldfileref->posix_delete = true; } Status = delete_fileref(oldfileref, NULL, oldfileref->open_count > 0 && flags & FILE_RENAME_POSIX_SEMANTICS, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("delete_fileref returned %08lx\n", Status); goto end; } } fr2 = create_fileref(Vcb); fr2->fcb = fcb; fcb->refcount++; fr2->created = true; fr2->parent = related; Status = add_dir_child(related->fcb, fcb->inode, false, &utf8, &fnus, fcb->type, &dc); if (!NT_SUCCESS(Status)) WARN("add_dir_child returned %08lx\n", Status); fr2->dc = dc; dc->fileref = fr2; ExAcquireResourceExclusiveLite(&related->fcb->nonpaged->dir_children_lock, true); InsertTailList(&related->children, &fr2->list_entry); ExReleaseResourceLite(&related->fcb->nonpaged->dir_children_lock); // add hardlink for existing fileref, if it's not there already if (IsListEmpty(&fcb->hardlinks)) { hl = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG); if (!hl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } hl->parent = fileref->parent->fcb->inode; hl->index = fileref->dc->index; hl->name.Length = hl->name.MaximumLength = fnus.Length; hl->name.Buffer = ExAllocatePoolWithTag(PagedPool, fnus.Length, ALLOC_TAG); if (!hl->name.Buffer) { ERR("out of memory\n"); ExFreePool(hl); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(hl->name.Buffer, fnus.Buffer, fnus.Length); hl->utf8.Length = hl->utf8.MaximumLength = fileref->dc->utf8.Length; hl->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl->utf8.MaximumLength, ALLOC_TAG); if (!hl->utf8.Buffer) { ERR("out of memory\n"); ExFreePool(hl->name.Buffer); ExFreePool(hl); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(hl->utf8.Buffer, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length); InsertTailList(&fcb->hardlinks, &hl->list_entry); } hl = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG); if (!hl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } hl->parent = related->fcb->inode; hl->index = dc->index; hl->name.Length = hl->name.MaximumLength = fnus.Length; hl->name.Buffer = ExAllocatePoolWithTag(PagedPool, hl->name.MaximumLength, ALLOC_TAG); if (!hl->name.Buffer) { ERR("out of memory\n"); ExFreePool(hl); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(hl->name.Buffer, fnus.Buffer, fnus.Length); hl->utf8.Length = hl->utf8.MaximumLength = utf8.Length; hl->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl->utf8.MaximumLength, ALLOC_TAG); if (!hl->utf8.Buffer) { ERR("out of memory\n"); ExFreePool(hl->name.Buffer); ExFreePool(hl); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(hl->utf8.Buffer, utf8.Buffer, utf8.Length); ExFreePool(utf8.Buffer); InsertTailList(&fcb->hardlinks, &hl->list_entry); mark_fileref_dirty(fr2); free_fileref(fr2); // update inode's INODE_ITEM KeQuerySystemTime(&time); win_time_to_unix(time, &now); fcb->inode_item.transid = Vcb->superblock.generation; fcb->inode_item.sequence++; fcb->inode_item.st_nlink++; if (!ccb->user_set_change_time) fcb->inode_item.st_ctime = now; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); // update parent's INODE_ITEM parfcb->inode_item.transid = Vcb->superblock.generation; TRACE("parfcb->inode_item.st_size (inode %I64x) was %I64x\n", parfcb->inode, parfcb->inode_item.st_size); parfcb->inode_item.st_size += 2 * utf8len; TRACE("parfcb->inode_item.st_size (inode %I64x) now %I64x\n", parfcb->inode, parfcb->inode_item.st_size); parfcb->inode_item.sequence++; parfcb->inode_item.st_ctime = now; parfcb->inode_item_changed = true; mark_fcb_dirty(parfcb); send_notification_fileref(fr2, FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL); Status = STATUS_SUCCESS; end: if (oldfileref) free_fileref(oldfileref); if (!NT_SUCCESS(Status) && related) free_fileref(related); if (!NT_SUCCESS(Status) && fr2) free_fileref(fr2); if (NT_SUCCESS(Status)) clear_rollback(&rollback); else do_rollback(Vcb, &rollback); ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&Vcb->fileref_lock); ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS set_valid_data_length_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) { FILE_VALID_DATA_LENGTH_INFORMATION* fvdli = Irp->AssociatedIrp.SystemBuffer; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); fcb* fcb = FileObject->FsContext; ccb* ccb = FileObject->FsContext2; file_ref* fileref = ccb ? ccb->fileref : NULL; NTSTATUS Status; LARGE_INTEGER time; CC_FILE_SIZES ccfs; LIST_ENTRY rollback; bool set_size = false; ULONG filter; if (IrpSp->Parameters.SetFile.Length < sizeof(FILE_VALID_DATA_LENGTH_INFORMATION)) { ERR("input buffer length was %lu, expected %Iu\n", IrpSp->Parameters.SetFile.Length, sizeof(FILE_VALID_DATA_LENGTH_INFORMATION)); return STATUS_INVALID_PARAMETER; } if (!fileref) { ERR("fileref is NULL\n"); return STATUS_INVALID_PARAMETER; } InitializeListHead(&rollback); ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (fcb->atts & FILE_ATTRIBUTE_SPARSE_FILE) { Status = STATUS_INVALID_PARAMETER; goto end; } if (fvdli->ValidDataLength.QuadPart <= fcb->Header.ValidDataLength.QuadPart || fvdli->ValidDataLength.QuadPart > fcb->Header.FileSize.QuadPart) { TRACE("invalid VDL of %I64u (current VDL = %I64u, file size = %I64u)\n", fvdli->ValidDataLength.QuadPart, fcb->Header.ValidDataLength.QuadPart, fcb->Header.FileSize.QuadPart); Status = STATUS_INVALID_PARAMETER; goto end; } if (fileref ? fileref->deleted : fcb->deleted) { Status = STATUS_FILE_CLOSED; goto end; } // This function doesn't really do anything - the fsctl can only increase the value of ValidDataLength, // and we set it to the max anyway. ccfs.AllocationSize = fcb->Header.AllocationSize; ccfs.FileSize = fcb->Header.FileSize; ccfs.ValidDataLength = fvdli->ValidDataLength; set_size = true; filter = FILE_NOTIFY_CHANGE_SIZE; if (!ccb->user_set_write_time) { KeQuerySystemTime(&time); win_time_to_unix(time, &fcb->inode_item.st_mtime); filter |= FILE_NOTIFY_CHANGE_LAST_WRITE; } fcb->inode_item_changed = true; mark_fcb_dirty(fcb); queue_notification_fcb(fileref, filter, FILE_ACTION_MODIFIED, NULL); Status = STATUS_SUCCESS; end: if (NT_SUCCESS(Status)) clear_rollback(&rollback); else do_rollback(Vcb, &rollback); ExReleaseResourceLite(fcb->Header.Resource); if (set_size) { try { CcSetFileSizes(FileObject, &ccfs); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) ERR("CcSetFileSizes threw exception %08lx\n", Status); else fcb->Header.AllocationSize = ccfs.AllocationSize; } ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS set_case_sensitive_information(PIRP Irp) { FILE_CASE_SENSITIVE_INFORMATION* fcsi = (FILE_CASE_SENSITIVE_INFORMATION*)Irp->AssociatedIrp.SystemBuffer; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); if (IrpSp->Parameters.FileSystemControl.InputBufferLength < sizeof(FILE_CASE_SENSITIVE_INFORMATION)) return STATUS_INFO_LENGTH_MISMATCH; PFILE_OBJECT FileObject = IrpSp->FileObject; if (!FileObject) return STATUS_INVALID_PARAMETER; fcb* fcb = FileObject->FsContext; if (!fcb) return STATUS_INVALID_PARAMETER; if (!(fcb->atts & FILE_ATTRIBUTE_DIRECTORY)) { WARN("cannot set case-sensitive flag on anything other than directory\n"); return STATUS_INVALID_PARAMETER; } ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true); fcb->case_sensitive = fcsi->Flags & FILE_CS_FLAG_CASE_SENSITIVE_DIR; mark_fcb_dirty(fcb); ExReleaseResourceLite(&fcb->Vcb->tree_lock); return STATUS_SUCCESS; } _Dispatch_type_(IRP_MJ_SET_INFORMATION) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_set_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS Status; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); device_extension* Vcb = DeviceObject->DeviceExtension; fcb* fcb = IrpSp->FileObject->FsContext; ccb* ccb = IrpSp->FileObject->FsContext2; bool top_level; FsRtlEnterFileSystem(); top_level = is_top_level(Irp); Irp->IoStatus.Information = 0; if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = STATUS_INVALID_DEVICE_REQUEST; goto end; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } if (!(Vcb->Vpb->Flags & VPB_MOUNTED)) { Status = STATUS_ACCESS_DENIED; goto end; } if (Vcb->readonly && IrpSp->Parameters.SetFile.FileInformationClass != FilePositionInformation) { Status = STATUS_MEDIA_WRITE_PROTECTED; goto end; } if (!fcb) { ERR("no fcb\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (!ccb) { ERR("no ccb\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (fcb != Vcb->dummy_fcb && is_subvol_readonly(fcb->subvol, Irp) && IrpSp->Parameters.SetFile.FileInformationClass != FilePositionInformation && (fcb->inode != SUBVOL_ROOT_INODE || (IrpSp->Parameters.SetFile.FileInformationClass != FileBasicInformation && IrpSp->Parameters.SetFile.FileInformationClass != FileRenameInformation && IrpSp->Parameters.SetFile.FileInformationClass != FileRenameInformationEx))) { Status = STATUS_ACCESS_DENIED; goto end; } Status = STATUS_NOT_IMPLEMENTED; TRACE("set information\n"); FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL); switch (IrpSp->Parameters.SetFile.FileInformationClass) { case FileAllocationInformation: { TRACE("FileAllocationInformation\n"); if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_DATA)) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; break; } Status = set_allocation_information(Vcb, Irp, IrpSp->FileObject); break; } case FileBasicInformation: { TRACE("FileBasicInformation\n"); if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; break; } Status = set_basic_information(Vcb, Irp, IrpSp->FileObject); break; } case FileDispositionInformation: { TRACE("FileDispositionInformation\n"); if (Irp->RequestorMode == UserMode && !(ccb->access & DELETE)) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; break; } Status = set_disposition_information(Vcb, Irp, IrpSp->FileObject, false); break; } case FileEndOfFileInformation: { TRACE("FileEndOfFileInformation\n"); if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_WRITE_DATA | FILE_APPEND_DATA))) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; break; } Status = set_end_of_file_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.AdvanceOnly); break; } case FileLinkInformation: TRACE("FileLinkInformation\n"); Status = set_link_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, false); break; case FilePositionInformation: TRACE("FilePositionInformation\n"); Status = set_position_information(IrpSp->FileObject, Irp); break; case FileRenameInformation: TRACE("FileRenameInformation\n"); Status = set_rename_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, false); break; case FileValidDataLengthInformation: { TRACE("FileValidDataLengthInformation\n"); if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_WRITE_DATA | FILE_APPEND_DATA))) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; break; } Status = set_valid_data_length_information(Vcb, Irp, IrpSp->FileObject); break; } #ifndef _MSC_VER #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch" #endif case FileDispositionInformationEx: { TRACE("FileDispositionInformationEx\n"); if (Irp->RequestorMode == UserMode && !(ccb->access & DELETE)) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; break; } Status = set_disposition_information(Vcb, Irp, IrpSp->FileObject, true); break; } case FileRenameInformationEx: TRACE("FileRenameInformationEx\n"); Status = set_rename_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, true); break; case FileLinkInformationEx: TRACE("FileLinkInformationEx\n"); Status = set_link_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, true); break; case FileCaseSensitiveInformation: TRACE("FileCaseSensitiveInformation\n"); if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; break; } Status = set_case_sensitive_information(Irp); break; case FileStorageReserveIdInformation: WARN("unimplemented FileInformationClass FileStorageReserveIdInformation\n"); break; #ifndef _MSC_VER #pragma GCC diagnostic pop #endif default: WARN("unknown FileInformationClass %u\n", IrpSp->Parameters.SetFile.FileInformationClass); } end: Irp->IoStatus.Status = Status; TRACE("returning %08lx\n", Status); IoCompleteRequest(Irp, IO_NO_INCREMENT); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } static NTSTATUS fill_in_file_basic_information(FILE_BASIC_INFORMATION* fbi, INODE_ITEM* ii, LONG* length, fcb* fcb, file_ref* fileref) { RtlZeroMemory(fbi, sizeof(FILE_BASIC_INFORMATION)); *length -= sizeof(FILE_BASIC_INFORMATION); if (fcb == fcb->Vcb->dummy_fcb) { LARGE_INTEGER time; KeQuerySystemTime(&time); fbi->CreationTime = fbi->LastAccessTime = fbi->LastWriteTime = fbi->ChangeTime = time; } else { fbi->CreationTime.QuadPart = unix_time_to_win(&ii->otime); fbi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime); fbi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime); fbi->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime); } if (fcb->ads) { if (!fileref || !fileref->parent) { ERR("no fileref for stream\n"); return STATUS_INTERNAL_ERROR; } else fbi->FileAttributes = fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fileref->parent->fcb->atts; } else fbi->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts; return STATUS_SUCCESS; } static NTSTATUS fill_in_file_network_open_information(FILE_NETWORK_OPEN_INFORMATION* fnoi, fcb* fcb, file_ref* fileref, LONG* length) { INODE_ITEM* ii; if (*length < (LONG)sizeof(FILE_NETWORK_OPEN_INFORMATION)) { WARN("overflow\n"); return STATUS_BUFFER_OVERFLOW; } RtlZeroMemory(fnoi, sizeof(FILE_NETWORK_OPEN_INFORMATION)); *length -= sizeof(FILE_NETWORK_OPEN_INFORMATION); if (fcb->ads) { if (!fileref || !fileref->parent) { ERR("no fileref for stream\n"); return STATUS_INTERNAL_ERROR; } ii = &fileref->parent->fcb->inode_item; } else ii = &fcb->inode_item; if (fcb == fcb->Vcb->dummy_fcb) { LARGE_INTEGER time; KeQuerySystemTime(&time); fnoi->CreationTime = fnoi->LastAccessTime = fnoi->LastWriteTime = fnoi->ChangeTime = time; } else { fnoi->CreationTime.QuadPart = unix_time_to_win(&ii->otime); fnoi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime); fnoi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime); fnoi->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime); } if (fcb->ads) { fnoi->AllocationSize.QuadPart = fnoi->EndOfFile.QuadPart = fcb->adsdata.Length; fnoi->FileAttributes = fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fileref->parent->fcb->atts; } else { fnoi->AllocationSize.QuadPart = fcb_alloc_size(fcb); fnoi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size; fnoi->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts; } return STATUS_SUCCESS; } static NTSTATUS fill_in_file_standard_information(FILE_STANDARD_INFORMATION* fsi, fcb* fcb, file_ref* fileref, LONG* length) { RtlZeroMemory(fsi, sizeof(FILE_STANDARD_INFORMATION)); *length -= sizeof(FILE_STANDARD_INFORMATION); if (fcb->ads) { if (!fileref || !fileref->parent) { ERR("no fileref for stream\n"); return STATUS_INTERNAL_ERROR; } fsi->AllocationSize.QuadPart = fsi->EndOfFile.QuadPart = fcb->adsdata.Length; fsi->NumberOfLinks = fileref->parent->fcb->inode_item.st_nlink; fsi->Directory = false; } else { fsi->AllocationSize.QuadPart = fcb_alloc_size(fcb); fsi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size; fsi->NumberOfLinks = fcb->inode_item.st_nlink; fsi->Directory = S_ISDIR(fcb->inode_item.st_mode); } TRACE("length = %I64u\n", fsi->EndOfFile.QuadPart); fsi->DeletePending = fileref ? fileref->delete_on_close : false; return STATUS_SUCCESS; } static NTSTATUS fill_in_file_internal_information(FILE_INTERNAL_INFORMATION* fii, fcb* fcb, LONG* length) { *length -= sizeof(FILE_INTERNAL_INFORMATION); fii->IndexNumber.QuadPart = make_file_id(fcb->subvol, fcb->inode); return STATUS_SUCCESS; } static NTSTATUS fill_in_file_ea_information(FILE_EA_INFORMATION* eai, fcb* fcb, LONG* length) { *length -= sizeof(FILE_EA_INFORMATION); /* This value appears to be the size of the structure NTFS stores on disk, and not, * as might be expected, the size of FILE_FULL_EA_INFORMATION (which is what we store). * The formula is 4 bytes as a header, followed by 5 + NameLength + ValueLength for each * item. */ eai->EaSize = fcb->ealen; return STATUS_SUCCESS; } static NTSTATUS fill_in_file_position_information(FILE_POSITION_INFORMATION* fpi, PFILE_OBJECT FileObject, LONG* length) { RtlZeroMemory(fpi, sizeof(FILE_POSITION_INFORMATION)); *length -= sizeof(FILE_POSITION_INFORMATION); fpi->CurrentByteOffset = FileObject->CurrentByteOffset; return STATUS_SUCCESS; } NTSTATUS fileref_get_filename(file_ref* fileref, PUNICODE_STRING fn, USHORT* name_offset, ULONG* preqlen) { file_ref* fr; NTSTATUS Status; ULONG reqlen = 0; USHORT offset; bool overflow = false; // FIXME - we need a lock on filerefs' filepart if (fileref == fileref->fcb->Vcb->root_fileref) { if (fn->MaximumLength >= sizeof(WCHAR)) { fn->Buffer[0] = '\\'; fn->Length = sizeof(WCHAR); if (name_offset) *name_offset = 0; return STATUS_SUCCESS; } else { if (preqlen) *preqlen = sizeof(WCHAR); fn->Length = 0; return STATUS_BUFFER_OVERFLOW; } } fr = fileref; offset = 0; while (fr->parent) { USHORT movelen; if (!fr->dc) return STATUS_INTERNAL_ERROR; if (!overflow) { if (fr->dc->name.Length + sizeof(WCHAR) + fn->Length > fn->MaximumLength) overflow = true; } if (overflow) movelen = fn->MaximumLength - fr->dc->name.Length - sizeof(WCHAR); else movelen = fn->Length; if ((!overflow || fn->MaximumLength > fr->dc->name.Length + sizeof(WCHAR)) && movelen > 0) { RtlMoveMemory(&fn->Buffer[(fr->dc->name.Length / sizeof(WCHAR)) + 1], fn->Buffer, movelen); offset += fr->dc->name.Length + sizeof(WCHAR); } if (fn->MaximumLength >= sizeof(WCHAR)) { fn->Buffer[0] = fr->fcb->ads ? ':' : '\\'; fn->Length += sizeof(WCHAR); if (fn->MaximumLength > sizeof(WCHAR)) { RtlCopyMemory(&fn->Buffer[1], fr->dc->name.Buffer, min(fr->dc->name.Length, fn->MaximumLength - sizeof(WCHAR))); fn->Length += fr->dc->name.Length; } if (fn->Length > fn->MaximumLength) { fn->Length = fn->MaximumLength; overflow = true; } } reqlen += sizeof(WCHAR) + fr->dc->name.Length; fr = fr->parent; } offset += sizeof(WCHAR); if (overflow) { if (preqlen) *preqlen = reqlen; Status = STATUS_BUFFER_OVERFLOW; } else { if (name_offset) *name_offset = offset; Status = STATUS_SUCCESS; } return Status; } static NTSTATUS fill_in_file_name_information(FILE_NAME_INFORMATION* fni, fcb* fcb, file_ref* fileref, LONG* length) { ULONG reqlen; UNICODE_STRING fn; NTSTATUS Status; static const WCHAR datasuf[] = {':','$','D','A','T','A',0}; uint16_t datasuflen = sizeof(datasuf) - sizeof(WCHAR); if (!fileref) { ERR("called without fileref\n"); return STATUS_INVALID_PARAMETER; } *length -= (LONG)offsetof(FILE_NAME_INFORMATION, FileName[0]); TRACE("maximum length is %li\n", *length); fni->FileNameLength = 0; fni->FileName[0] = 0; fn.Buffer = fni->FileName; fn.Length = 0; fn.MaximumLength = (uint16_t)*length; Status = fileref_get_filename(fileref, &fn, NULL, &reqlen); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) { ERR("fileref_get_filename returned %08lx\n", Status); return Status; } if (fcb->ads) { if (Status == STATUS_BUFFER_OVERFLOW) reqlen += datasuflen; else { if (fn.Length + datasuflen > fn.MaximumLength) { RtlCopyMemory(&fn.Buffer[fn.Length / sizeof(WCHAR)], datasuf, fn.MaximumLength - fn.Length); reqlen += datasuflen; Status = STATUS_BUFFER_OVERFLOW; } else { RtlCopyMemory(&fn.Buffer[fn.Length / sizeof(WCHAR)], datasuf, datasuflen); fn.Length += datasuflen; } } } if (Status == STATUS_BUFFER_OVERFLOW) { *length = -1; fni->FileNameLength = reqlen; TRACE("%.*S (truncated)\n", (int)(fn.Length / sizeof(WCHAR)), fn.Buffer); } else { *length -= fn.Length; fni->FileNameLength = fn.Length; TRACE("%.*S\n", (int)(fn.Length / sizeof(WCHAR)), fn.Buffer); } return Status; } static NTSTATUS fill_in_file_attribute_information(FILE_ATTRIBUTE_TAG_INFORMATION* ati, fcb* fcb, ccb* ccb, LONG* length) { *length -= sizeof(FILE_ATTRIBUTE_TAG_INFORMATION); if (fcb->ads) { if (!ccb->fileref || !ccb->fileref->parent) { ERR("no fileref for stream\n"); return STATUS_INTERNAL_ERROR; } ati->FileAttributes = ccb->fileref->parent->fcb->atts; } else ati->FileAttributes = fcb->atts; if (!(ati->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) ati->ReparseTag = 0; else ati->ReparseTag = get_reparse_tag_fcb(fcb); return STATUS_SUCCESS; } static NTSTATUS fill_in_file_stream_information(FILE_STREAM_INFORMATION* fsi, file_ref* fileref, LONG* length) { LONG reqsize; LIST_ENTRY* le; FILE_STREAM_INFORMATION *entry, *lastentry; NTSTATUS Status; static const WCHAR datasuf[] = L":$DATA"; UNICODE_STRING suf; if (!fileref) { ERR("fileref was NULL\n"); return STATUS_INVALID_PARAMETER; } suf.Buffer = (WCHAR*)datasuf; suf.Length = suf.MaximumLength = sizeof(datasuf) - sizeof(WCHAR); if (fileref->fcb->type != BTRFS_TYPE_DIRECTORY) reqsize = sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR); else reqsize = 0; ExAcquireResourceSharedLite(&fileref->fcb->nonpaged->dir_children_lock, true); le = fileref->fcb->dir_children_index.Flink; while (le != &fileref->fcb->dir_children_index) { dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index); if (dc->index == 0) { reqsize = (ULONG)sector_align(reqsize, sizeof(LONGLONG)); reqsize += sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR) + dc->name.Length; } else break; le = le->Flink; } TRACE("length = %li, reqsize = %lu\n", *length, reqsize); if (reqsize > *length) { Status = STATUS_BUFFER_OVERFLOW; goto end; } entry = fsi; lastentry = NULL; if (fileref->fcb->type != BTRFS_TYPE_DIRECTORY) { ULONG off; entry->NextEntryOffset = 0; entry->StreamNameLength = suf.Length + sizeof(WCHAR); entry->StreamSize.QuadPart = fileref->fcb->inode_item.st_size; entry->StreamAllocationSize.QuadPart = fcb_alloc_size(fileref->fcb); entry->StreamName[0] = ':'; RtlCopyMemory(&entry->StreamName[1], suf.Buffer, suf.Length); off = (ULONG)sector_align(sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR), sizeof(LONGLONG)); lastentry = entry; entry = (FILE_STREAM_INFORMATION*)((uint8_t*)entry + off); } le = fileref->fcb->dir_children_index.Flink; while (le != &fileref->fcb->dir_children_index) { dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index); if (dc->index == 0) { ULONG off; entry->NextEntryOffset = 0; entry->StreamNameLength = dc->name.Length + suf.Length + sizeof(WCHAR); if (dc->fileref) entry->StreamSize.QuadPart = dc->fileref->fcb->adsdata.Length; else entry->StreamSize.QuadPart = dc->size; entry->StreamAllocationSize.QuadPart = entry->StreamSize.QuadPart; entry->StreamName[0] = ':'; RtlCopyMemory(&entry->StreamName[1], dc->name.Buffer, dc->name.Length); RtlCopyMemory(&entry->StreamName[1 + (dc->name.Length / sizeof(WCHAR))], suf.Buffer, suf.Length); if (lastentry) lastentry->NextEntryOffset = (uint32_t)((uint8_t*)entry - (uint8_t*)lastentry); off = (ULONG)sector_align(sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR) + dc->name.Length, sizeof(LONGLONG)); lastentry = entry; entry = (FILE_STREAM_INFORMATION*)((uint8_t*)entry + off); } else break; le = le->Flink; } *length -= reqsize; Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&fileref->fcb->nonpaged->dir_children_lock); return Status; } static NTSTATUS fill_in_file_standard_link_information(FILE_STANDARD_LINK_INFORMATION* fsli, fcb* fcb, file_ref* fileref, LONG* length) { TRACE("FileStandardLinkInformation\n"); // FIXME - NumberOfAccessibleLinks should subtract open links which have been marked as delete_on_close fsli->NumberOfAccessibleLinks = fcb->inode_item.st_nlink; fsli->TotalNumberOfLinks = fcb->inode_item.st_nlink; fsli->DeletePending = fileref ? fileref->delete_on_close : false; fsli->Directory = (!fcb->ads && fcb->type == BTRFS_TYPE_DIRECTORY) ? true : false; *length -= sizeof(FILE_STANDARD_LINK_INFORMATION); return STATUS_SUCCESS; } static NTSTATUS fill_in_hard_link_information(FILE_LINKS_INFORMATION* fli, file_ref* fileref, PIRP Irp, LONG* length) { NTSTATUS Status; LIST_ENTRY* le; LONG bytes_needed; FILE_LINK_ENTRY_INFORMATION* feli; bool overflow = false; fcb* fcb = fileref->fcb; ULONG len; if (fcb->ads) return STATUS_INVALID_PARAMETER; if (*length < (LONG)offsetof(FILE_LINKS_INFORMATION, Entry)) return STATUS_INVALID_PARAMETER; RtlZeroMemory(fli, *length); bytes_needed = offsetof(FILE_LINKS_INFORMATION, Entry); len = bytes_needed; feli = NULL; ExAcquireResourceSharedLite(fcb->Header.Resource, true); if (fcb->inode == SUBVOL_ROOT_INODE) { ULONG namelen; if (fcb == fcb->Vcb->root_fileref->fcb) namelen = sizeof(WCHAR); else namelen = fileref->dc->name.Length; bytes_needed += sizeof(FILE_LINK_ENTRY_INFORMATION) - sizeof(WCHAR) + namelen; if (bytes_needed > *length) overflow = true; if (!overflow) { feli = &fli->Entry; feli->NextEntryOffset = 0; feli->ParentFileId = 0; // we use an inode of 0 to mean the parent of a subvolume if (fcb == fcb->Vcb->root_fileref->fcb) { feli->FileNameLength = 1; feli->FileName[0] = '.'; } else { feli->FileNameLength = fileref->dc->name.Length / sizeof(WCHAR); RtlCopyMemory(feli->FileName, fileref->dc->name.Buffer, fileref->dc->name.Length); } fli->EntriesReturned++; len = bytes_needed; } } else { ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true); if (IsListEmpty(&fcb->hardlinks)) { if (!fileref->dc) { ExReleaseResourceLite(&fcb->Vcb->fileref_lock); Status = STATUS_INVALID_PARAMETER; goto end; } bytes_needed += sizeof(FILE_LINK_ENTRY_INFORMATION) + fileref->dc->name.Length - sizeof(WCHAR); if (bytes_needed > *length) overflow = true; if (!overflow) { feli = &fli->Entry; feli->NextEntryOffset = 0; feli->ParentFileId = fileref->parent->fcb->inode; feli->FileNameLength = fileref->dc->name.Length / sizeof(WCHAR); RtlCopyMemory(feli->FileName, fileref->dc->name.Buffer, fileref->dc->name.Length); fli->EntriesReturned++; len = bytes_needed; } } else { le = fcb->hardlinks.Flink; while (le != &fcb->hardlinks) { hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry); file_ref* parfr; TRACE("parent %I64x, index %I64x, name %.*S\n", hl->parent, hl->index, (int)(hl->name.Length / sizeof(WCHAR)), hl->name.Buffer); Status = open_fileref_by_inode(fcb->Vcb, fcb->subvol, hl->parent, &parfr, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fileref_by_inode returned %08lx\n", Status); } else if (!parfr->deleted) { LIST_ENTRY* le2; bool found = false, deleted = false; UNICODE_STRING* fn = NULL; le2 = parfr->children.Flink; while (le2 != &parfr->children) { file_ref* fr2 = CONTAINING_RECORD(le2, file_ref, list_entry); if (fr2->dc && fr2->dc->index == hl->index) { found = true; deleted = fr2->deleted; if (!deleted) fn = &fr2->dc->name; break; } le2 = le2->Flink; } if (!found) fn = &hl->name; if (!deleted) { TRACE("fn = %.*S (found = %u)\n", (int)(fn->Length / sizeof(WCHAR)), fn->Buffer, found); if (feli) bytes_needed = (LONG)sector_align(bytes_needed, 8); bytes_needed += sizeof(FILE_LINK_ENTRY_INFORMATION) + fn->Length - sizeof(WCHAR); if (bytes_needed > *length) overflow = true; if (!overflow) { if (feli) { feli->NextEntryOffset = (ULONG)sector_align(sizeof(FILE_LINK_ENTRY_INFORMATION) + ((feli->FileNameLength - 1) * sizeof(WCHAR)), 8); feli = (FILE_LINK_ENTRY_INFORMATION*)((uint8_t*)feli + feli->NextEntryOffset); } else feli = &fli->Entry; feli->NextEntryOffset = 0; feli->ParentFileId = parfr->fcb->inode; feli->FileNameLength = fn->Length / sizeof(WCHAR); RtlCopyMemory(feli->FileName, fn->Buffer, fn->Length); fli->EntriesReturned++; len = bytes_needed; } } free_fileref(parfr); } le = le->Flink; } } ExReleaseResourceLite(&fcb->Vcb->fileref_lock); } fli->BytesNeeded = bytes_needed; *length -= len; Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS; end: ExReleaseResourceLite(fcb->Header.Resource); return Status; } static NTSTATUS fill_in_hard_link_full_id_information(FILE_LINKS_FULL_ID_INFORMATION* flfii, file_ref* fileref, PIRP Irp, LONG* length) { NTSTATUS Status; LIST_ENTRY* le; LONG bytes_needed; FILE_LINK_ENTRY_FULL_ID_INFORMATION* flefii; bool overflow = false; fcb* fcb = fileref->fcb; ULONG len; if (fcb->ads) return STATUS_INVALID_PARAMETER; if (*length < (LONG)offsetof(FILE_LINKS_FULL_ID_INFORMATION, Entry)) return STATUS_INVALID_PARAMETER; RtlZeroMemory(flfii, *length); bytes_needed = offsetof(FILE_LINKS_FULL_ID_INFORMATION, Entry); len = bytes_needed; flefii = NULL; ExAcquireResourceSharedLite(fcb->Header.Resource, true); if (fcb->inode == SUBVOL_ROOT_INODE) { ULONG namelen; if (fcb == fcb->Vcb->root_fileref->fcb) namelen = sizeof(WCHAR); else namelen = fileref->dc->name.Length; bytes_needed += offsetof(FILE_LINK_ENTRY_FULL_ID_INFORMATION, FileName[0]) + namelen; if (bytes_needed > *length) overflow = true; if (!overflow) { flefii = &flfii->Entry; flefii->NextEntryOffset = 0; if (fcb == fcb->Vcb->root_fileref->fcb) { RtlZeroMemory(&flefii->ParentFileId.Identifier[0], sizeof(FILE_ID_128)); flefii->FileNameLength = 1; flefii->FileName[0] = '.'; } else { RtlCopyMemory(&flefii->ParentFileId.Identifier[0], &fileref->parent->fcb->inode, sizeof(uint64_t)); RtlCopyMemory(&flefii->ParentFileId.Identifier[sizeof(uint64_t)], &fileref->parent->fcb->subvol->id, sizeof(uint64_t)); flefii->FileNameLength = fileref->dc->name.Length / sizeof(WCHAR); RtlCopyMemory(flefii->FileName, fileref->dc->name.Buffer, fileref->dc->name.Length); } flfii->EntriesReturned++; len = bytes_needed; } } else { ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true); if (IsListEmpty(&fcb->hardlinks)) { bytes_needed += offsetof(FILE_LINK_ENTRY_FULL_ID_INFORMATION, FileName[0]) + fileref->dc->name.Length; if (bytes_needed > *length) overflow = true; if (!overflow) { flefii = &flfii->Entry; flefii->NextEntryOffset = 0; RtlCopyMemory(&flefii->ParentFileId.Identifier[0], &fileref->parent->fcb->inode, sizeof(uint64_t)); RtlCopyMemory(&flefii->ParentFileId.Identifier[sizeof(uint64_t)], &fileref->parent->fcb->subvol->id, sizeof(uint64_t)); flefii->FileNameLength = fileref->dc->name.Length / sizeof(WCHAR); RtlCopyMemory(flefii->FileName, fileref->dc->name.Buffer, fileref->dc->name.Length); flfii->EntriesReturned++; len = bytes_needed; } } else { le = fcb->hardlinks.Flink; while (le != &fcb->hardlinks) { hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry); file_ref* parfr; TRACE("parent %I64x, index %I64x, name %.*S\n", hl->parent, hl->index, (int)(hl->name.Length / sizeof(WCHAR)), hl->name.Buffer); Status = open_fileref_by_inode(fcb->Vcb, fcb->subvol, hl->parent, &parfr, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fileref_by_inode returned %08lx\n", Status); } else if (!parfr->deleted) { LIST_ENTRY* le2; bool found = false, deleted = false; UNICODE_STRING* fn = NULL; le2 = parfr->children.Flink; while (le2 != &parfr->children) { file_ref* fr2 = CONTAINING_RECORD(le2, file_ref, list_entry); if (fr2->dc->index == hl->index) { found = true; deleted = fr2->deleted; if (!deleted) fn = &fr2->dc->name; break; } le2 = le2->Flink; } if (!found) fn = &hl->name; if (!deleted) { TRACE("fn = %.*S (found = %u)\n", (int)(fn->Length / sizeof(WCHAR)), fn->Buffer, found); if (flefii) bytes_needed = (LONG)sector_align(bytes_needed, 8); bytes_needed += offsetof(FILE_LINK_ENTRY_FULL_ID_INFORMATION, FileName[0]) + fn->Length; if (bytes_needed > *length) overflow = true; if (!overflow) { if (flefii) { flefii->NextEntryOffset = (ULONG)sector_align(offsetof(FILE_LINK_ENTRY_FULL_ID_INFORMATION, FileName[0]) + (flefii->FileNameLength * sizeof(WCHAR)), 8); flefii = (FILE_LINK_ENTRY_FULL_ID_INFORMATION*)((uint8_t*)flefii + flefii->NextEntryOffset); } else flefii = &flfii->Entry; flefii->NextEntryOffset = 0; RtlCopyMemory(&flefii->ParentFileId.Identifier[0], &parfr->fcb->inode, sizeof(uint64_t)); RtlCopyMemory(&flefii->ParentFileId.Identifier[sizeof(uint64_t)], &parfr->fcb->subvol->id, sizeof(uint64_t)); flefii->FileNameLength = fn->Length / sizeof(WCHAR); RtlCopyMemory(flefii->FileName, fn->Buffer, fn->Length); flfii->EntriesReturned++; len = bytes_needed; } } free_fileref(parfr); } le = le->Flink; } } ExReleaseResourceLite(&fcb->Vcb->fileref_lock); } flfii->BytesNeeded = bytes_needed; *length -= len; Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS; ExReleaseResourceLite(fcb->Header.Resource); return Status; } static NTSTATUS fill_in_file_id_information(FILE_ID_INFORMATION* fii, fcb* fcb, LONG* length) { RtlCopyMemory(&fii->VolumeSerialNumber, &fcb->Vcb->superblock.uuid.uuid[8], sizeof(uint64_t)); RtlCopyMemory(&fii->FileId.Identifier[0], &fcb->inode, sizeof(uint64_t)); RtlCopyMemory(&fii->FileId.Identifier[sizeof(uint64_t)], &fcb->subvol->id, sizeof(uint64_t)); *length -= sizeof(FILE_ID_INFORMATION); return STATUS_SUCCESS; } static NTSTATUS fill_in_file_stat_information(FILE_STAT_INFORMATION* fsi, fcb* fcb, ccb* ccb, LONG* length) { INODE_ITEM* ii; fsi->FileId.QuadPart = make_file_id(fcb->subvol, fcb->inode); if (fcb->ads) ii = &ccb->fileref->parent->fcb->inode_item; else ii = &fcb->inode_item; if (fcb == fcb->Vcb->dummy_fcb) { LARGE_INTEGER time; KeQuerySystemTime(&time); fsi->CreationTime = fsi->LastAccessTime = fsi->LastWriteTime = fsi->ChangeTime = time; } else { fsi->CreationTime.QuadPart = unix_time_to_win(&ii->otime); fsi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime); fsi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime); fsi->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime); } if (fcb->ads) { fsi->AllocationSize.QuadPart = fsi->EndOfFile.QuadPart = fcb->adsdata.Length; fsi->FileAttributes = ccb->fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : ccb->fileref->parent->fcb->atts; } else { fsi->AllocationSize.QuadPart = fcb_alloc_size(fcb); fsi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size; fsi->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts; } if (fcb->type == BTRFS_TYPE_SOCKET) fsi->ReparseTag = IO_REPARSE_TAG_AF_UNIX; else if (fcb->type == BTRFS_TYPE_FIFO) fsi->ReparseTag = IO_REPARSE_TAG_LX_FIFO; else if (fcb->type == BTRFS_TYPE_CHARDEV) fsi->ReparseTag = IO_REPARSE_TAG_LX_CHR; else if (fcb->type == BTRFS_TYPE_BLOCKDEV) fsi->ReparseTag = IO_REPARSE_TAG_LX_BLK; else if (!(fsi->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) fsi->ReparseTag = 0; else fsi->ReparseTag = get_reparse_tag_fcb(fcb); if (fcb->type == BTRFS_TYPE_SOCKET || fcb->type == BTRFS_TYPE_FIFO || fcb->type == BTRFS_TYPE_CHARDEV || fcb->type == BTRFS_TYPE_BLOCKDEV) fsi->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT; if (fcb->ads) fsi->NumberOfLinks = ccb->fileref->parent->fcb->inode_item.st_nlink; else fsi->NumberOfLinks = fcb->inode_item.st_nlink; fsi->EffectiveAccess = ccb->access; *length -= sizeof(FILE_STAT_INFORMATION); return STATUS_SUCCESS; } static NTSTATUS fill_in_file_stat_lx_information(FILE_STAT_LX_INFORMATION* fsli, fcb* fcb, ccb* ccb, LONG* length) { INODE_ITEM* ii; fsli->FileId.QuadPart = make_file_id(fcb->subvol, fcb->inode); if (fcb->ads) ii = &ccb->fileref->parent->fcb->inode_item; else ii = &fcb->inode_item; if (fcb == fcb->Vcb->dummy_fcb) { LARGE_INTEGER time; KeQuerySystemTime(&time); fsli->CreationTime = fsli->LastAccessTime = fsli->LastWriteTime = fsli->ChangeTime = time; } else { fsli->CreationTime.QuadPart = unix_time_to_win(&ii->otime); fsli->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime); fsli->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime); fsli->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime); } if (fcb->ads) { fsli->AllocationSize.QuadPart = fsli->EndOfFile.QuadPart = fcb->adsdata.Length; fsli->FileAttributes = ccb->fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : ccb->fileref->parent->fcb->atts; } else { fsli->AllocationSize.QuadPart = fcb_alloc_size(fcb); fsli->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size; fsli->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts; } if (fcb->type == BTRFS_TYPE_SOCKET) fsli->ReparseTag = IO_REPARSE_TAG_AF_UNIX; else if (fcb->type == BTRFS_TYPE_FIFO) fsli->ReparseTag = IO_REPARSE_TAG_LX_FIFO; else if (fcb->type == BTRFS_TYPE_CHARDEV) fsli->ReparseTag = IO_REPARSE_TAG_LX_CHR; else if (fcb->type == BTRFS_TYPE_BLOCKDEV) fsli->ReparseTag = IO_REPARSE_TAG_LX_BLK; else if (!(fsli->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) fsli->ReparseTag = 0; else fsli->ReparseTag = get_reparse_tag_fcb(fcb); if (fcb->type == BTRFS_TYPE_SOCKET || fcb->type == BTRFS_TYPE_FIFO || fcb->type == BTRFS_TYPE_CHARDEV || fcb->type == BTRFS_TYPE_BLOCKDEV) fsli->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT; if (fcb->ads) fsli->NumberOfLinks = ccb->fileref->parent->fcb->inode_item.st_nlink; else fsli->NumberOfLinks = fcb->inode_item.st_nlink; fsli->EffectiveAccess = ccb->access; fsli->LxFlags = LX_FILE_METADATA_HAS_UID | LX_FILE_METADATA_HAS_GID | LX_FILE_METADATA_HAS_MODE | LX_FILE_METADATA_HAS_DEVICE_ID; if (fcb->case_sensitive) fsli->LxFlags |= LX_FILE_CASE_SENSITIVE_DIR; fsli->LxUid = ii->st_uid; fsli->LxGid = ii->st_gid; fsli->LxMode = ii->st_mode; if (ii->st_mode & __S_IFBLK || ii->st_mode & __S_IFCHR) { fsli->LxDeviceIdMajor = (ii->st_rdev & 0xFFFFFFFFFFF00000) >> 20; fsli->LxDeviceIdMinor = (ii->st_rdev & 0xFFFFF); } else { fsli->LxDeviceIdMajor = 0; fsli->LxDeviceIdMinor = 0; } *length -= sizeof(FILE_STAT_LX_INFORMATION); return STATUS_SUCCESS; } static NTSTATUS fill_in_file_case_sensitive_information(FILE_CASE_SENSITIVE_INFORMATION* fcsi, fcb* fcb, LONG* length) { fcsi->Flags = fcb->case_sensitive ? FILE_CS_FLAG_CASE_SENSITIVE_DIR : 0; *length -= sizeof(FILE_CASE_SENSITIVE_INFORMATION); return STATUS_SUCCESS; } static NTSTATUS fill_in_file_compression_information(FILE_COMPRESSION_INFORMATION* fci, LONG* length, fcb* fcb) { *length -= sizeof(FILE_COMPRESSION_INFORMATION); memset(fci, 0, sizeof(FILE_COMPRESSION_INFORMATION)); if (fcb->ads) fci->CompressedFileSize.QuadPart = fcb->adsdata.Length; else if (!S_ISDIR(fcb->inode_item.st_mode)) fci->CompressedFileSize.QuadPart = fcb->inode_item.st_size; return STATUS_SUCCESS; } static NTSTATUS query_info(device_extension* Vcb, PFILE_OBJECT FileObject, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); LONG length = IrpSp->Parameters.QueryFile.Length; fcb* fcb = FileObject->FsContext; ccb* ccb = FileObject->FsContext2; file_ref* fileref = ccb ? ccb->fileref : NULL; NTSTATUS Status; TRACE("(%p, %p, %p)\n", Vcb, FileObject, Irp); TRACE("fcb = %p\n", fcb); if (fcb == Vcb->volume_fcb) return STATUS_INVALID_PARAMETER; if (!ccb) { ERR("ccb is NULL\n"); return STATUS_INVALID_PARAMETER; } switch (IrpSp->Parameters.QueryFile.FileInformationClass) { case FileAllInformation: { FILE_ALL_INFORMATION* fai = Irp->AssociatedIrp.SystemBuffer; INODE_ITEM* ii; TRACE("FileAllInformation\n"); if (Irp->RequestorMode != KernelMode && !(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; goto exit; } if (fcb->ads) { if (!fileref || !fileref->parent) { ERR("no fileref for stream\n"); Status = STATUS_INTERNAL_ERROR; goto exit; } ii = &fileref->parent->fcb->inode_item; } else ii = &fcb->inode_item; // Access, mode, and alignment are all filled in by the kernel if (length > 0) fill_in_file_basic_information(&fai->BasicInformation, ii, &length, fcb, fileref); if (length > 0) fill_in_file_standard_information(&fai->StandardInformation, fcb, fileref, &length); if (length > 0) fill_in_file_internal_information(&fai->InternalInformation, fcb, &length); if (length > 0) fill_in_file_ea_information(&fai->EaInformation, fcb, &length); length -= sizeof(FILE_ACCESS_INFORMATION); if (length > 0) fill_in_file_position_information(&fai->PositionInformation, FileObject, &length); length -= sizeof(FILE_MODE_INFORMATION); length -= sizeof(FILE_ALIGNMENT_INFORMATION); if (length > 0) fill_in_file_name_information(&fai->NameInformation, fcb, fileref, &length); Status = STATUS_SUCCESS; break; } case FileAttributeTagInformation: { FILE_ATTRIBUTE_TAG_INFORMATION* ati = Irp->AssociatedIrp.SystemBuffer; TRACE("FileAttributeTagInformation\n"); if (Irp->RequestorMode != KernelMode && !(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; goto exit; } ExAcquireResourceSharedLite(&Vcb->tree_lock, true); Status = fill_in_file_attribute_information(ati, fcb, ccb, &length); ExReleaseResourceLite(&Vcb->tree_lock); break; } case FileBasicInformation: { FILE_BASIC_INFORMATION* fbi = Irp->AssociatedIrp.SystemBuffer; INODE_ITEM* ii; TRACE("FileBasicInformation\n"); if (Irp->RequestorMode != KernelMode && !(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; goto exit; } if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_BASIC_INFORMATION)) { WARN("overflow\n"); Status = STATUS_BUFFER_OVERFLOW; goto exit; } if (fcb->ads) { if (!fileref || !fileref->parent) { ERR("no fileref for stream\n"); Status = STATUS_INTERNAL_ERROR; goto exit; } ii = &fileref->parent->fcb->inode_item; } else ii = &fcb->inode_item; Status = fill_in_file_basic_information(fbi, ii, &length, fcb, fileref); break; } case FileCompressionInformation: { FILE_COMPRESSION_INFORMATION* fci = Irp->AssociatedIrp.SystemBuffer; TRACE("FileCompressionInformation\n"); Status = fill_in_file_compression_information(fci, &length, fcb); break; } case FileEaInformation: { FILE_EA_INFORMATION* eai = Irp->AssociatedIrp.SystemBuffer; TRACE("FileEaInformation\n"); Status = fill_in_file_ea_information(eai, fcb, &length); break; } case FileInternalInformation: { FILE_INTERNAL_INFORMATION* fii = Irp->AssociatedIrp.SystemBuffer; TRACE("FileInternalInformation\n"); Status = fill_in_file_internal_information(fii, fcb, &length); break; } case FileNameInformation: { FILE_NAME_INFORMATION* fni = Irp->AssociatedIrp.SystemBuffer; TRACE("FileNameInformation\n"); Status = fill_in_file_name_information(fni, fcb, fileref, &length); break; } case FileNetworkOpenInformation: { FILE_NETWORK_OPEN_INFORMATION* fnoi = Irp->AssociatedIrp.SystemBuffer; TRACE("FileNetworkOpenInformation\n"); if (Irp->RequestorMode != KernelMode && !(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; goto exit; } Status = fill_in_file_network_open_information(fnoi, fcb, fileref, &length); break; } case FilePositionInformation: { FILE_POSITION_INFORMATION* fpi = Irp->AssociatedIrp.SystemBuffer; TRACE("FilePositionInformation\n"); Status = fill_in_file_position_information(fpi, FileObject, &length); break; } case FileStandardInformation: { FILE_STANDARD_INFORMATION* fsi = Irp->AssociatedIrp.SystemBuffer; TRACE("FileStandardInformation\n"); if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_STANDARD_INFORMATION)) { WARN("overflow\n"); Status = STATUS_BUFFER_OVERFLOW; goto exit; } Status = fill_in_file_standard_information(fsi, fcb, ccb->fileref, &length); break; } case FileStreamInformation: { FILE_STREAM_INFORMATION* fsi = Irp->AssociatedIrp.SystemBuffer; TRACE("FileStreamInformation\n"); Status = fill_in_file_stream_information(fsi, fileref, &length); break; } case FileHardLinkInformation: { FILE_LINKS_INFORMATION* fli = Irp->AssociatedIrp.SystemBuffer; TRACE("FileHardLinkInformation\n"); ExAcquireResourceSharedLite(&Vcb->tree_lock, true); Status = fill_in_hard_link_information(fli, fileref, Irp, &length); ExReleaseResourceLite(&Vcb->tree_lock); break; } case FileNormalizedNameInformation: { FILE_NAME_INFORMATION* fni = Irp->AssociatedIrp.SystemBuffer; TRACE("FileNormalizedNameInformation\n"); Status = fill_in_file_name_information(fni, fcb, fileref, &length); break; } case FileStandardLinkInformation: { FILE_STANDARD_LINK_INFORMATION* fsli = Irp->AssociatedIrp.SystemBuffer; TRACE("FileStandardLinkInformation\n"); Status = fill_in_file_standard_link_information(fsli, fcb, ccb->fileref, &length); break; } case FileRemoteProtocolInformation: TRACE("FileRemoteProtocolInformation\n"); Status = STATUS_INVALID_PARAMETER; goto exit; #ifndef _MSC_VER #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch" #endif case FileIdInformation: { FILE_ID_INFORMATION* fii = Irp->AssociatedIrp.SystemBuffer; if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_ID_INFORMATION)) { WARN("overflow\n"); Status = STATUS_BUFFER_OVERFLOW; goto exit; } TRACE("FileIdInformation\n"); Status = fill_in_file_id_information(fii, fcb, &length); break; } case FileStatInformation: { FILE_STAT_INFORMATION* fsi = Irp->AssociatedIrp.SystemBuffer; if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_STAT_INFORMATION)) { WARN("overflow\n"); Status = STATUS_BUFFER_OVERFLOW; goto exit; } TRACE("FileStatInformation\n"); Status = fill_in_file_stat_information(fsi, fcb, ccb, &length); break; } case FileStatLxInformation: { FILE_STAT_LX_INFORMATION* fsli = Irp->AssociatedIrp.SystemBuffer; if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_STAT_LX_INFORMATION)) { WARN("overflow\n"); Status = STATUS_BUFFER_OVERFLOW; goto exit; } TRACE("FileStatLxInformation\n"); Status = fill_in_file_stat_lx_information(fsli, fcb, ccb, &length); break; } case FileCaseSensitiveInformation: { FILE_CASE_SENSITIVE_INFORMATION* fcsi = Irp->AssociatedIrp.SystemBuffer; if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_CASE_SENSITIVE_INFORMATION)) { WARN("overflow\n"); Status = STATUS_BUFFER_OVERFLOW; goto exit; } TRACE("FileCaseSensitiveInformation\n"); Status = fill_in_file_case_sensitive_information(fcsi, fcb, &length); break; } case FileHardLinkFullIdInformation: { FILE_LINKS_FULL_ID_INFORMATION* flfii = Irp->AssociatedIrp.SystemBuffer; TRACE("FileHardLinkFullIdInformation\n"); ExAcquireResourceSharedLite(&Vcb->tree_lock, true); Status = fill_in_hard_link_full_id_information(flfii, fileref, Irp, &length); ExReleaseResourceLite(&Vcb->tree_lock); break; } #ifndef _MSC_VER #pragma GCC diagnostic pop #endif default: WARN("unknown FileInformationClass %u\n", IrpSp->Parameters.QueryFile.FileInformationClass); Status = STATUS_INVALID_PARAMETER; goto exit; } if (length < 0) { length = 0; Status = STATUS_BUFFER_OVERFLOW; } Irp->IoStatus.Information = IrpSp->Parameters.QueryFile.Length - length; exit: TRACE("query_info returning %08lx\n", Status); return Status; } _Dispatch_type_(IRP_MJ_QUERY_INFORMATION) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_query_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { PIO_STACK_LOCATION IrpSp; NTSTATUS Status; fcb* fcb; device_extension* Vcb = DeviceObject->DeviceExtension; bool top_level; FsRtlEnterFileSystem(); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = STATUS_INVALID_DEVICE_REQUEST; goto end; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } Irp->IoStatus.Information = 0; TRACE("query information\n"); IrpSp = IoGetCurrentIrpStackLocation(Irp); fcb = IrpSp->FileObject->FsContext; TRACE("fcb = %p\n", fcb); TRACE("fcb->subvol = %p\n", fcb->subvol); Status = query_info(fcb->Vcb, IrpSp->FileObject, Irp); end: TRACE("returning %08lx\n", Status); Irp->IoStatus.Status = Status; IoCompleteRequest( Irp, IO_NO_INCREMENT ); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } _Dispatch_type_(IRP_MJ_QUERY_EA) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_query_ea(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS Status; bool top_level; device_extension* Vcb = DeviceObject->DeviceExtension; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; fcb* fcb; ccb* ccb; FILE_FULL_EA_INFORMATION* ffei; ULONG retlen = 0; FsRtlEnterFileSystem(); TRACE("(%p, %p)\n", DeviceObject, Irp); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = STATUS_INVALID_DEVICE_REQUEST; goto end; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } ffei = map_user_buffer(Irp, NormalPagePriority); if (!ffei) { ERR("could not get output buffer\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (!FileObject) { ERR("no file object\n"); Status = STATUS_INVALID_PARAMETER; goto end; } fcb = FileObject->FsContext; if (!fcb) { ERR("no fcb\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (fcb == fcb->Vcb->volume_fcb) { Status = STATUS_INVALID_PARAMETER; goto end; } ccb = FileObject->FsContext2; if (!ccb) { ERR("no ccb\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_READ_EA | FILE_WRITE_EA))) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; goto end; } if (fcb->ads) fcb = ccb->fileref->parent->fcb; ExAcquireResourceSharedLite(fcb->Header.Resource, true); if (fcb->ea_xattr.Length == 0) { Status = STATUS_NO_EAS_ON_FILE; goto end2; } Status = STATUS_SUCCESS; if (IrpSp->Parameters.QueryEa.EaList) { FILE_FULL_EA_INFORMATION *ea, *out; FILE_GET_EA_INFORMATION* in; uint8_t padding = retlen % 4 > 0 ? (4 - (retlen % 4)) : 0; in = IrpSp->Parameters.QueryEa.EaList; out = NULL; do { bool found = false; STRING s; s.Length = s.MaximumLength = in->EaNameLength; s.Buffer = in->EaName; RtlUpperString(&s, &s); ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer; do { if (ea->EaNameLength == in->EaNameLength && RtlCompareMemory(ea->EaName, in->EaName, ea->EaNameLength) == ea->EaNameLength) { found = true; break; } if (ea->NextEntryOffset == 0) break; ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset); } while (true); if (offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + 1 + ea->EaValueLength > IrpSp->Parameters.QueryEa.Length - retlen - padding) { Status = STATUS_BUFFER_OVERFLOW; retlen = 0; goto end2; } retlen += padding; if (out) { out->NextEntryOffset = (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + out->EaNameLength + 1 + out->EaValueLength + padding; out = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)out) + out->NextEntryOffset); } else out = ffei; out->NextEntryOffset = 0; if (found) { out->Flags = ea->Flags; out->EaNameLength = ea->EaNameLength; out->EaValueLength = ea->EaValueLength; RtlCopyMemory(out->EaName, ea->EaName, ea->EaNameLength + ea->EaValueLength + 1); } else { out->Flags = 0; out->EaNameLength = in->EaNameLength; out->EaValueLength = 0; RtlCopyMemory(out->EaName, in->EaName, in->EaNameLength); out->EaName[in->EaNameLength] = 0; } retlen += (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + out->EaNameLength + 1 + out->EaValueLength; if (IrpSp->Flags & SL_RETURN_SINGLE_ENTRY) break; if (in->NextEntryOffset == 0) break; in = (FILE_GET_EA_INFORMATION*)(((uint8_t*)in) + in->NextEntryOffset); } while (true); } else { FILE_FULL_EA_INFORMATION *ea, *out; ULONG index; if (IrpSp->Flags & SL_INDEX_SPECIFIED) { // The index is 1-based if (IrpSp->Parameters.QueryEa.EaIndex == 0) { Status = STATUS_NONEXISTENT_EA_ENTRY; goto end2; } else index = IrpSp->Parameters.QueryEa.EaIndex - 1; } else if (IrpSp->Flags & SL_RESTART_SCAN) index = ccb->ea_index = 0; else index = ccb->ea_index; ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer; if (index > 0) { ULONG i; for (i = 0; i < index; i++) { if (ea->NextEntryOffset == 0) { // last item Status = STATUS_NO_MORE_EAS; goto end2; } ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset); } } out = NULL; do { uint8_t padding = retlen % 4 > 0 ? (4 - (retlen % 4)) : 0; if (offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + 1 + ea->EaValueLength > IrpSp->Parameters.QueryEa.Length - retlen - padding) { Status = retlen == 0 ? STATUS_BUFFER_TOO_SMALL : STATUS_BUFFER_OVERFLOW; goto end2; } retlen += padding; if (out) { out->NextEntryOffset = (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + out->EaNameLength + 1 + out->EaValueLength + padding; out = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)out) + out->NextEntryOffset); } else out = ffei; out->NextEntryOffset = 0; out->Flags = ea->Flags; out->EaNameLength = ea->EaNameLength; out->EaValueLength = ea->EaValueLength; RtlCopyMemory(out->EaName, ea->EaName, ea->EaNameLength + ea->EaValueLength + 1); retlen += (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + 1 + ea->EaValueLength; if (!(IrpSp->Flags & SL_INDEX_SPECIFIED)) ccb->ea_index++; if (ea->NextEntryOffset == 0 || IrpSp->Flags & SL_RETURN_SINGLE_ENTRY) break; ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset); } while (true); } end2: ExReleaseResourceLite(fcb->Header.Resource); end: TRACE("returning %08lx\n", Status); Irp->IoStatus.Status = Status; Irp->IoStatus.Information = NT_SUCCESS(Status) || Status == STATUS_BUFFER_OVERFLOW ? retlen : 0; IoCompleteRequest( Irp, IO_NO_INCREMENT ); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } _Dispatch_type_(IRP_MJ_SET_EA) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_set_ea(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { device_extension* Vcb = DeviceObject->DeviceExtension; NTSTATUS Status; bool top_level; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; fcb* fcb; ccb* ccb; file_ref* fileref; FILE_FULL_EA_INFORMATION* ffei; ULONG offset; LIST_ENTRY ealist; ea_item* item; FILE_FULL_EA_INFORMATION* ea; LIST_ENTRY* le; LARGE_INTEGER time; BTRFS_TIME now; FsRtlEnterFileSystem(); TRACE("(%p, %p)\n", DeviceObject, Irp); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = STATUS_INVALID_DEVICE_REQUEST; goto end; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } if (Vcb->readonly) { Status = STATUS_MEDIA_WRITE_PROTECTED; goto end; } ffei = map_user_buffer(Irp, NormalPagePriority); if (!ffei) { ERR("could not get output buffer\n"); Status = STATUS_INVALID_PARAMETER; goto end; } Status = IoCheckEaBufferValidity(ffei, IrpSp->Parameters.SetEa.Length, &offset); if (!NT_SUCCESS(Status)) { ERR("IoCheckEaBufferValidity returned %08lx (error at offset %lu)\n", Status, offset); goto end; } if (!FileObject) { ERR("no file object\n"); Status = STATUS_INVALID_PARAMETER; goto end; } fcb = FileObject->FsContext; if (!fcb) { ERR("no fcb\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (fcb == fcb->Vcb->volume_fcb) { Status = STATUS_INVALID_PARAMETER; goto end; } ccb = FileObject->FsContext2; if (!ccb) { ERR("no ccb\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_EA)) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; goto end; } if (fcb->ads) { fileref = ccb->fileref->parent; fcb = fileref->fcb; } else fileref = ccb->fileref; InitializeListHead(&ealist); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (fcb->ea_xattr.Length > 0) { ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer; do { item = ExAllocatePoolWithTag(PagedPool, sizeof(ea_item), ALLOC_TAG); if (!item) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end2; } item->name.Length = item->name.MaximumLength = ea->EaNameLength; item->name.Buffer = ea->EaName; item->value.Length = item->value.MaximumLength = ea->EaValueLength; item->value.Buffer = &ea->EaName[ea->EaNameLength + 1]; item->flags = ea->Flags; InsertTailList(&ealist, &item->list_entry); if (ea->NextEntryOffset == 0) break; ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset); } while (true); } ea = ffei; do { STRING s; bool found = false; s.Length = s.MaximumLength = ea->EaNameLength; s.Buffer = ea->EaName; RtlUpperString(&s, &s); le = ealist.Flink; while (le != &ealist) { item = CONTAINING_RECORD(le, ea_item, list_entry); if (item->name.Length == s.Length && RtlCompareMemory(item->name.Buffer, s.Buffer, s.Length) == s.Length) { item->flags = ea->Flags; item->value.Length = item->value.MaximumLength = ea->EaValueLength; item->value.Buffer = &ea->EaName[ea->EaNameLength + 1]; found = true; break; } le = le->Flink; } if (!found) { item = ExAllocatePoolWithTag(PagedPool, sizeof(ea_item), ALLOC_TAG); if (!item) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end2; } item->name.Length = item->name.MaximumLength = ea->EaNameLength; item->name.Buffer = ea->EaName; item->value.Length = item->value.MaximumLength = ea->EaValueLength; item->value.Buffer = &ea->EaName[ea->EaNameLength + 1]; item->flags = ea->Flags; InsertTailList(&ealist, &item->list_entry); } if (ea->NextEntryOffset == 0) break; ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset); } while (true); // remove entries with zero-length value le = ealist.Flink; while (le != &ealist) { LIST_ENTRY* le2 = le->Flink; item = CONTAINING_RECORD(le, ea_item, list_entry); if (item->value.Length == 0) { RemoveEntryList(&item->list_entry); ExFreePool(item); } le = le2; } // handle LXSS values le = ealist.Flink; while (le != &ealist) { LIST_ENTRY* le2 = le->Flink; item = CONTAINING_RECORD(le, ea_item, list_entry); if (item->name.Length == sizeof(lxuid) - 1 && RtlCompareMemory(item->name.Buffer, lxuid, item->name.Length) == item->name.Length) { if (item->value.Length < sizeof(uint32_t)) { ERR("uid value was shorter than expected\n"); Status = STATUS_INVALID_PARAMETER; goto end2; } if (Irp->RequestorMode == KernelMode || ccb->access & FILE_WRITE_ATTRIBUTES) { RtlCopyMemory(&fcb->inode_item.st_uid, item->value.Buffer, sizeof(uint32_t)); fcb->sd_dirty = true; fcb->sd_deleted = false; } RemoveEntryList(&item->list_entry); ExFreePool(item); } else if (item->name.Length == sizeof(lxgid) - 1 && RtlCompareMemory(item->name.Buffer, lxgid, item->name.Length) == item->name.Length) { if (item->value.Length < sizeof(uint32_t)) { ERR("gid value was shorter than expected\n"); Status = STATUS_INVALID_PARAMETER; goto end2; } if (Irp->RequestorMode == KernelMode || ccb->access & FILE_WRITE_ATTRIBUTES) RtlCopyMemory(&fcb->inode_item.st_gid, item->value.Buffer, sizeof(uint32_t)); RemoveEntryList(&item->list_entry); ExFreePool(item); } else if (item->name.Length == sizeof(lxmod) - 1 && RtlCompareMemory(item->name.Buffer, lxmod, item->name.Length) == item->name.Length) { if (item->value.Length < sizeof(uint32_t)) { ERR("mode value was shorter than expected\n"); Status = STATUS_INVALID_PARAMETER; goto end2; } if (Irp->RequestorMode == KernelMode || ccb->access & FILE_WRITE_ATTRIBUTES) { 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; uint32_t val; RtlCopyMemory(&val, item->value.Buffer, sizeof(uint32_t)); fcb->inode_item.st_mode &= ~allowed; fcb->inode_item.st_mode |= val & allowed; } RemoveEntryList(&item->list_entry); ExFreePool(item); } le = le2; } if (IsListEmpty(&ealist)) { fcb->ealen = 0; if (fcb->ea_xattr.Buffer) ExFreePool(fcb->ea_xattr.Buffer); fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = 0; fcb->ea_xattr.Buffer = NULL; } else { uint16_t size = 0; char *buf, *oldbuf; le = ealist.Flink; while (le != &ealist) { item = CONTAINING_RECORD(le, ea_item, list_entry); if (size % 4 > 0) size += 4 - (size % 4); size += (uint16_t)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + item->name.Length + 1 + item->value.Length; le = le->Flink; } buf = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG); if (!buf) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end2; } oldbuf = fcb->ea_xattr.Buffer; fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = size; fcb->ea_xattr.Buffer = buf; fcb->ealen = 4; ea = NULL; le = ealist.Flink; while (le != &ealist) { item = CONTAINING_RECORD(le, ea_item, list_entry); if (ea) { ea->NextEntryOffset = (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + ea->EaValueLength; if (ea->NextEntryOffset % 4 > 0) ea->NextEntryOffset += 4 - (ea->NextEntryOffset % 4); ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset); } else ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer; ea->NextEntryOffset = 0; ea->Flags = item->flags; ea->EaNameLength = (UCHAR)item->name.Length; ea->EaValueLength = item->value.Length; RtlCopyMemory(ea->EaName, item->name.Buffer, item->name.Length); ea->EaName[item->name.Length] = 0; RtlCopyMemory(&ea->EaName[item->name.Length + 1], item->value.Buffer, item->value.Length); fcb->ealen += 5 + item->name.Length + item->value.Length; le = le->Flink; } if (oldbuf) ExFreePool(oldbuf); } fcb->ea_changed = true; KeQuerySystemTime(&time); win_time_to_unix(time, &now); fcb->inode_item.transid = Vcb->superblock.generation; fcb->inode_item.sequence++; if (!ccb->user_set_change_time) fcb->inode_item.st_ctime = now; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); send_notification_fileref(fileref, FILE_NOTIFY_CHANGE_EA, FILE_ACTION_MODIFIED, NULL); Status = STATUS_SUCCESS; end2: ExReleaseResourceLite(fcb->Header.Resource); while (!IsListEmpty(&ealist)) { le = RemoveHeadList(&ealist); item = CONTAINING_RECORD(le, ea_item, list_entry); ExFreePool(item); } end: TRACE("returning %08lx\n", Status); Irp->IoStatus.Status = Status; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } ================================================ FILE: src/flushthread.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include "zstd/lib/common/xxhash.h" #include "crc32c.h" #include #include #include /* cf. __MAX_CSUM_ITEMS in Linux - it needs sizeof(leaf_node) bytes free * so it can do a split. Linux tries to get it so a run will fit in a * sector, but the MAX_CSUM_ITEMS logic is wrong... */ #define MAX_CSUM_SIZE (4096 - sizeof(tree_header) - (2 * sizeof(leaf_node))) // #define DEBUG_WRITE_LOOPS #define BATCH_ITEM_LIMIT 1000 typedef struct { KEVENT Event; IO_STATUS_BLOCK iosb; } write_context; typedef struct { EXTENT_ITEM_TREE eit; uint8_t type; TREE_BLOCK_REF tbr; } EXTENT_ITEM_TREE2; typedef struct { EXTENT_ITEM ei; uint8_t type; TREE_BLOCK_REF tbr; } EXTENT_ITEM_SKINNY_METADATA; static NTSTATUS create_chunk(device_extension* Vcb, chunk* c, PIRP Irp); static NTSTATUS update_tree_extents(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback); static NTSTATUS insert_tree_item_batch(LIST_ENTRY* batchlist, device_extension* Vcb, root* r, uint64_t objid, uint8_t objtype, uint64_t offset, _In_opt_ _When_(return >= 0, __drv_aliasesMem) void* data, uint16_t datalen, enum batch_operation operation); _Function_class_(IO_COMPLETION_ROUTINE) static NTSTATUS __stdcall write_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) { write_context* context = conptr; UNUSED(DeviceObject); context->iosb = Irp->IoStatus; KeSetEvent(&context->Event, 0, false); return STATUS_MORE_PROCESSING_REQUIRED; } NTSTATUS write_data_phys(_In_ PDEVICE_OBJECT device, _In_ PFILE_OBJECT fileobj, _In_ uint64_t address, _In_reads_bytes_(length) void* data, _In_ uint32_t length) { NTSTATUS Status; LARGE_INTEGER offset; PIRP Irp; PIO_STACK_LOCATION IrpSp; write_context context; TRACE("(%p, %I64x, %p, %x)\n", device, address, data, length); RtlZeroMemory(&context, sizeof(write_context)); KeInitializeEvent(&context.Event, NotificationEvent, false); offset.QuadPart = address; Irp = IoAllocateIrp(device->StackSize, false); if (!Irp) { ERR("IoAllocateIrp failed\n"); return STATUS_INSUFFICIENT_RESOURCES; } IrpSp = IoGetNextIrpStackLocation(Irp); IrpSp->MajorFunction = IRP_MJ_WRITE; IrpSp->FileObject = fileobj; if (device->Flags & DO_BUFFERED_IO) { Irp->AssociatedIrp.SystemBuffer = data; Irp->Flags = IRP_BUFFERED_IO; } else if (device->Flags & DO_DIRECT_IO) { Irp->MdlAddress = IoAllocateMdl(data, length, false, false, NULL); if (!Irp->MdlAddress) { DbgPrint("IoAllocateMdl failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Status = STATUS_SUCCESS; try { MmProbeAndLockPages(Irp->MdlAddress, KernelMode, IoReadAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); IoFreeMdl(Irp->MdlAddress); goto exit; } } else { Irp->UserBuffer = data; } IrpSp->Parameters.Write.Length = length; IrpSp->Parameters.Write.ByteOffset = offset; Irp->UserIosb = &context.iosb; Irp->UserEvent = &context.Event; IoSetCompletionRoutine(Irp, write_completion, &context, true, true, true); Status = IoCallDriver(device, Irp); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL); Status = context.iosb.Status; } if (!NT_SUCCESS(Status)) { ERR("IoCallDriver returned %08lx\n", Status); } if (device->Flags & DO_DIRECT_IO) { MmUnlockPages(Irp->MdlAddress); IoFreeMdl(Irp->MdlAddress); } exit: IoFreeIrp(Irp); return Status; } static void add_trim_entry(device* dev, uint64_t address, uint64_t size) { space* s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG); if (!s) { ERR("out of memory\n"); return; } s->address = address; s->size = size; dev->num_trim_entries++; InsertTailList(&dev->trim_list, &s->list_entry); } static void clean_space_cache_chunk(device_extension* Vcb, chunk* c) { LIST_ENTRY* le; ULONG type; if (c->chunk_item->type & BLOCK_FLAG_DUPLICATE) type = BLOCK_FLAG_DUPLICATE; else if (c->chunk_item->type & BLOCK_FLAG_RAID0) type = BLOCK_FLAG_RAID0; else if (c->chunk_item->type & BLOCK_FLAG_RAID1) type = BLOCK_FLAG_DUPLICATE; else if (c->chunk_item->type & BLOCK_FLAG_RAID10) type = BLOCK_FLAG_RAID10; else if (c->chunk_item->type & BLOCK_FLAG_RAID5) type = BLOCK_FLAG_RAID5; else if (c->chunk_item->type & BLOCK_FLAG_RAID6) type = BLOCK_FLAG_RAID6; else if (c->chunk_item->type & BLOCK_FLAG_RAID1C3) type = BLOCK_FLAG_DUPLICATE; else if (c->chunk_item->type & BLOCK_FLAG_RAID1C4) type = BLOCK_FLAG_DUPLICATE; else // SINGLE type = BLOCK_FLAG_DUPLICATE; le = c->deleting.Flink; while (le != &c->deleting) { space* s = CONTAINING_RECORD(le, space, list_entry); if (!Vcb->options.no_barrier || !(c->chunk_item->type & BLOCK_FLAG_METADATA)) { CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; if (type == BLOCK_FLAG_DUPLICATE) { uint16_t i; for (i = 0; i < c->chunk_item->num_stripes; i++) { if (c->devices[i] && c->devices[i]->devobj && !c->devices[i]->readonly && c->devices[i]->trim) add_trim_entry(c->devices[i], s->address - c->offset + cis[i].offset, s->size); } } else if (type == BLOCK_FLAG_RAID0) { uint64_t startoff, endoff; uint16_t startoffstripe, endoffstripe, i; get_raid0_offset(s->address - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &startoff, &startoffstripe); get_raid0_offset(s->address - c->offset + s->size - 1, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &endoff, &endoffstripe); for (i = 0; i < c->chunk_item->num_stripes; i++) { if (c->devices[i] && c->devices[i]->devobj && !c->devices[i]->readonly && c->devices[i]->trim) { uint64_t stripestart, stripeend; if (startoffstripe > i) stripestart = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; else if (startoffstripe == i) stripestart = startoff; else stripestart = startoff - (startoff % c->chunk_item->stripe_length); if (endoffstripe > i) stripeend = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; else if (endoffstripe == i) stripeend = endoff + 1; else stripeend = endoff - (endoff % c->chunk_item->stripe_length); if (stripestart != stripeend) add_trim_entry(c->devices[i], stripestart + cis[i].offset, stripeend - stripestart); } } } else if (type == BLOCK_FLAG_RAID10) { uint64_t startoff, endoff; uint16_t sub_stripes, startoffstripe, endoffstripe, i; sub_stripes = max(1, c->chunk_item->sub_stripes); get_raid0_offset(s->address - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes / sub_stripes, &startoff, &startoffstripe); get_raid0_offset(s->address - c->offset + s->size - 1, c->chunk_item->stripe_length, c->chunk_item->num_stripes / sub_stripes, &endoff, &endoffstripe); startoffstripe *= sub_stripes; endoffstripe *= sub_stripes; for (i = 0; i < c->chunk_item->num_stripes; i += sub_stripes) { ULONG j; uint64_t stripestart, stripeend; if (startoffstripe > i) stripestart = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; else if (startoffstripe == i) stripestart = startoff; else stripestart = startoff - (startoff % c->chunk_item->stripe_length); if (endoffstripe > i) stripeend = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; else if (endoffstripe == i) stripeend = endoff + 1; else stripeend = endoff - (endoff % c->chunk_item->stripe_length); if (stripestart != stripeend) { for (j = 0; j < sub_stripes; j++) { if (c->devices[i+j] && c->devices[i+j]->devobj && !c->devices[i+j]->readonly && c->devices[i+j]->trim) add_trim_entry(c->devices[i+j], stripestart + cis[i+j].offset, stripeend - stripestart); } } } } // FIXME - RAID5(?), RAID6(?) } le = le->Flink; } } typedef struct { DEVICE_MANAGE_DATA_SET_ATTRIBUTES* dmdsa; ATA_PASS_THROUGH_EX apte; PIRP Irp; IO_STATUS_BLOCK iosb; #ifdef DEBUG_TRIM_EMULATION PMDL mdl; void* buf; #endif } ioctl_context_stripe; typedef struct { KEVENT Event; LONG left; ioctl_context_stripe* stripes; } ioctl_context; _Function_class_(IO_COMPLETION_ROUTINE) static NTSTATUS __stdcall ioctl_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) { ioctl_context* context = (ioctl_context*)conptr; LONG left2 = InterlockedDecrement(&context->left); UNUSED(DeviceObject); UNUSED(Irp); if (left2 == 0) KeSetEvent(&context->Event, 0, false); return STATUS_MORE_PROCESSING_REQUIRED; } #ifdef DEBUG_TRIM_EMULATION static void trim_emulation(device* dev) { LIST_ENTRY* le; ioctl_context context; unsigned int i = 0, count = 0; le = dev->trim_list.Flink; while (le != &dev->trim_list) { count++; le = le->Flink; } context.left = count; KeInitializeEvent(&context.Event, NotificationEvent, false); context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(ioctl_context_stripe) * context.left, ALLOC_TAG); if (!context.stripes) { ERR("out of memory\n"); return; } RtlZeroMemory(context.stripes, sizeof(ioctl_context_stripe) * context.left); i = 0; le = dev->trim_list.Flink; while (le != &dev->trim_list) { ioctl_context_stripe* stripe = &context.stripes[i]; space* s = CONTAINING_RECORD(le, space, list_entry); WARN("(%I64x, %I64x)\n", s->address, s->size); stripe->Irp = IoAllocateIrp(dev->devobj->StackSize, false); if (!stripe->Irp) { ERR("IoAllocateIrp failed\n"); } else { PIO_STACK_LOCATION IrpSp = IoGetNextIrpStackLocation(stripe->Irp); IrpSp->MajorFunction = IRP_MJ_WRITE; IrpSp->FileObject = dev->fileobj; stripe->buf = ExAllocatePoolWithTag(NonPagedPool, (uint32_t)s->size, ALLOC_TAG); if (!stripe->buf) { ERR("out of memory\n"); } else { RtlZeroMemory(stripe->buf, (uint32_t)s->size); // FIXME - randomize instead? stripe->mdl = IoAllocateMdl(stripe->buf, (uint32_t)s->size, false, false, NULL); if (!stripe->mdl) { ERR("IoAllocateMdl failed\n"); } else { MmBuildMdlForNonPagedPool(stripe->mdl); stripe->Irp->MdlAddress = stripe->mdl; IrpSp->Parameters.Write.ByteOffset.QuadPart = s->address; IrpSp->Parameters.Write.Length = s->size; stripe->Irp->UserIosb = &stripe->iosb; IoSetCompletionRoutine(stripe->Irp, ioctl_completion, &context, true, true, true); IoCallDriver(dev->devobj, stripe->Irp); } } } i++; le = le->Flink; } KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL); for (i = 0; i < count; i++) { ioctl_context_stripe* stripe = &context.stripes[i]; if (stripe->mdl) IoFreeMdl(stripe->mdl); if (stripe->buf) ExFreePool(stripe->buf); } ExFreePool(context.stripes); } #endif static void clean_space_cache(device_extension* Vcb) { LIST_ENTRY* le; chunk* c; #ifndef DEBUG_TRIM_EMULATION ULONG num; #endif TRACE("(%p)\n", Vcb); ExAcquireResourceSharedLite(&Vcb->chunk_lock, true); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { c = CONTAINING_RECORD(le, chunk, list_entry); if (c->space_changed) { acquire_chunk_lock(c, Vcb); if (c->space_changed) { if (Vcb->trim && !Vcb->options.no_trim) clean_space_cache_chunk(Vcb, c); space_list_merge(&c->space, &c->space_size, &c->deleting); while (!IsListEmpty(&c->deleting)) { space* s = CONTAINING_RECORD(RemoveHeadList(&c->deleting), space, list_entry); ExFreePool(s); } } c->space_changed = false; release_chunk_lock(c, Vcb); } le = le->Flink; } ExReleaseResourceLite(&Vcb->chunk_lock); if (Vcb->trim && !Vcb->options.no_trim) { #ifndef DEBUG_TRIM_EMULATION ioctl_context context; ULONG total_num; context.left = 0; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (dev->devobj && !dev->readonly && dev->trim && dev->num_trim_entries > 0) context.left++; le = le->Flink; } if (context.left == 0) return; total_num = context.left; num = 0; KeInitializeEvent(&context.Event, NotificationEvent, false); context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(ioctl_context_stripe) * context.left, ALLOC_TAG); if (!context.stripes) { ERR("out of memory\n"); return; } RtlZeroMemory(context.stripes, sizeof(ioctl_context_stripe) * context.left); #endif le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (dev->devobj && !dev->readonly && dev->trim && dev->num_trim_entries > 0) { #ifdef DEBUG_TRIM_EMULATION trim_emulation(dev); #else LIST_ENTRY* le2; ioctl_context_stripe* stripe = &context.stripes[num]; DEVICE_DATA_SET_RANGE* ranges; ULONG datalen = (ULONG)sector_align(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), sizeof(uint64_t)) + (dev->num_trim_entries * sizeof(DEVICE_DATA_SET_RANGE)), i; PIO_STACK_LOCATION IrpSp; stripe->dmdsa = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG); if (!stripe->dmdsa) { ERR("out of memory\n"); goto nextdev; } stripe->dmdsa->Size = sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES); stripe->dmdsa->Action = DeviceDsmAction_Trim; stripe->dmdsa->Flags = DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED; stripe->dmdsa->ParameterBlockOffset = 0; stripe->dmdsa->ParameterBlockLength = 0; stripe->dmdsa->DataSetRangesOffset = (ULONG)sector_align(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), sizeof(uint64_t)); stripe->dmdsa->DataSetRangesLength = dev->num_trim_entries * sizeof(DEVICE_DATA_SET_RANGE); ranges = (DEVICE_DATA_SET_RANGE*)((uint8_t*)stripe->dmdsa + stripe->dmdsa->DataSetRangesOffset); i = 0; le2 = dev->trim_list.Flink; while (le2 != &dev->trim_list) { space* s = CONTAINING_RECORD(le2, space, list_entry); ranges[i].StartingOffset = s->address; ranges[i].LengthInBytes = s->size; i++; le2 = le2->Flink; } stripe->Irp = IoAllocateIrp(dev->devobj->StackSize, false); if (!stripe->Irp) { ERR("IoAllocateIrp failed\n"); goto nextdev; } IrpSp = IoGetNextIrpStackLocation(stripe->Irp); IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL; IrpSp->FileObject = dev->fileobj; IrpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES; IrpSp->Parameters.DeviceIoControl.InputBufferLength = datalen; IrpSp->Parameters.DeviceIoControl.OutputBufferLength = 0; stripe->Irp->AssociatedIrp.SystemBuffer = stripe->dmdsa; stripe->Irp->Flags |= IRP_BUFFERED_IO; stripe->Irp->UserBuffer = NULL; stripe->Irp->UserIosb = &stripe->iosb; IoSetCompletionRoutine(stripe->Irp, ioctl_completion, &context, true, true, true); IoCallDriver(dev->devobj, stripe->Irp); nextdev: #endif while (!IsListEmpty(&dev->trim_list)) { space* s = CONTAINING_RECORD(RemoveHeadList(&dev->trim_list), space, list_entry); ExFreePool(s); } dev->num_trim_entries = 0; #ifndef DEBUG_TRIM_EMULATION num++; #endif } le = le->Flink; } #ifndef DEBUG_TRIM_EMULATION KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL); for (num = 0; num < total_num; num++) { if (context.stripes[num].dmdsa) ExFreePool(context.stripes[num].dmdsa); if (context.stripes[num].Irp) IoFreeIrp(context.stripes[num].Irp); } ExFreePool(context.stripes); #endif } } static bool trees_consistent(device_extension* Vcb) { ULONG maxsize = Vcb->superblock.node_size - sizeof(tree_header); LIST_ENTRY* le; le = Vcb->trees.Flink; while (le != &Vcb->trees) { tree* t = CONTAINING_RECORD(le, tree, list_entry); if (t->write) { if (t->header.num_items == 0 && t->parent) { #ifdef DEBUG_WRITE_LOOPS ERR("empty tree found, looping again\n"); #endif return false; } if (t->size > maxsize) { #ifdef DEBUG_WRITE_LOOPS ERR("overlarge tree found (%u > %u), looping again\n", t->size, maxsize); #endif return false; } if (!t->has_new_address) { #ifdef DEBUG_WRITE_LOOPS ERR("tree found without new address, looping again\n"); #endif return false; } } le = le->Flink; } return true; } static NTSTATUS add_parents(device_extension* Vcb, PIRP Irp) { ULONG level; LIST_ENTRY* le; for (level = 0; level <= 255; level++) { bool nothing_found = true; TRACE("level = %lu\n", level); le = Vcb->trees.Flink; while (le != &Vcb->trees) { tree* t = CONTAINING_RECORD(le, tree, list_entry); if (t->write && t->header.level == level) { TRACE("tree %p: root = %I64x, level = %x, parent = %p\n", t, t->header.tree_id, t->header.level, t->parent); nothing_found = false; if (t->parent) { if (!t->parent->write) TRACE("adding tree %p (level %x)\n", t->parent, t->header.level); t->parent->write = true; } else if (t->root != Vcb->root_root && t->root != Vcb->chunk_root) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; searchkey.obj_id = t->root->id; searchkey.obj_type = TYPE_ROOT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { ERR("could not find ROOT_ITEM for tree %I64x\n", searchkey.obj_id); return STATUS_INTERNAL_ERROR; } if (tp.item->size < sizeof(ROOT_ITEM)) { // if not full length, delete and create new entry ROOT_ITEM* ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG); if (!ri) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(ri, &t->root->root_item, sizeof(ROOT_ITEM)); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); ExFreePool(ri); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(ri); return Status; } } tree* t2 = tp.tree; while (t2) { t2->write = true; t2 = t2->parent; } } } le = le->Flink; } if (nothing_found) break; } return STATUS_SUCCESS; } static void add_parents_to_cache(tree* t) { while (t->parent) { t = t->parent; t->write = true; } } static 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) { NTSTATUS Status; EXTENT_ITEM_SKINNY_METADATA* eism; traverse_ptr insert_tp; eism = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_ITEM_SKINNY_METADATA), ALLOC_TAG); if (!eism) { ERR("out of memory\n"); return false; } eism->ei.refcount = 1; eism->ei.generation = Vcb->superblock.generation; eism->ei.flags = EXTENT_ITEM_TREE_BLOCK; eism->type = TYPE_TREE_BLOCK_REF; eism->tbr.offset = root_id; Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_METADATA_ITEM, level, eism, sizeof(EXTENT_ITEM_SKINNY_METADATA), &insert_tp, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(eism); return false; } acquire_chunk_lock(c, Vcb); space_list_subtract(c, address, Vcb->superblock.node_size, rollback); release_chunk_lock(c, Vcb); add_parents_to_cache(insert_tp.tree); return true; } bool find_metadata_address_in_chunk(device_extension* Vcb, chunk* c, uint64_t* address) { LIST_ENTRY* le; space* s; TRACE("(%p, %I64x, %p)\n", Vcb, c->offset, address); if (Vcb->superblock.node_size > c->chunk_item->size - c->used) return false; if (!c->cache_loaded) { NTSTATUS Status = load_cache_chunk(Vcb, c, NULL); if (!NT_SUCCESS(Status)) { ERR("load_cache_chunk returned %08lx\n", Status); return false; } } if (IsListEmpty(&c->space_size)) return false; if (!c->last_alloc_set) { s = CONTAINING_RECORD(c->space.Blink, space, list_entry); c->last_alloc = s->address; c->last_alloc_set = true; if (s->size >= Vcb->superblock.node_size) { *address = s->address; c->last_alloc += Vcb->superblock.node_size; return true; } } le = c->space.Flink; while (le != &c->space) { s = CONTAINING_RECORD(le, space, list_entry); if (s->address <= c->last_alloc && s->address + s->size >= c->last_alloc + Vcb->superblock.node_size) { *address = c->last_alloc; c->last_alloc += Vcb->superblock.node_size; return true; } le = le->Flink; } le = c->space_size.Flink; while (le != &c->space_size) { s = CONTAINING_RECORD(le, space, list_entry_size); if (s->size == Vcb->superblock.node_size) { *address = s->address; c->last_alloc = s->address + Vcb->superblock.node_size; return true; } else if (s->size < Vcb->superblock.node_size) { if (le == c->space_size.Flink) return false; s = CONTAINING_RECORD(le->Blink, space, list_entry_size); *address = s->address; c->last_alloc = s->address + Vcb->superblock.node_size; return true; } le = le->Flink; } s = CONTAINING_RECORD(c->space_size.Blink, space, list_entry_size); if (s->size > Vcb->superblock.node_size) { *address = s->address; c->last_alloc = s->address + Vcb->superblock.node_size; return true; } return false; } static 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) { NTSTATUS Status; uint64_t address; EXTENT_ITEM_TREE2* eit2; traverse_ptr insert_tp; TRACE("(%p, %x, %I64x, %p, %p, %p, %p)\n", Vcb, level, root_id, c, new_address, Irp, rollback); if (!find_metadata_address_in_chunk(Vcb, c, &address)) return false; if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA) { bool b = insert_tree_extent_skinny(Vcb, level, root_id, c, address, Irp, rollback); if (b) *new_address = address; return b; } eit2 = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_ITEM_TREE2), ALLOC_TAG); if (!eit2) { ERR("out of memory\n"); return false; } eit2->eit.extent_item.refcount = 1; eit2->eit.extent_item.generation = Vcb->superblock.generation; eit2->eit.extent_item.flags = EXTENT_ITEM_TREE_BLOCK; eit2->eit.level = level; eit2->type = TYPE_TREE_BLOCK_REF; eit2->tbr.offset = root_id; Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, Vcb->superblock.node_size, eit2, sizeof(EXTENT_ITEM_TREE2), &insert_tp, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(eit2); return false; } acquire_chunk_lock(c, Vcb); space_list_subtract(c, address, Vcb->superblock.node_size, rollback); release_chunk_lock(c, Vcb); add_parents_to_cache(insert_tp.tree); *new_address = address; return true; } NTSTATUS get_tree_new_address(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; chunk *origchunk = NULL, *c; LIST_ENTRY* le; uint64_t flags, addr; if (t->root->id == BTRFS_ROOT_CHUNK) flags = Vcb->system_flags; else flags = Vcb->metadata_flags; if (t->has_address) { origchunk = get_chunk_from_address(Vcb, t->header.address); if (origchunk && !origchunk->readonly && !origchunk->reloc && origchunk->chunk_item->type == flags && insert_tree_extent(Vcb, t->header.level, t->root->id, origchunk, &addr, Irp, rollback)) { t->new_address = addr; t->has_new_address = true; return STATUS_SUCCESS; } } ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { c = CONTAINING_RECORD(le, chunk, list_entry); if (!c->readonly && !c->reloc) { acquire_chunk_lock(c, Vcb); if (c != origchunk && c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= Vcb->superblock.node_size) { if (insert_tree_extent(Vcb, t->header.level, t->root->id, c, &addr, Irp, rollback)) { release_chunk_lock(c, Vcb); ExReleaseResourceLite(&Vcb->chunk_lock); t->new_address = addr; t->has_new_address = true; return STATUS_SUCCESS; } } release_chunk_lock(c, Vcb); } le = le->Flink; } // allocate new chunk if necessary Status = alloc_chunk(Vcb, flags, &c, false); if (!NT_SUCCESS(Status)) { ERR("alloc_chunk returned %08lx\n", Status); ExReleaseResourceLite(&Vcb->chunk_lock); return Status; } acquire_chunk_lock(c, Vcb); if ((c->chunk_item->size - c->used) >= Vcb->superblock.node_size) { if (insert_tree_extent(Vcb, t->header.level, t->root->id, c, &addr, Irp, rollback)) { release_chunk_lock(c, Vcb); ExReleaseResourceLite(&Vcb->chunk_lock); t->new_address = addr; t->has_new_address = true; return STATUS_SUCCESS; } } release_chunk_lock(c, Vcb); ExReleaseResourceLite(&Vcb->chunk_lock); ERR("couldn't find any metadata chunks with %x bytes free\n", Vcb->superblock.node_size); return STATUS_DISK_FULL; } static NTSTATUS reduce_tree_extent(device_extension* Vcb, uint64_t address, tree* t, uint64_t parent_root, uint8_t level, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; uint64_t rc, root; TRACE("(%p, %I64x, %p)\n", Vcb, address, t); rc = get_extent_refcount(Vcb, address, Vcb->superblock.node_size, Irp); if (rc == 0) { ERR("error - refcount for extent %I64x was 0\n", address); return STATUS_INTERNAL_ERROR; } if (!t || t->parent) root = parent_root; else root = t->header.tree_id; Status = decrease_extent_refcount_tree(Vcb, address, Vcb->superblock.node_size, root, level, Irp); if (!NT_SUCCESS(Status)) { ERR("decrease_extent_refcount_tree returned %08lx\n", Status); return Status; } if (rc == 1) { chunk* c = get_chunk_from_address(Vcb, address); if (c) { acquire_chunk_lock(c, Vcb); if (!c->cache_loaded) { Status = load_cache_chunk(Vcb, c, NULL); if (!NT_SUCCESS(Status)) { ERR("load_cache_chunk returned %08lx\n", Status); release_chunk_lock(c, Vcb); return Status; } } c->used -= Vcb->superblock.node_size; space_list_add(c, address, Vcb->superblock.node_size, rollback); release_chunk_lock(c, Vcb); } else ERR("could not find chunk for address %I64x\n", address); } return STATUS_SUCCESS; } static NTSTATUS add_changed_extent_ref_edr(changed_extent* ce, EXTENT_DATA_REF* edr, bool old) { LIST_ENTRY *le2, *list; changed_extent_ref* cer; list = old ? &ce->old_refs : &ce->refs; le2 = list->Flink; while (le2 != list) { cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry); if (cer->type == TYPE_EXTENT_DATA_REF && cer->edr.root == edr->root && cer->edr.objid == edr->objid && cer->edr.offset == edr->offset) { cer->edr.count += edr->count; goto end; } le2 = le2->Flink; } cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG); if (!cer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } cer->type = TYPE_EXTENT_DATA_REF; RtlCopyMemory(&cer->edr, edr, sizeof(EXTENT_DATA_REF)); InsertTailList(list, &cer->list_entry); end: if (old) ce->old_count += edr->count; else ce->count += edr->count; return STATUS_SUCCESS; } static NTSTATUS add_changed_extent_ref_sdr(changed_extent* ce, SHARED_DATA_REF* sdr, bool old) { LIST_ENTRY *le2, *list; changed_extent_ref* cer; list = old ? &ce->old_refs : &ce->refs; le2 = list->Flink; while (le2 != list) { cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry); if (cer->type == TYPE_SHARED_DATA_REF && cer->sdr.offset == sdr->offset) { cer->sdr.count += sdr->count; goto end; } le2 = le2->Flink; } cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG); if (!cer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } cer->type = TYPE_SHARED_DATA_REF; RtlCopyMemory(&cer->sdr, sdr, sizeof(SHARED_DATA_REF)); InsertTailList(list, &cer->list_entry); end: if (old) ce->old_count += sdr->count; else ce->count += sdr->count; return STATUS_SUCCESS; } static bool shared_tree_is_unique(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; if (!t->updated_extents && t->has_address) { Status = update_tree_extents(Vcb, t, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("update_tree_extents returned %08lx\n", Status); return false; } } searchkey.obj_id = t->header.address; searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return false; } 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)) return false; else return true; } static NTSTATUS update_tree_extents(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; uint64_t rc = get_extent_refcount(Vcb, t->header.address, Vcb->superblock.node_size, Irp); uint64_t flags = get_extent_flags(Vcb, t->header.address, Irp); if (rc == 0) { ERR("refcount for extent %I64x was 0\n", t->header.address); return STATUS_INTERNAL_ERROR; } if (flags & EXTENT_ITEM_SHARED_BACKREFS || t->header.flags & HEADER_FLAG_SHARED_BACKREF || !(t->header.flags & HEADER_FLAG_MIXED_BACKREF)) { TREE_BLOCK_REF tbr; bool unique = rc > 1 ? false : (t->parent ? shared_tree_is_unique(Vcb, t->parent, Irp, rollback) : false); if (t->header.level == 0) { LIST_ENTRY* le; le = t->itemlist.Flink; while (le != &t->itemlist) { tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry); if (!td->inserted && td->key.obj_type == TYPE_EXTENT_DATA && td->size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) { EXTENT_DATA* ed = (EXTENT_DATA*)td->data; if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; if (ed2->size > 0) { EXTENT_DATA_REF edr; changed_extent* ce = NULL; chunk* c = get_chunk_from_address(Vcb, ed2->address); if (c) { LIST_ENTRY* le2; le2 = c->changed_extents.Flink; while (le2 != &c->changed_extents) { changed_extent* ce2 = CONTAINING_RECORD(le2, changed_extent, list_entry); if (ce2->address == ed2->address) { ce = ce2; break; } le2 = le2->Flink; } } edr.root = t->root->id; edr.objid = td->key.obj_id; edr.offset = td->key.offset - ed2->offset; edr.count = 1; if (ce) { Status = add_changed_extent_ref_edr(ce, &edr, true); if (!NT_SUCCESS(Status)) { ERR("add_changed_extent_ref_edr returned %08lx\n", Status); return Status; } Status = add_changed_extent_ref_edr(ce, &edr, false); if (!NT_SUCCESS(Status)) { ERR("add_changed_extent_ref_edr returned %08lx\n", Status); return Status; } } Status = increase_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, Irp); if (!NT_SUCCESS(Status)) { ERR("increase_extent_refcount returned %08lx\n", Status); return Status; } if ((flags & EXTENT_ITEM_SHARED_BACKREFS && unique) || !(t->header.flags & HEADER_FLAG_MIXED_BACKREF)) { uint64_t sdrrc = find_extent_shared_data_refcount(Vcb, ed2->address, t->header.address, Irp); if (sdrrc > 0) { SHARED_DATA_REF sdr; sdr.offset = t->header.address; sdr.count = 1; Status = decrease_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_SHARED_DATA_REF, &sdr, NULL, 0, t->header.address, ce ? ce->superseded : false, Irp); if (!NT_SUCCESS(Status)) { ERR("decrease_extent_refcount returned %08lx\n", Status); return Status; } if (ce) { LIST_ENTRY* le2; le2 = ce->refs.Flink; while (le2 != &ce->refs) { changed_extent_ref* cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry); if (cer->type == TYPE_SHARED_DATA_REF && cer->sdr.offset == sdr.offset) { ce->count--; cer->sdr.count--; break; } le2 = le2->Flink; } le2 = ce->old_refs.Flink; while (le2 != &ce->old_refs) { changed_extent_ref* cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry); if (cer->type == TYPE_SHARED_DATA_REF && cer->sdr.offset == sdr.offset) { ce->old_count--; if (cer->sdr.count > 1) cer->sdr.count--; else { RemoveEntryList(&cer->list_entry); ExFreePool(cer); } break; } le2 = le2->Flink; } } } } // FIXME - clear shared flag if unique? } } } le = le->Flink; } } else { LIST_ENTRY* le; le = t->itemlist.Flink; while (le != &t->itemlist) { tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry); if (!td->inserted) { tbr.offset = t->root->id; Status = increase_extent_refcount(Vcb, td->treeholder.address, Vcb->superblock.node_size, TYPE_TREE_BLOCK_REF, &tbr, &td->key, t->header.level - 1, Irp); if (!NT_SUCCESS(Status)) { ERR("increase_extent_refcount returned %08lx\n", Status); return Status; } if (unique || !(t->header.flags & HEADER_FLAG_MIXED_BACKREF)) { uint64_t sbrrc = find_extent_shared_tree_refcount(Vcb, td->treeholder.address, t->header.address, Irp); if (sbrrc > 0) { SHARED_BLOCK_REF sbr; sbr.offset = t->header.address; Status = decrease_extent_refcount(Vcb, td->treeholder.address, Vcb->superblock.node_size, TYPE_SHARED_BLOCK_REF, &sbr, NULL, 0, t->header.address, false, Irp); if (!NT_SUCCESS(Status)) { ERR("decrease_extent_refcount returned %08lx\n", Status); return Status; } } } // FIXME - clear shared flag if unique? } le = le->Flink; } } if (unique) { uint64_t sbrrc = find_extent_shared_tree_refcount(Vcb, t->header.address, t->parent->header.address, Irp); if (sbrrc == 1) { SHARED_BLOCK_REF sbr; sbr.offset = t->parent->header.address; Status = decrease_extent_refcount(Vcb, t->header.address, Vcb->superblock.node_size, TYPE_SHARED_BLOCK_REF, &sbr, NULL, 0, t->parent->header.address, false, Irp); if (!NT_SUCCESS(Status)) { ERR("decrease_extent_refcount returned %08lx\n", Status); return Status; } } } if (t->parent) tbr.offset = t->parent->header.tree_id; else tbr.offset = t->header.tree_id; Status = increase_extent_refcount(Vcb, t->header.address, Vcb->superblock.node_size, TYPE_TREE_BLOCK_REF, &tbr, t->parent ? &t->paritem->key : NULL, t->header.level, Irp); if (!NT_SUCCESS(Status)) { ERR("increase_extent_refcount returned %08lx\n", Status); return Status; } // FIXME - clear shared flag if unique? t->header.flags &= ~HEADER_FLAG_SHARED_BACKREF; } if (rc > 1 || t->header.tree_id == t->root->id) { 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); if (!NT_SUCCESS(Status)) { ERR("reduce_tree_extent returned %08lx\n", Status); return Status; } } t->has_address = false; if ((rc > 1 || t->header.tree_id != t->root->id) && !(flags & EXTENT_ITEM_SHARED_BACKREFS)) { if (t->header.tree_id == t->root->id) { flags |= EXTENT_ITEM_SHARED_BACKREFS; update_extent_flags(Vcb, t->header.address, flags, Irp); } if (t->header.level > 0) { LIST_ENTRY* le; le = t->itemlist.Flink; while (le != &t->itemlist) { tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry); if (!td->inserted) { if (t->header.tree_id == t->root->id) { SHARED_BLOCK_REF sbr; sbr.offset = t->header.address; Status = increase_extent_refcount(Vcb, td->treeholder.address, Vcb->superblock.node_size, TYPE_SHARED_BLOCK_REF, &sbr, &td->key, t->header.level - 1, Irp); } else { TREE_BLOCK_REF tbr; tbr.offset = t->root->id; Status = increase_extent_refcount(Vcb, td->treeholder.address, Vcb->superblock.node_size, TYPE_TREE_BLOCK_REF, &tbr, &td->key, t->header.level - 1, Irp); } if (!NT_SUCCESS(Status)) { ERR("increase_extent_refcount returned %08lx\n", Status); return Status; } } le = le->Flink; } } else { LIST_ENTRY* le; le = t->itemlist.Flink; while (le != &t->itemlist) { tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry); if (!td->inserted && td->key.obj_type == TYPE_EXTENT_DATA && td->size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) { EXTENT_DATA* ed = (EXTENT_DATA*)td->data; if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; if (ed2->size > 0) { changed_extent* ce = NULL; chunk* c = get_chunk_from_address(Vcb, ed2->address); if (c) { LIST_ENTRY* le2; le2 = c->changed_extents.Flink; while (le2 != &c->changed_extents) { changed_extent* ce2 = CONTAINING_RECORD(le2, changed_extent, list_entry); if (ce2->address == ed2->address) { ce = ce2; break; } le2 = le2->Flink; } } if (t->header.tree_id == t->root->id) { SHARED_DATA_REF sdr; sdr.offset = t->header.address; sdr.count = 1; if (ce) { Status = add_changed_extent_ref_sdr(ce, &sdr, true); if (!NT_SUCCESS(Status)) { ERR("add_changed_extent_ref_edr returned %08lx\n", Status); return Status; } Status = add_changed_extent_ref_sdr(ce, &sdr, false); if (!NT_SUCCESS(Status)) { ERR("add_changed_extent_ref_edr returned %08lx\n", Status); return Status; } } Status = increase_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_SHARED_DATA_REF, &sdr, NULL, 0, Irp); } else { EXTENT_DATA_REF edr; edr.root = t->root->id; edr.objid = td->key.obj_id; edr.offset = td->key.offset - ed2->offset; edr.count = 1; if (ce) { Status = add_changed_extent_ref_edr(ce, &edr, true); if (!NT_SUCCESS(Status)) { ERR("add_changed_extent_ref_edr returned %08lx\n", Status); return Status; } Status = add_changed_extent_ref_edr(ce, &edr, false); if (!NT_SUCCESS(Status)) { ERR("add_changed_extent_ref_edr returned %08lx\n", Status); return Status; } } Status = increase_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, Irp); } if (!NT_SUCCESS(Status)) { ERR("increase_extent_refcount returned %08lx\n", Status); return Status; } } } } le = le->Flink; } } } t->updated_extents = true; t->header.tree_id = t->root->id; return STATUS_SUCCESS; } static NTSTATUS allocate_tree_extents(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) { LIST_ENTRY* le; NTSTATUS Status; bool changed = false; uint8_t max_level = 0, level; TRACE("(%p)\n", Vcb); le = Vcb->trees.Flink; while (le != &Vcb->trees) { tree* t = CONTAINING_RECORD(le, tree, list_entry); if (t->write && !t->has_new_address) { chunk* c; if (t->has_address) { c = get_chunk_from_address(Vcb, t->header.address); if (c) { if (!c->cache_loaded) { acquire_chunk_lock(c, Vcb); if (!c->cache_loaded) { Status = load_cache_chunk(Vcb, c, NULL); if (!NT_SUCCESS(Status)) { ERR("load_cache_chunk returned %08lx\n", Status); release_chunk_lock(c, Vcb); return Status; } } release_chunk_lock(c, Vcb); } } } Status = get_tree_new_address(Vcb, t, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("get_tree_new_address returned %08lx\n", Status); return Status; } TRACE("allocated extent %I64x\n", t->new_address); c = get_chunk_from_address(Vcb, t->new_address); if (c) c->used += Vcb->superblock.node_size; else { ERR("could not find chunk for address %I64x\n", t->new_address); return STATUS_INTERNAL_ERROR; } changed = true; if (t->header.level > max_level) max_level = t->header.level; } le = le->Flink; } if (!changed) return STATUS_SUCCESS; level = max_level; do { le = Vcb->trees.Flink; while (le != &Vcb->trees) { tree* t = CONTAINING_RECORD(le, tree, list_entry); if (t->write && !t->updated_extents && t->has_address && t->header.level == level) { Status = update_tree_extents(Vcb, t, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("update_tree_extents returned %08lx\n", Status); return Status; } } le = le->Flink; } if (level == 0) break; level--; } while (true); return STATUS_SUCCESS; } static NTSTATUS update_root_root(device_extension* Vcb, bool no_cache, PIRP Irp, LIST_ENTRY* rollback) { LIST_ENTRY* le; NTSTATUS Status; TRACE("(%p)\n", Vcb); le = Vcb->trees.Flink; while (le != &Vcb->trees) { tree* t = CONTAINING_RECORD(le, tree, list_entry); if (t->write && !t->parent) { if (t->root != Vcb->root_root && t->root != Vcb->chunk_root) { KEY searchkey; traverse_ptr tp; searchkey.obj_id = t->root->id; searchkey.obj_type = TYPE_ROOT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { ERR("could not find ROOT_ITEM for tree %I64x\n", searchkey.obj_id); return STATUS_INTERNAL_ERROR; } TRACE("updating the address for root %I64x to %I64x\n", searchkey.obj_id, t->new_address); t->root->root_item.block_number = t->new_address; t->root->root_item.root_level = t->header.level; t->root->root_item.generation = Vcb->superblock.generation; t->root->root_item.generation2 = Vcb->superblock.generation; // item is guaranteed to be at least sizeof(ROOT_ITEM), due to add_parents RtlCopyMemory(tp.item->data, &t->root->root_item, sizeof(ROOT_ITEM)); } t->root->treeholder.address = t->new_address; t->root->treeholder.generation = Vcb->superblock.generation; } le = le->Flink; } if (!no_cache && !(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE)) { ExAcquireResourceSharedLite(&Vcb->chunk_lock, true); Status = update_chunk_caches(Vcb, Irp, rollback); ExReleaseResourceLite(&Vcb->chunk_lock); if (!NT_SUCCESS(Status)) { ERR("update_chunk_caches returned %08lx\n", Status); return Status; } } return STATUS_SUCCESS; } NTSTATUS do_tree_writes(device_extension* Vcb, LIST_ENTRY* tree_writes, bool no_free) { chunk* c; LIST_ENTRY* le; tree_write* tw; NTSTATUS Status; ULONG i, num_bits; write_data_context* wtc; ULONG bit_num = 0; bool raid56 = false; // merge together runs c = NULL; le = tree_writes->Flink; while (le != tree_writes) { tw = CONTAINING_RECORD(le, tree_write, list_entry); if (!c || tw->address < c->offset || tw->address >= c->offset + c->chunk_item->size) c = get_chunk_from_address(Vcb, tw->address); else { tree_write* tw2 = CONTAINING_RECORD(le->Blink, tree_write, list_entry); if (tw->address == tw2->address + tw2->length) { uint8_t* data = ExAllocatePoolWithTag(NonPagedPool, tw2->length + tw->length, ALLOC_TAG); if (!data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(data, tw2->data, tw2->length); RtlCopyMemory(&data[tw2->length], tw->data, tw->length); if (!no_free || tw2->allocated) ExFreePool(tw2->data); tw2->data = data; tw2->length += tw->length; tw2->allocated = true; if (!no_free || tw->allocated) ExFreePool(tw->data); RemoveEntryList(&tw->list_entry); ExFreePool(tw); le = tw2->list_entry.Flink; continue; } } tw->c = c; if (c->chunk_item->type & (BLOCK_FLAG_RAID5 | BLOCK_FLAG_RAID6)) raid56 = true; le = le->Flink; } num_bits = 0; le = tree_writes->Flink; while (le != tree_writes) { tw = CONTAINING_RECORD(le, tree_write, list_entry); num_bits++; le = le->Flink; } wtc = ExAllocatePoolWithTag(NonPagedPool, sizeof(write_data_context) * num_bits, ALLOC_TAG); if (!wtc) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } le = tree_writes->Flink; while (le != tree_writes) { tw = CONTAINING_RECORD(le, tree_write, list_entry); TRACE("address: %I64x, size: %x\n", tw->address, tw->length); KeInitializeEvent(&wtc[bit_num].Event, NotificationEvent, false); InitializeListHead(&wtc[bit_num].stripes); wtc[bit_num].need_wait = false; wtc[bit_num].stripes_left = 0; wtc[bit_num].parity1 = wtc[bit_num].parity2 = wtc[bit_num].scratch = NULL; wtc[bit_num].mdl = wtc[bit_num].parity1_mdl = wtc[bit_num].parity2_mdl = NULL; Status = write_data(Vcb, tw->address, tw->data, tw->length, &wtc[bit_num], NULL, NULL, false, 0, HighPagePriority); if (!NT_SUCCESS(Status)) { ERR("write_data returned %08lx\n", Status); for (i = 0; i < num_bits; i++) { free_write_data_stripes(&wtc[i]); } ExFreePool(wtc); return Status; } bit_num++; le = le->Flink; } for (i = 0; i < num_bits; i++) { if (wtc[i].stripes.Flink != &wtc[i].stripes) { // launch writes and wait le = wtc[i].stripes.Flink; while (le != &wtc[i].stripes) { write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry); if (stripe->status != WriteDataStatus_Ignore) { wtc[i].need_wait = true; IoCallDriver(stripe->device->devobj, stripe->Irp); } le = le->Flink; } } } for (i = 0; i < num_bits; i++) { if (wtc[i].need_wait) KeWaitForSingleObject(&wtc[i].Event, Executive, KernelMode, false, NULL); } for (i = 0; i < num_bits; i++) { le = wtc[i].stripes.Flink; while (le != &wtc[i].stripes) { write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry); if (stripe->status != WriteDataStatus_Ignore && !NT_SUCCESS(stripe->iosb.Status)) { Status = stripe->iosb.Status; log_device_error(Vcb, stripe->device, BTRFS_DEV_STAT_WRITE_ERRORS); break; } le = le->Flink; } free_write_data_stripes(&wtc[i]); } ExFreePool(wtc); if (raid56) { c = NULL; le = tree_writes->Flink; while (le != tree_writes) { tw = CONTAINING_RECORD(le, tree_write, list_entry); if (tw->c != c) { c = tw->c; ExAcquireResourceExclusiveLite(&c->partial_stripes_lock, true); while (!IsListEmpty(&c->partial_stripes)) { partial_stripe* ps = CONTAINING_RECORD(RemoveHeadList(&c->partial_stripes), partial_stripe, list_entry); Status = flush_partial_stripe(Vcb, c, ps); if (ps->bmparr) ExFreePool(ps->bmparr); ExFreePool(ps); if (!NT_SUCCESS(Status)) { ERR("flush_partial_stripe returned %08lx\n", Status); ExReleaseResourceLite(&c->partial_stripes_lock); return Status; } } ExReleaseResourceLite(&c->partial_stripes_lock); } le = le->Flink; } } return STATUS_SUCCESS; } void calc_tree_checksum(device_extension* Vcb, tree_header* th) { switch (Vcb->superblock.csum_type) { case CSUM_TYPE_CRC32C: *((uint32_t*)th) = ~calc_crc32c(0xffffffff, (uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum)); break; case CSUM_TYPE_XXHASH: *((uint64_t*)th) = XXH64((uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum), 0); break; case CSUM_TYPE_SHA256: calc_sha256((uint8_t*)th, &th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum)); break; case CSUM_TYPE_BLAKE2: blake2b((uint8_t*)th, BLAKE2_HASH_SIZE, &th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum)); break; } } static NTSTATUS write_trees(device_extension* Vcb, PIRP Irp) { ULONG level; uint8_t *data, *body; NTSTATUS Status; LIST_ENTRY* le; LIST_ENTRY tree_writes; tree_write* tw; TRACE("(%p)\n", Vcb); InitializeListHead(&tree_writes); for (level = 0; level <= 255; level++) { bool nothing_found = true; TRACE("level = %lu\n", level); le = Vcb->trees.Flink; while (le != &Vcb->trees) { tree* t = CONTAINING_RECORD(le, tree, list_entry); if (t->write && t->header.level == level) { KEY firstitem, searchkey; LIST_ENTRY* le2; traverse_ptr tp; if (!t->has_new_address) { ERR("error - tried to write tree with no new address\n"); return STATUS_INTERNAL_ERROR; } le2 = t->itemlist.Flink; while (le2 != &t->itemlist) { tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry); if (!td->ignore) { firstitem = td->key; break; } le2 = le2->Flink; } if (t->parent) { t->paritem->key = firstitem; t->paritem->treeholder.address = t->new_address; t->paritem->treeholder.generation = Vcb->superblock.generation; } if (!(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) { EXTENT_ITEM_TREE* eit; searchkey.obj_id = t->new_address; searchkey.obj_type = TYPE_EXTENT_ITEM; searchkey.offset = Vcb->superblock.node_size; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (keycmp(searchkey, tp.item->key)) { 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); return STATUS_INTERNAL_ERROR; } if (tp.item->size < sizeof(EXTENT_ITEM_TREE)) { 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)); return STATUS_INTERNAL_ERROR; } eit = (EXTENT_ITEM_TREE*)tp.item->data; eit->firstitem = firstitem; } nothing_found = false; } le = le->Flink; } if (nothing_found) break; } TRACE("allocated tree extents\n"); le = Vcb->trees.Flink; while (le != &Vcb->trees) { tree* t = CONTAINING_RECORD(le, tree, list_entry); LIST_ENTRY* le2; #ifdef DEBUG_PARANOID uint32_t num_items = 0, size = 0; bool crash = false; #endif if (t->write) { #ifdef DEBUG_PARANOID bool first = true; KEY lastkey; le2 = t->itemlist.Flink; while (le2 != &t->itemlist) { tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry); if (!td->ignore) { num_items++; if (!first) { if (keycmp(td->key, lastkey) == 0) { ERR("(%I64x,%x,%I64x): duplicate key\n", td->key.obj_id, td->key.obj_type, td->key.offset); crash = true; } else if (keycmp(td->key, lastkey) == -1) { ERR("(%I64x,%x,%I64x): key out of order\n", td->key.obj_id, td->key.obj_type, td->key.offset); crash = true; } } else first = false; lastkey = td->key; if (t->header.level == 0) size += td->size; } le2 = le2->Flink; } if (t->header.level == 0) size += num_items * sizeof(leaf_node); else size += num_items * sizeof(internal_node); if (num_items != t->header.num_items) { ERR("tree %I64x, level %x: num_items was %x, expected %x\n", t->root->id, t->header.level, num_items, t->header.num_items); crash = true; } if (size != t->size) { ERR("tree %I64x, level %x: size was %x, expected %x\n", t->root->id, t->header.level, size, t->size); crash = true; } if (t->header.num_items == 0 && t->parent) { ERR("tree %I64x, level %x: tried to write empty tree with parent\n", t->root->id, t->header.level); crash = true; } if (t->size > Vcb->superblock.node_size - sizeof(tree_header)) { 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)); crash = true; } if (crash) { ERR("tree %p\n", t); le2 = t->itemlist.Flink; while (le2 != &t->itemlist) { tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry); if (!td->ignore) { ERR("%I64x,%x,%I64x inserted=%u\n", td->key.obj_id, td->key.obj_type, td->key.offset, td->inserted); } le2 = le2->Flink; } int3; } #endif t->header.address = t->new_address; t->header.generation = Vcb->superblock.generation; t->header.tree_id = t->root->id; t->header.flags |= HEADER_FLAG_MIXED_BACKREF; t->header.fs_uuid = Vcb->superblock.metadata_uuid; t->has_address = true; data = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size, ALLOC_TAG); if (!data) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } body = data + sizeof(tree_header); RtlCopyMemory(data, &t->header, sizeof(tree_header)); RtlZeroMemory(body, Vcb->superblock.node_size - sizeof(tree_header)); if (t->header.level == 0) { leaf_node* itemptr = (leaf_node*)body; int i = 0; uint8_t* dataptr = data + Vcb->superblock.node_size; le2 = t->itemlist.Flink; while (le2 != &t->itemlist) { tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry); if (!td->ignore) { dataptr = dataptr - td->size; itemptr[i].key = td->key; itemptr[i].offset = (uint32_t)((uint8_t*)dataptr - (uint8_t*)body); itemptr[i].size = td->size; i++; if (td->size > 0) RtlCopyMemory(dataptr, td->data, td->size); } le2 = le2->Flink; } } else { internal_node* itemptr = (internal_node*)body; int i = 0; le2 = t->itemlist.Flink; while (le2 != &t->itemlist) { tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry); if (!td->ignore) { itemptr[i].key = td->key; itemptr[i].address = td->treeholder.address; itemptr[i].generation = td->treeholder.generation; i++; } le2 = le2->Flink; } } calc_tree_checksum(Vcb, (tree_header*)data); tw = ExAllocatePoolWithTag(PagedPool, sizeof(tree_write), ALLOC_TAG); if (!tw) { ERR("out of memory\n"); ExFreePool(data); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } tw->address = t->new_address; tw->length = Vcb->superblock.node_size; tw->data = data; tw->allocated = false; if (IsListEmpty(&tree_writes)) InsertTailList(&tree_writes, &tw->list_entry); else { bool inserted = false; le2 = tree_writes.Flink; while (le2 != &tree_writes) { tree_write* tw2 = CONTAINING_RECORD(le2, tree_write, list_entry); if (tw2->address > tw->address) { InsertHeadList(le2->Blink, &tw->list_entry); inserted = true; break; } le2 = le2->Flink; } if (!inserted) InsertTailList(&tree_writes, &tw->list_entry); } } le = le->Flink; } Status = do_tree_writes(Vcb, &tree_writes, false); if (!NT_SUCCESS(Status)) { ERR("do_tree_writes returned %08lx\n", Status); goto end; } Status = STATUS_SUCCESS; end: while (!IsListEmpty(&tree_writes)) { le = RemoveHeadList(&tree_writes); tw = CONTAINING_RECORD(le, tree_write, list_entry); if (tw->data) ExFreePool(tw->data); ExFreePool(tw); } return Status; } static void update_backup_superblock(device_extension* Vcb, superblock_backup* sb, PIRP Irp) { KEY searchkey; traverse_ptr tp; RtlZeroMemory(sb, sizeof(superblock_backup)); sb->root_tree_addr = Vcb->superblock.root_tree_addr; sb->root_tree_generation = Vcb->superblock.generation; sb->root_level = Vcb->superblock.root_level; sb->chunk_tree_addr = Vcb->superblock.chunk_tree_addr; sb->chunk_tree_generation = Vcb->superblock.chunk_root_generation; sb->chunk_root_level = Vcb->superblock.chunk_root_level; searchkey.obj_id = BTRFS_ROOT_EXTENT; searchkey.obj_type = TYPE_ROOT_ITEM; searchkey.offset = 0xffffffffffffffff; if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp))) { if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) { ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data; sb->extent_tree_addr = ri->block_number; sb->extent_tree_generation = ri->generation; sb->extent_root_level = ri->root_level; } } searchkey.obj_id = BTRFS_ROOT_FSTREE; if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp))) { if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) { ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data; sb->fs_tree_addr = ri->block_number; sb->fs_tree_generation = ri->generation; sb->fs_root_level = ri->root_level; } } searchkey.obj_id = BTRFS_ROOT_DEVTREE; if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp))) { if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) { ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data; sb->dev_root_addr = ri->block_number; sb->dev_root_generation = ri->generation; sb->dev_root_level = ri->root_level; } } searchkey.obj_id = BTRFS_ROOT_CHECKSUM; if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp))) { if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) { ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data; sb->csum_root_addr = ri->block_number; sb->csum_root_generation = ri->generation; sb->csum_root_level = ri->root_level; } } sb->total_bytes = Vcb->superblock.total_bytes; sb->bytes_used = Vcb->superblock.bytes_used; sb->num_devices = Vcb->superblock.num_devices; } typedef struct { void* context; uint8_t* buf; PMDL mdl; device* device; NTSTATUS Status; PIRP Irp; LIST_ENTRY list_entry; } write_superblocks_stripe; typedef struct _write_superblocks_context { KEVENT Event; LIST_ENTRY stripes; LONG left; } write_superblocks_context; _Function_class_(IO_COMPLETION_ROUTINE) static NTSTATUS __stdcall write_superblock_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) { write_superblocks_stripe* stripe = conptr; write_superblocks_context* context = stripe->context; UNUSED(DeviceObject); stripe->Status = Irp->IoStatus.Status; if (InterlockedDecrement(&context->left) == 0) KeSetEvent(&context->Event, 0, false); return STATUS_MORE_PROCESSING_REQUIRED; } static void calc_superblock_checksum(superblock* sb) { switch (sb->csum_type) { case CSUM_TYPE_CRC32C: *(uint32_t*)sb = ~calc_crc32c(0xffffffff, (uint8_t*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum)); break; case CSUM_TYPE_XXHASH: *(uint64_t*)sb = XXH64(&sb->uuid, sizeof(superblock) - sizeof(sb->checksum), 0); break; case CSUM_TYPE_SHA256: calc_sha256((uint8_t*)sb, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum)); break; case CSUM_TYPE_BLAKE2: blake2b((uint8_t*)sb, BLAKE2_HASH_SIZE, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum)); break; } } static NTSTATUS write_superblock(device_extension* Vcb, device* device, write_superblocks_context* context) { unsigned int i = 0; // All the documentation says that the Linux driver only writes one superblock // if it thinks a disk is an SSD, but this doesn't seem to be the case! while (superblock_addrs[i] > 0 && device->devitem.num_bytes >= superblock_addrs[i] + sizeof(superblock)) { ULONG sblen = (ULONG)sector_align(sizeof(superblock), Vcb->superblock.sector_size); superblock* sb; write_superblocks_stripe* stripe; PIO_STACK_LOCATION IrpSp; sb = ExAllocatePoolWithTag(NonPagedPool, sblen, ALLOC_TAG); if (!sb) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(sb, &Vcb->superblock, sizeof(superblock)); if (sblen > sizeof(superblock)) RtlZeroMemory((uint8_t*)sb + sizeof(superblock), sblen - sizeof(superblock)); RtlCopyMemory(&sb->dev_item, &device->devitem, sizeof(DEV_ITEM)); sb->sb_phys_addr = superblock_addrs[i]; calc_superblock_checksum(sb); stripe = ExAllocatePoolWithTag(NonPagedPool, sizeof(write_superblocks_stripe), ALLOC_TAG); if (!stripe) { ERR("out of memory\n"); ExFreePool(sb); return STATUS_INSUFFICIENT_RESOURCES; } stripe->buf = (uint8_t*)sb; stripe->Irp = IoAllocateIrp(device->devobj->StackSize, false); if (!stripe->Irp) { ERR("IoAllocateIrp failed\n"); ExFreePool(stripe); ExFreePool(sb); return STATUS_INSUFFICIENT_RESOURCES; } IrpSp = IoGetNextIrpStackLocation(stripe->Irp); IrpSp->MajorFunction = IRP_MJ_WRITE; IrpSp->FileObject = device->fileobj; if (i == 0) IrpSp->Flags |= SL_WRITE_THROUGH; if (device->devobj->Flags & DO_BUFFERED_IO) { stripe->Irp->AssociatedIrp.SystemBuffer = sb; stripe->mdl = NULL; stripe->Irp->Flags = IRP_BUFFERED_IO; } else if (device->devobj->Flags & DO_DIRECT_IO) { stripe->mdl = IoAllocateMdl(sb, sblen, false, false, NULL); if (!stripe->mdl) { ERR("IoAllocateMdl failed\n"); IoFreeIrp(stripe->Irp); ExFreePool(stripe); ExFreePool(sb); return STATUS_INSUFFICIENT_RESOURCES; } stripe->Irp->MdlAddress = stripe->mdl; MmBuildMdlForNonPagedPool(stripe->mdl); } else { stripe->Irp->UserBuffer = sb; stripe->mdl = NULL; } IrpSp->Parameters.Write.Length = sblen; IrpSp->Parameters.Write.ByteOffset.QuadPart = superblock_addrs[i]; IoSetCompletionRoutine(stripe->Irp, write_superblock_completion, stripe, true, true, true); stripe->context = context; stripe->device = device; InsertTailList(&context->stripes, &stripe->list_entry); context->left++; i++; } if (i == 0) ERR("no superblocks written!\n"); return STATUS_SUCCESS; } static NTSTATUS write_superblocks(device_extension* Vcb, PIRP Irp) { uint64_t i; NTSTATUS Status; LIST_ENTRY* le; write_superblocks_context context; TRACE("(%p)\n", Vcb); le = Vcb->trees.Flink; while (le != &Vcb->trees) { tree* t = CONTAINING_RECORD(le, tree, list_entry); if (t->write && !t->parent) { if (t->root == Vcb->root_root) { Vcb->superblock.root_tree_addr = t->new_address; Vcb->superblock.root_level = t->header.level; } else if (t->root == Vcb->chunk_root) { Vcb->superblock.chunk_tree_addr = t->new_address; Vcb->superblock.chunk_root_generation = t->header.generation; Vcb->superblock.chunk_root_level = t->header.level; } } le = le->Flink; } for (i = 0; i < BTRFS_NUM_BACKUP_ROOTS - 1; i++) { RtlCopyMemory(&Vcb->superblock.backup[i], &Vcb->superblock.backup[i+1], sizeof(superblock_backup)); } update_backup_superblock(Vcb, &Vcb->superblock.backup[BTRFS_NUM_BACKUP_ROOTS - 1], Irp); KeInitializeEvent(&context.Event, NotificationEvent, false); InitializeListHead(&context.stripes); context.left = 0; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (dev->devobj && !dev->readonly) { Status = write_superblock(Vcb, dev, &context); if (!NT_SUCCESS(Status)) { ERR("write_superblock returned %08lx\n", Status); goto end; } } le = le->Flink; } if (IsListEmpty(&context.stripes)) { ERR("error - not writing any superblocks\n"); Status = STATUS_INTERNAL_ERROR; goto end; } le = context.stripes.Flink; while (le != &context.stripes) { write_superblocks_stripe* stripe = CONTAINING_RECORD(le, write_superblocks_stripe, list_entry); IoCallDriver(stripe->device->devobj, stripe->Irp); le = le->Flink; } KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL); le = context.stripes.Flink; while (le != &context.stripes) { write_superblocks_stripe* stripe = CONTAINING_RECORD(le, write_superblocks_stripe, list_entry); if (!NT_SUCCESS(stripe->Status)) { ERR("device %I64x returned %08lx\n", stripe->device->devitem.dev_id, stripe->Status); log_device_error(Vcb, stripe->device, BTRFS_DEV_STAT_WRITE_ERRORS); Status = stripe->Status; goto end; } le = le->Flink; } Status = STATUS_SUCCESS; end: while (!IsListEmpty(&context.stripes)) { write_superblocks_stripe* stripe = CONTAINING_RECORD(RemoveHeadList(&context.stripes), write_superblocks_stripe, list_entry); if (stripe->mdl) { if (stripe->mdl->MdlFlags & MDL_PAGES_LOCKED) MmUnlockPages(stripe->mdl); IoFreeMdl(stripe->mdl); } if (stripe->Irp) IoFreeIrp(stripe->Irp); if (stripe->buf) ExFreePool(stripe->buf); ExFreePool(stripe); } return Status; } static NTSTATUS flush_changed_extent(device_extension* Vcb, chunk* c, changed_extent* ce, PIRP Irp, LIST_ENTRY* rollback) { LIST_ENTRY *le, *le2; NTSTATUS Status; uint64_t old_size; if (ce->count == 0 && ce->old_count == 0) { while (!IsListEmpty(&ce->refs)) { changed_extent_ref* cer = CONTAINING_RECORD(RemoveHeadList(&ce->refs), changed_extent_ref, list_entry); ExFreePool(cer); } while (!IsListEmpty(&ce->old_refs)) { changed_extent_ref* cer = CONTAINING_RECORD(RemoveHeadList(&ce->old_refs), changed_extent_ref, list_entry); ExFreePool(cer); } goto end; } le = ce->refs.Flink; while (le != &ce->refs) { changed_extent_ref* cer = CONTAINING_RECORD(le, changed_extent_ref, list_entry); uint32_t old_count = 0; if (cer->type == TYPE_EXTENT_DATA_REF) { le2 = ce->old_refs.Flink; while (le2 != &ce->old_refs) { changed_extent_ref* cer2 = CONTAINING_RECORD(le2, changed_extent_ref, list_entry); 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) { old_count = cer2->edr.count; break; } le2 = le2->Flink; } old_size = ce->old_count > 0 ? ce->old_size : ce->size; if (cer->edr.count > old_count) { 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); if (!NT_SUCCESS(Status)) { ERR("increase_extent_refcount_data returned %08lx\n", Status); return Status; } } } else if (cer->type == TYPE_SHARED_DATA_REF) { le2 = ce->old_refs.Flink; while (le2 != &ce->old_refs) { changed_extent_ref* cer2 = CONTAINING_RECORD(le2, changed_extent_ref, list_entry); if (cer2->type == TYPE_SHARED_DATA_REF && cer2->sdr.offset == cer->sdr.offset) { RemoveEntryList(&cer2->list_entry); ExFreePool(cer2); break; } le2 = le2->Flink; } } le = le->Flink; } le = ce->refs.Flink; while (le != &ce->refs) { changed_extent_ref* cer = CONTAINING_RECORD(le, changed_extent_ref, list_entry); LIST_ENTRY* le3 = le->Flink; uint32_t old_count = 0; if (cer->type == TYPE_EXTENT_DATA_REF) { le2 = ce->old_refs.Flink; while (le2 != &ce->old_refs) { changed_extent_ref* cer2 = CONTAINING_RECORD(le2, changed_extent_ref, list_entry); 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) { old_count = cer2->edr.count; RemoveEntryList(&cer2->list_entry); ExFreePool(cer2); break; } le2 = le2->Flink; } old_size = ce->old_count > 0 ? ce->old_size : ce->size; if (cer->edr.count < old_count) { Status = decrease_extent_refcount_data(Vcb, ce->address, old_size, cer->edr.root, cer->edr.objid, cer->edr.offset, old_count - cer->edr.count, ce->superseded, Irp); if (!NT_SUCCESS(Status)) { ERR("decrease_extent_refcount_data returned %08lx\n", Status); return Status; } } if (ce->size != ce->old_size && ce->old_count > 0) { KEY searchkey; traverse_ptr tp; void* data; searchkey.obj_id = ce->address; searchkey.obj_type = TYPE_EXTENT_ITEM; searchkey.offset = ce->old_size; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (keycmp(searchkey, tp.item->key)) { ERR("could not find (%I64x,%x,%I64x) in extent tree\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); return STATUS_INTERNAL_ERROR; } if (tp.item->size > 0) { data = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG); if (!data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(data, tp.item->data, tp.item->size); } else data = NULL; Status = insert_tree_item(Vcb, Vcb->extent_root, ce->address, TYPE_EXTENT_ITEM, ce->size, data, tp.item->size, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); if (data) ExFreePool(data); return Status; } Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } } RemoveEntryList(&cer->list_entry); ExFreePool(cer); le = le3; } #ifdef DEBUG_PARANOID if (!IsListEmpty(&ce->old_refs)) WARN("old_refs not empty\n"); #endif end: if (ce->count == 0 && !ce->superseded) { c->used -= ce->size; space_list_add(c, ce->address, ce->size, rollback); } RemoveEntryList(&ce->list_entry); ExFreePool(ce); return STATUS_SUCCESS; } void add_checksum_entry(device_extension* Vcb, uint64_t address, ULONG length, void* csum, PIRP Irp) { KEY searchkey; traverse_ptr tp, next_tp; NTSTATUS Status; uint64_t startaddr, endaddr; ULONG len; RTL_BITMAP bmp; ULONG* bmparr; ULONG runlength, index; TRACE("(%p, %I64x, %lx, %p, %p)\n", Vcb, address, length, csum, Irp); searchkey.obj_id = EXTENT_CSUM_ID; searchkey.obj_type = TYPE_EXTENT_CSUM; searchkey.offset = address; // FIXME - create checksum_root if it doesn't exist at all Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, false, Irp); if (Status == STATUS_NOT_FOUND) { // tree is completely empty if (csum) { // not deleted ULONG length2 = length; uint64_t off = address; void* data = csum; do { uint16_t il = (uint16_t)min(length2, MAX_CSUM_SIZE / Vcb->csum_size); void* checksums = ExAllocatePoolWithTag(PagedPool, il * Vcb->csum_size, ALLOC_TAG); if (!checksums) { ERR("out of memory\n"); return; } RtlCopyMemory(checksums, data, il * Vcb->csum_size); Status = insert_tree_item(Vcb, Vcb->checksum_root, EXTENT_CSUM_ID, TYPE_EXTENT_CSUM, off, checksums, il * Vcb->csum_size, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(checksums); return; } length2 -= il; if (length2 > 0) { off += (uint64_t)il << Vcb->sector_shift; data = (uint8_t*)data + (il * Vcb->csum_size); } } while (length2 > 0); } } else if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return; } else { uint32_t tplen; void* checksums; // FIXME - check entry is TYPE_EXTENT_CSUM? if (tp.item->key.offset < address && tp.item->key.offset + (((uint64_t)tp.item->size << Vcb->sector_shift) / Vcb->csum_size) >= address) startaddr = tp.item->key.offset; else startaddr = address; searchkey.obj_id = EXTENT_CSUM_ID; searchkey.obj_type = TYPE_EXTENT_CSUM; searchkey.offset = address + (length << Vcb->sector_shift); Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return; } tplen = tp.item->size / Vcb->csum_size; if (tp.item->key.offset + (tplen << Vcb->sector_shift) >= address + (length << Vcb->sector_shift)) endaddr = tp.item->key.offset + (tplen << Vcb->sector_shift); else endaddr = address + (length << Vcb->sector_shift); TRACE("cs starts at %I64x (%lx sectors)\n", address, length); TRACE("startaddr = %I64x\n", startaddr); TRACE("endaddr = %I64x\n", endaddr); len = (ULONG)((endaddr - startaddr) >> Vcb->sector_shift); checksums = ExAllocatePoolWithTag(PagedPool, Vcb->csum_size * len, ALLOC_TAG); if (!checksums) { ERR("out of memory\n"); return; } bmparr = ExAllocatePoolWithTag(PagedPool, sizeof(ULONG) * ((len/8)+1), ALLOC_TAG); if (!bmparr) { ERR("out of memory\n"); ExFreePool(checksums); return; } RtlInitializeBitMap(&bmp, bmparr, len); RtlSetAllBits(&bmp); searchkey.obj_id = EXTENT_CSUM_ID; searchkey.obj_type = TYPE_EXTENT_CSUM; searchkey.offset = address; Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); ExFreePool(checksums); ExFreePool(bmparr); return; } // set bit = free space, cleared bit = allocated sector while (tp.item->key.offset < endaddr) { if (tp.item->key.offset >= startaddr) { if (tp.item->size > 0) { ULONG itemlen = (ULONG)min((len - ((tp.item->key.offset - startaddr) >> Vcb->sector_shift)) * Vcb->csum_size, tp.item->size); RtlCopyMemory((uint8_t*)checksums + (((tp.item->key.offset - startaddr) * Vcb->csum_size) >> Vcb->sector_shift), tp.item->data, itemlen); RtlClearBits(&bmp, (ULONG)((tp.item->key.offset - startaddr) >> Vcb->sector_shift), itemlen / Vcb->csum_size); } Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); ExFreePool(checksums); ExFreePool(bmparr); return; } } if (find_next_item(Vcb, &tp, &next_tp, false, Irp)) { tp = next_tp; } else break; } if (!csum) { // deleted RtlSetBits(&bmp, (ULONG)((address - startaddr) >> Vcb->sector_shift), length); } else { RtlCopyMemory((uint8_t*)checksums + (((address - startaddr) * Vcb->csum_size) >> Vcb->sector_shift), csum, length * Vcb->csum_size); RtlClearBits(&bmp, (ULONG)((address - startaddr) >> Vcb->sector_shift), length); } runlength = RtlFindFirstRunClear(&bmp, &index); while (runlength != 0) { if (index >= len) break; if (index + runlength >= len) { runlength = len - index; if (runlength == 0) break; } do { uint16_t rl; uint64_t off; void* data; if (runlength * Vcb->csum_size > MAX_CSUM_SIZE) rl = (uint16_t)(MAX_CSUM_SIZE / Vcb->csum_size); else rl = (uint16_t)runlength; data = ExAllocatePoolWithTag(PagedPool, Vcb->csum_size * rl, ALLOC_TAG); if (!data) { ERR("out of memory\n"); ExFreePool(bmparr); ExFreePool(checksums); return; } RtlCopyMemory(data, (uint8_t*)checksums + (Vcb->csum_size * index), Vcb->csum_size * rl); off = startaddr + ((uint64_t)index << Vcb->sector_shift); Status = insert_tree_item(Vcb, Vcb->checksum_root, EXTENT_CSUM_ID, TYPE_EXTENT_CSUM, off, data, Vcb->csum_size * rl, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(data); ExFreePool(bmparr); ExFreePool(checksums); return; } runlength -= rl; index += rl; } while (runlength > 0); runlength = RtlFindNextForwardRunClear(&bmp, index, &index); } ExFreePool(bmparr); ExFreePool(checksums); } } static NTSTATUS update_chunk_usage(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) { LIST_ENTRY *le = Vcb->chunks.Flink, *le2; chunk* c; KEY searchkey; traverse_ptr tp; BLOCK_GROUP_ITEM* bgi; NTSTATUS Status; TRACE("(%p)\n", Vcb); ExAcquireResourceSharedLite(&Vcb->chunk_lock, true); while (le != &Vcb->chunks) { c = CONTAINING_RECORD(le, chunk, list_entry); acquire_chunk_lock(c, Vcb); if (!c->cache_loaded && (!IsListEmpty(&c->changed_extents) || c->used != c->oldused)) { Status = load_cache_chunk(Vcb, c, NULL); if (!NT_SUCCESS(Status)) { ERR("load_cache_chunk returned %08lx\n", Status); release_chunk_lock(c, Vcb); goto end; } } le2 = c->changed_extents.Flink; while (le2 != &c->changed_extents) { LIST_ENTRY* le3 = le2->Flink; changed_extent* ce = CONTAINING_RECORD(le2, changed_extent, list_entry); Status = flush_changed_extent(Vcb, c, ce, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("flush_changed_extent returned %08lx\n", Status); release_chunk_lock(c, Vcb); goto end; } le2 = le3; } // This is usually done by update_chunks, but we have to check again in case any new chunks // have been allocated since. if (c->created) { Status = create_chunk(Vcb, c, Irp); if (!NT_SUCCESS(Status)) { ERR("create_chunk returned %08lx\n", Status); release_chunk_lock(c, Vcb); goto end; } } if (c->old_cache) { if (c->old_cache->dirty) { LIST_ENTRY batchlist; InitializeListHead(&batchlist); Status = flush_fcb(c->old_cache, false, &batchlist, Irp); if (!NT_SUCCESS(Status)) { ERR("flush_fcb returned %08lx\n", Status); release_chunk_lock(c, Vcb); clear_batch_list(Vcb, &batchlist); goto end; } Status = commit_batch_list(Vcb, &batchlist, Irp); if (!NT_SUCCESS(Status)) { ERR("commit_batch_list returned %08lx\n", Status); release_chunk_lock(c, Vcb); goto end; } } free_fcb(c->old_cache); if (c->old_cache->refcount == 0) reap_fcb(c->old_cache); c->old_cache = NULL; } if (c->used != c->oldused) { root* r = Vcb->block_group_root ? Vcb->block_group_root : Vcb->extent_root; searchkey.obj_id = c->offset; searchkey.obj_type = TYPE_BLOCK_GROUP_ITEM; searchkey.offset = c->chunk_item->size; Status = find_item(Vcb, r, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); release_chunk_lock(c, Vcb); goto end; } if (keycmp(searchkey, tp.item->key)) { ERR("could not find (%I64x;%I64x,%x,%I64x)\n", r->id, searchkey.obj_id, searchkey.obj_type, searchkey.offset); Status = STATUS_INTERNAL_ERROR; release_chunk_lock(c, Vcb); goto end; } if (tp.item->size < sizeof(BLOCK_GROUP_ITEM)) { 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)); Status = STATUS_INTERNAL_ERROR; release_chunk_lock(c, Vcb); goto end; } bgi = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG); if (!bgi) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; release_chunk_lock(c, Vcb); goto end; } RtlCopyMemory(bgi, tp.item->data, tp.item->size); bgi->used = c->used; #ifdef DEBUG_PARANOID if (bgi->used & 0x8000000000000000) { ERR("refusing to write BLOCK_GROUP_ITEM with negative usage value (%I64x)\n", bgi->used); int3; } #endif TRACE("adjusting usage of chunk %I64x to %I64x\n", c->offset, c->used); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); ExFreePool(bgi); release_chunk_lock(c, Vcb); goto end; } Status = insert_tree_item(Vcb, r, searchkey.obj_id, searchkey.obj_type, searchkey.offset, bgi, tp.item->size, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(bgi); release_chunk_lock(c, Vcb); goto end; } Vcb->superblock.bytes_used += c->used - c->oldused; c->oldused = c->used; } release_chunk_lock(c, Vcb); le = le->Flink; } Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&Vcb->chunk_lock); return Status; } static void get_first_item(tree* t, KEY* key) { LIST_ENTRY* le; le = t->itemlist.Flink; while (le != &t->itemlist) { tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry); *key = td->key; return; } } static NTSTATUS split_tree_at(device_extension* Vcb, tree* t, tree_data* newfirstitem, uint32_t numitems, uint32_t size) { tree *nt, *pt; tree_data* td; tree_data* oldlastitem; TRACE("splitting tree in %I64x at (%I64x,%x,%I64x)\n", t->root->id, newfirstitem->key.obj_id, newfirstitem->key.obj_type, newfirstitem->key.offset); nt = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG); if (!nt) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } if (t->header.level > 0) { nt->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(tree_nonpaged), ALLOC_TAG); if (!nt->nonpaged) { ERR("out of memory\n"); ExFreePool(nt); return STATUS_INSUFFICIENT_RESOURCES; } ExInitializeFastMutex(&nt->nonpaged->mutex); } else nt->nonpaged = NULL; RtlCopyMemory(&nt->header, &t->header, sizeof(tree_header)); nt->header.address = 0; nt->header.generation = Vcb->superblock.generation; nt->header.num_items = t->header.num_items - numitems; nt->header.flags = HEADER_FLAG_MIXED_BACKREF | HEADER_FLAG_WRITTEN; nt->has_address = false; nt->Vcb = Vcb; nt->parent = t->parent; #ifdef DEBUG_PARANOID if (nt->parent && nt->parent->header.level <= nt->header.level) int3; #endif nt->root = t->root; nt->new_address = 0; nt->has_new_address = false; nt->updated_extents = false; nt->uniqueness_determined = true; nt->is_unique = true; nt->list_entry_hash.Flink = NULL; nt->buf = NULL; InitializeListHead(&nt->itemlist); oldlastitem = CONTAINING_RECORD(newfirstitem->list_entry.Blink, tree_data, list_entry); nt->itemlist.Flink = &newfirstitem->list_entry; nt->itemlist.Blink = t->itemlist.Blink; nt->itemlist.Flink->Blink = &nt->itemlist; nt->itemlist.Blink->Flink = &nt->itemlist; t->itemlist.Blink = &oldlastitem->list_entry; t->itemlist.Blink->Flink = &t->itemlist; nt->size = t->size - size; t->size = size; t->header.num_items = numitems; nt->write = true; InsertTailList(&Vcb->trees, &nt->list_entry); if (nt->header.level > 0) { LIST_ENTRY* le = nt->itemlist.Flink; while (le != &nt->itemlist) { tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry); if (td2->treeholder.tree) { td2->treeholder.tree->parent = nt; #ifdef DEBUG_PARANOID if (td2->treeholder.tree->parent && td2->treeholder.tree->parent->header.level <= td2->treeholder.tree->header.level) int3; #endif } le = le->Flink; } } else { LIST_ENTRY* le = nt->itemlist.Flink; while (le != &nt->itemlist) { tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry); if (!td2->inserted && td2->data) { uint8_t* data = ExAllocatePoolWithTag(PagedPool, td2->size, ALLOC_TAG); if (!data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(data, td2->data, td2->size); td2->data = data; td2->inserted = true; } le = le->Flink; } } if (nt->parent) { td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside); if (!td) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } td->key = newfirstitem->key; InsertHeadList(&t->paritem->list_entry, &td->list_entry); td->ignore = false; td->inserted = true; td->treeholder.tree = nt; nt->paritem = td; nt->parent->header.num_items++; nt->parent->size += sizeof(internal_node); goto end; } TRACE("adding new tree parent\n"); if (nt->header.level == 255) { ERR("cannot add parent to tree at level 255\n"); return STATUS_INTERNAL_ERROR; } pt = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG); if (!pt) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } pt->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(tree_nonpaged), ALLOC_TAG); if (!pt->nonpaged) { ERR("out of memory\n"); ExFreePool(pt); return STATUS_INSUFFICIENT_RESOURCES; } ExInitializeFastMutex(&pt->nonpaged->mutex); RtlCopyMemory(&pt->header, &nt->header, sizeof(tree_header)); pt->header.address = 0; pt->header.num_items = 2; pt->header.level = nt->header.level + 1; pt->header.flags = HEADER_FLAG_MIXED_BACKREF | HEADER_FLAG_WRITTEN; pt->has_address = false; pt->Vcb = Vcb; pt->parent = NULL; pt->paritem = NULL; pt->root = t->root; pt->new_address = 0; pt->has_new_address = false; pt->updated_extents = false; pt->size = pt->header.num_items * sizeof(internal_node); pt->uniqueness_determined = true; pt->is_unique = true; pt->list_entry_hash.Flink = NULL; pt->buf = NULL; InitializeListHead(&pt->itemlist); InsertTailList(&Vcb->trees, &pt->list_entry); td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside); if (!td) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } get_first_item(t, &td->key); td->ignore = false; td->inserted = false; td->treeholder.address = 0; td->treeholder.generation = Vcb->superblock.generation; td->treeholder.tree = t; InsertTailList(&pt->itemlist, &td->list_entry); t->paritem = td; td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside); if (!td) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } td->key = newfirstitem->key; td->ignore = false; td->inserted = false; td->treeholder.address = 0; td->treeholder.generation = Vcb->superblock.generation; td->treeholder.tree = nt; InsertTailList(&pt->itemlist, &td->list_entry); nt->paritem = td; pt->write = true; t->root->treeholder.tree = pt; t->parent = pt; nt->parent = pt; #ifdef DEBUG_PARANOID if (t->parent && t->parent->header.level <= t->header.level) int3; if (nt->parent && nt->parent->header.level <= nt->header.level) int3; #endif end: t->root->root_item.bytes_used += Vcb->superblock.node_size; return STATUS_SUCCESS; } static NTSTATUS split_tree(device_extension* Vcb, tree* t) { LIST_ENTRY* le; uint32_t size, ds, numitems; size = 0; numitems = 0; // FIXME - naïve implementation: maximizes number of filled trees le = t->itemlist.Flink; while (le != &t->itemlist) { tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry); if (!td->ignore) { if (t->header.level == 0) ds = sizeof(leaf_node) + td->size; else ds = sizeof(internal_node); if (numitems == 0 && ds > Vcb->superblock.node_size - sizeof(tree_header)) { ERR("(%I64x,%x,%I64x) in tree %I64x is too large (%x > %Ix)\n", td->key.obj_id, td->key.obj_type, td->key.offset, t->root->id, ds, Vcb->superblock.node_size - sizeof(tree_header)); return STATUS_INTERNAL_ERROR; } // FIXME - move back if previous item was deleted item with same key if (size + ds > Vcb->superblock.node_size - sizeof(tree_header)) return split_tree_at(Vcb, t, td, numitems, size); size += ds; numitems++; } le = le->Flink; } return STATUS_SUCCESS; } bool is_tree_unique(device_extension* Vcb, tree* t, PIRP Irp) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; bool ret = false; EXTENT_ITEM* ei; uint8_t* type; if (t->uniqueness_determined) return t->is_unique; if (t->parent && !is_tree_unique(Vcb, t->parent, Irp)) goto end; if (t->has_address) { searchkey.obj_id = t->header.address; searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); goto end; } 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)) goto end; if (tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->size == sizeof(EXTENT_ITEM_V0)) goto end; if (tp.item->size < sizeof(EXTENT_ITEM)) goto end; ei = (EXTENT_ITEM*)tp.item->data; if (ei->refcount > 1) goto end; if (tp.item->key.obj_type == TYPE_EXTENT_ITEM && ei->flags & EXTENT_ITEM_TREE_BLOCK) { EXTENT_ITEM2* ei2; if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) goto end; ei2 = (EXTENT_ITEM2*)&ei[1]; type = (uint8_t*)&ei2[1]; } else type = (uint8_t*)&ei[1]; if (type >= tp.item->data + tp.item->size || *type != TYPE_TREE_BLOCK_REF) goto end; } ret = true; end: t->is_unique = ret; t->uniqueness_determined = true; return ret; } static NTSTATUS try_tree_amalgamate(device_extension* Vcb, tree* t, bool* done, bool* done_deletions, PIRP Irp, LIST_ENTRY* rollback) { LIST_ENTRY* le; tree_data* nextparitem = NULL; NTSTATUS Status; tree *next_tree, *par; *done = false; TRACE("trying to amalgamate tree in root %I64x, level %x (size %u)\n", t->root->id, t->header.level, t->size); // FIXME - doesn't capture everything, as it doesn't ascend le = t->paritem->list_entry.Flink; while (le != &t->parent->itemlist) { tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry); if (!td->ignore) { nextparitem = td; break; } le = le->Flink; } if (!nextparitem) return STATUS_SUCCESS; TRACE("nextparitem: key = %I64x,%x,%I64x\n", nextparitem->key.obj_id, nextparitem->key.obj_type, nextparitem->key.offset); if (!nextparitem->treeholder.tree) { Status = do_load_tree(Vcb, &nextparitem->treeholder, t->root, t->parent, nextparitem, NULL); if (!NT_SUCCESS(Status)) { ERR("do_load_tree returned %08lx\n", Status); return Status; } } if (!is_tree_unique(Vcb, nextparitem->treeholder.tree, Irp)) return STATUS_SUCCESS; next_tree = nextparitem->treeholder.tree; if (!next_tree->updated_extents && next_tree->has_address) { Status = update_tree_extents(Vcb, next_tree, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("update_tree_extents returned %08lx\n", Status); return Status; } } if (t->size + next_tree->size <= Vcb->superblock.node_size - sizeof(tree_header)) { // merge two trees into one t->header.num_items += next_tree->header.num_items; t->size += next_tree->size; if (next_tree->header.level > 0) { le = next_tree->itemlist.Flink; while (le != &next_tree->itemlist) { tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry); if (td2->treeholder.tree) { td2->treeholder.tree->parent = t; #ifdef DEBUG_PARANOID if (td2->treeholder.tree->parent && td2->treeholder.tree->parent->header.level <= td2->treeholder.tree->header.level) int3; #endif } td2->inserted = true; le = le->Flink; } } else { le = next_tree->itemlist.Flink; while (le != &next_tree->itemlist) { tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry); if (!td2->inserted && td2->data) { uint8_t* data = ExAllocatePoolWithTag(PagedPool, td2->size, ALLOC_TAG); if (!data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(data, td2->data, td2->size); td2->data = data; td2->inserted = true; } le = le->Flink; } } t->itemlist.Blink->Flink = next_tree->itemlist.Flink; t->itemlist.Blink->Flink->Blink = t->itemlist.Blink; t->itemlist.Blink = next_tree->itemlist.Blink; t->itemlist.Blink->Flink = &t->itemlist; next_tree->itemlist.Flink = next_tree->itemlist.Blink = &next_tree->itemlist; next_tree->header.num_items = 0; next_tree->size = 0; if (next_tree->has_new_address) { // delete associated EXTENT_ITEM Status = reduce_tree_extent(Vcb, next_tree->new_address, next_tree, next_tree->parent->header.tree_id, next_tree->header.level, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("reduce_tree_extent returned %08lx\n", Status); return Status; } } else if (next_tree->has_address) { Status = reduce_tree_extent(Vcb, next_tree->header.address, next_tree, next_tree->parent->header.tree_id, next_tree->header.level, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("reduce_tree_extent returned %08lx\n", Status); return Status; } } if (!nextparitem->ignore) { nextparitem->ignore = true; next_tree->parent->header.num_items--; next_tree->parent->size -= sizeof(internal_node); *done_deletions = true; } par = next_tree->parent; while (par) { par->write = true; par = par->parent; } RemoveEntryList(&nextparitem->list_entry); ExFreePool(next_tree->paritem); next_tree->paritem = NULL; next_tree->root->root_item.bytes_used -= Vcb->superblock.node_size; free_tree(next_tree); *done = true; } else { // rebalance by moving items from second tree into first ULONG avg_size = (t->size + next_tree->size) / 2; KEY firstitem = {0, 0, 0}; bool changed = false; TRACE("attempting rebalance\n"); le = next_tree->itemlist.Flink; while (le != &next_tree->itemlist && t->size < avg_size && next_tree->header.num_items > 1) { tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry); ULONG size; if (!td->ignore) { if (next_tree->header.level == 0) size = sizeof(leaf_node) + td->size; else size = sizeof(internal_node); } else size = 0; if (t->size + size < Vcb->superblock.node_size - sizeof(tree_header)) { RemoveEntryList(&td->list_entry); InsertTailList(&t->itemlist, &td->list_entry); if (next_tree->header.level > 0 && td->treeholder.tree) { td->treeholder.tree->parent = t; #ifdef DEBUG_PARANOID if (td->treeholder.tree->parent && td->treeholder.tree->parent->header.level <= td->treeholder.tree->header.level) int3; #endif } else if (next_tree->header.level == 0 && !td->inserted && td->size > 0) { uint8_t* data = ExAllocatePoolWithTag(PagedPool, td->size, ALLOC_TAG); if (!data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(data, td->data, td->size); td->data = data; } td->inserted = true; if (!td->ignore) { next_tree->size -= size; t->size += size; next_tree->header.num_items--; t->header.num_items++; } changed = true; } else break; le = next_tree->itemlist.Flink; } le = next_tree->itemlist.Flink; while (le != &next_tree->itemlist) { tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry); if (!td->ignore) { firstitem = td->key; break; } le = le->Flink; } // FIXME - once ascension is working, make this work with parent's parent, etc. if (next_tree->paritem) next_tree->paritem->key = firstitem; par = next_tree; while (par) { par->write = true; par = par->parent; } if (changed) *done = true; } return STATUS_SUCCESS; } static NTSTATUS update_extent_level(device_extension* Vcb, uint64_t address, tree* t, uint8_t level, PIRP Irp) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA) { searchkey.obj_id = address; searchkey.obj_type = TYPE_METADATA_ITEM; searchkey.offset = t->header.level; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (!keycmp(tp.item->key, searchkey)) { EXTENT_ITEM_SKINNY_METADATA* eism; if (tp.item->size > 0) { eism = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG); if (!eism) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(eism, tp.item->data, tp.item->size); } else eism = NULL; Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); if (eism) ExFreePool(eism); return Status; } Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_METADATA_ITEM, level, eism, tp.item->size, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); if (eism) ExFreePool(eism); return Status; } return STATUS_SUCCESS; } } searchkey.obj_id = address; searchkey.obj_type = TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { EXTENT_ITEM_TREE* eit; if (tp.item->size < sizeof(EXTENT_ITEM_TREE)) { 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)); return STATUS_INTERNAL_ERROR; } eit = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG); if (!eit) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(eit, tp.item->data, tp.item->size); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); ExFreePool(eit); return Status; } eit->level = level; 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(eit); return Status; } return STATUS_SUCCESS; } ERR("could not find EXTENT_ITEM for address %I64x\n", address); return STATUS_INTERNAL_ERROR; } static NTSTATUS update_tree_extents_recursive(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; if (t->parent && !t->parent->updated_extents && t->parent->has_address) { Status = update_tree_extents_recursive(Vcb, t->parent, Irp, rollback); if (!NT_SUCCESS(Status)) return Status; } Status = update_tree_extents(Vcb, t, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("update_tree_extents returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } static NTSTATUS do_splits(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) { ULONG level, max_level; uint32_t min_size, min_size_fst; bool empty, done_deletions = false; NTSTATUS Status; tree* t; TRACE("(%p)\n", Vcb); max_level = 0; for (level = 0; level <= 255; level++) { LIST_ENTRY *le, *nextle; empty = true; TRACE("doing level %lu\n", level); le = Vcb->trees.Flink; while (le != &Vcb->trees) { t = CONTAINING_RECORD(le, tree, list_entry); nextle = le->Flink; if (t->write && t->header.level == level) { empty = false; if (t->header.num_items == 0) { if (t->parent) { done_deletions = true; TRACE("deleting tree in root %I64x\n", t->root->id); t->root->root_item.bytes_used -= Vcb->superblock.node_size; if (t->has_new_address) { // delete associated EXTENT_ITEM Status = reduce_tree_extent(Vcb, t->new_address, t, t->parent->header.tree_id, t->header.level, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("reduce_tree_extent returned %08lx\n", Status); return Status; } t->has_new_address = false; } else if (t->has_address) { Status = reduce_tree_extent(Vcb,t->header.address, t, t->parent->header.tree_id, t->header.level, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("reduce_tree_extent returned %08lx\n", Status); return Status; } t->has_address = false; } if (!t->paritem->ignore) { t->paritem->ignore = true; t->parent->header.num_items--; t->parent->size -= sizeof(internal_node); } RemoveEntryList(&t->paritem->list_entry); ExFreePool(t->paritem); t->paritem = NULL; free_tree(t); } else if (t->header.level != 0) { if (t->has_new_address) { Status = update_extent_level(Vcb, t->new_address, t, 0, Irp); if (!NT_SUCCESS(Status)) { ERR("update_extent_level returned %08lx\n", Status); return Status; } } t->header.level = 0; } } else if (t->size > Vcb->superblock.node_size - sizeof(tree_header)) { TRACE("splitting overlarge tree (%x > %Ix)\n", t->size, Vcb->superblock.node_size - sizeof(tree_header)); if (!t->updated_extents && t->has_address) { Status = update_tree_extents_recursive(Vcb, t, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("update_tree_extents_recursive returned %08lx\n", Status); return Status; } } Status = split_tree(Vcb, t); if (!NT_SUCCESS(Status)) { ERR("split_tree returned %08lx\n", Status); return Status; } } } le = nextle; } if (!empty) { max_level = level; } else { TRACE("nothing found for level %lu\n", level); break; } } min_size = (Vcb->superblock.node_size - sizeof(tree_header)) / 2; min_size_fst = (Vcb->superblock.node_size - sizeof(tree_header)) / 4; for (level = 0; level <= max_level; level++) { LIST_ENTRY* le; le = Vcb->trees.Flink; while (le != &Vcb->trees) { t = CONTAINING_RECORD(le, tree, list_entry); if (t->write && t->header.level == level && t->header.num_items > 0 && t->parent && ((t->size < min_size && t->root->id != BTRFS_ROOT_FREE_SPACE) || (t->size < min_size_fst && t->root->id == BTRFS_ROOT_FREE_SPACE)) && is_tree_unique(Vcb, t, Irp)) { bool done; do { Status = try_tree_amalgamate(Vcb, t, &done, &done_deletions, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("try_tree_amalgamate returned %08lx\n", Status); return Status; } } while (done && t->size < min_size); } le = le->Flink; } } // simplify trees if top tree only has one entry if (done_deletions) { for (level = max_level; level > 0; level--) { LIST_ENTRY *le, *nextle; le = Vcb->trees.Flink; while (le != &Vcb->trees) { nextle = le->Flink; t = CONTAINING_RECORD(le, tree, list_entry); if (t->write && t->header.level == level) { if (!t->parent && t->header.num_items == 1) { LIST_ENTRY* le2 = t->itemlist.Flink; tree_data* td = NULL; tree* child_tree = NULL; while (le2 != &t->itemlist) { td = CONTAINING_RECORD(le2, tree_data, list_entry); if (!td->ignore) break; le2 = le2->Flink; } TRACE("deleting top-level tree in root %I64x with one item\n", t->root->id); if (t->has_new_address) { // delete associated EXTENT_ITEM Status = reduce_tree_extent(Vcb, t->new_address, t, t->header.tree_id, t->header.level, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("reduce_tree_extent returned %08lx\n", Status); return Status; } t->has_new_address = false; } else if (t->has_address) { Status = reduce_tree_extent(Vcb,t->header.address, t, t->header.tree_id, t->header.level, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("reduce_tree_extent returned %08lx\n", Status); return Status; } t->has_address = false; } if (!td->treeholder.tree) { // load first item if not already loaded KEY searchkey = {0,0,0}; traverse_ptr tp; Status = find_item(Vcb, t->root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } } child_tree = td->treeholder.tree; if (child_tree) { child_tree->parent = NULL; child_tree->paritem = NULL; } t->root->root_item.bytes_used -= Vcb->superblock.node_size; free_tree(t); if (child_tree) child_tree->root->treeholder.tree = child_tree; } } le = nextle; } } } return STATUS_SUCCESS; } static NTSTATUS remove_root_extents(device_extension* Vcb, root* r, tree_holder* th, uint8_t level, tree* parent, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; if (!th->tree) { uint8_t* buf; chunk* c; buf = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.node_size, ALLOC_TAG); if (!buf) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } Status = read_data(Vcb, th->address, Vcb->superblock.node_size, NULL, true, buf, NULL, &c, Irp, th->generation, false, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned 0x%08lx\n", Status); ExFreePool(buf); return Status; } Status = load_tree(Vcb, th->address, buf, r, &th->tree); if (!th->tree || th->tree->buf != buf) ExFreePool(buf); if (!NT_SUCCESS(Status)) { ERR("load_tree(%I64x) returned %08lx\n", th->address, Status); return Status; } } if (level > 0) { LIST_ENTRY* le = th->tree->itemlist.Flink; while (le != &th->tree->itemlist) { tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry); if (!td->ignore) { Status = remove_root_extents(Vcb, r, &td->treeholder, th->tree->header.level - 1, th->tree, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("remove_root_extents returned %08lx\n", Status); return Status; } } le = le->Flink; } } if (th->tree && !th->tree->updated_extents && th->tree->has_address) { Status = update_tree_extents(Vcb, th->tree, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("update_tree_extents returned %08lx\n", Status); return Status; } } if (!th->tree || th->tree->has_address) { Status = reduce_tree_extent(Vcb, th->address, NULL, parent ? parent->header.tree_id : r->id, level, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("reduce_tree_extent(%I64x) returned %08lx\n", th->address, Status); return Status; } } return STATUS_SUCCESS; } static NTSTATUS drop_root(device_extension* Vcb, root* r, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; Status = remove_root_extents(Vcb, r, &r->treeholder, r->root_item.root_level, NULL, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("remove_root_extents returned %08lx\n", Status); return Status; } // remove entries in uuid root (tree 9) if (Vcb->uuid_root) { RtlCopyMemory(&searchkey.obj_id, &r->root_item.uuid.uuid[0], sizeof(uint64_t)); searchkey.obj_type = TYPE_SUBVOL_UUID; RtlCopyMemory(&searchkey.offset, &r->root_item.uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t)); if (searchkey.obj_id != 0 || searchkey.offset != 0) { Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { WARN("find_item returned %08lx\n", Status); } else { if (!keycmp(tp.item->key, searchkey)) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } else WARN("could not find (%I64x,%x,%I64x) in uuid tree\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); } } if (r->root_item.rtransid > 0) { RtlCopyMemory(&searchkey.obj_id, &r->root_item.received_uuid.uuid[0], sizeof(uint64_t)); searchkey.obj_type = TYPE_SUBVOL_REC_UUID; RtlCopyMemory(&searchkey.offset, &r->root_item.received_uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t)); Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) WARN("find_item returned %08lx\n", Status); else { if (!keycmp(tp.item->key, searchkey)) { if (tp.item->size == sizeof(uint64_t)) { uint64_t* id = (uint64_t*)tp.item->data; if (*id == r->id) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } } else if (tp.item->size > sizeof(uint64_t)) { ULONG i; uint64_t* ids = (uint64_t*)tp.item->data; for (i = 0; i < tp.item->size / sizeof(uint64_t); i++) { if (ids[i] == r->id) { uint64_t* ne; ne = ExAllocatePoolWithTag(PagedPool, tp.item->size - sizeof(uint64_t), ALLOC_TAG); if (!ne) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } if (i > 0) RtlCopyMemory(ne, ids, sizeof(uint64_t) * i); if ((i + 1) * sizeof(uint64_t) < tp.item->size) RtlCopyMemory(&ne[i], &ids[i + 1], tp.item->size - ((i + 1) * sizeof(uint64_t))); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); ExFreePool(ne); return Status; } Status = insert_tree_item(Vcb, Vcb->uuid_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, ne, tp.item->size - sizeof(uint64_t), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(ne); return Status; } break; } } } } else WARN("could not find (%I64x,%x,%I64x) in uuid tree\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); } } } // delete ROOT_ITEM searchkey.obj_id = r->id; searchkey.obj_type = TYPE_ROOT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } else WARN("could not find (%I64x,%x,%I64x) in root_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); // delete items in tree cache free_trees_root(Vcb, r); return STATUS_SUCCESS; } static NTSTATUS drop_roots(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) { LIST_ENTRY *le = Vcb->drop_roots.Flink, *le2; NTSTATUS Status; while (le != &Vcb->drop_roots) { root* r = CONTAINING_RECORD(le, root, list_entry); le2 = le->Flink; Status = drop_root(Vcb, r, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("drop_root(%I64x) returned %08lx\n", r->id, Status); return Status; } le = le2; } return STATUS_SUCCESS; } NTSTATUS update_dev_item(device_extension* Vcb, device* device, PIRP Irp) { KEY searchkey; traverse_ptr tp; DEV_ITEM* di; NTSTATUS Status; searchkey.obj_id = 1; searchkey.obj_type = TYPE_DEV_ITEM; searchkey.offset = device->devitem.dev_id; Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (keycmp(tp.item->key, searchkey)) { ERR("error - could not find DEV_ITEM for device %I64x\n", device->devitem.dev_id); return STATUS_INTERNAL_ERROR; } Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } di = ExAllocatePoolWithTag(PagedPool, sizeof(DEV_ITEM), ALLOC_TAG); if (!di) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(di, &device->devitem, sizeof(DEV_ITEM)); Status = insert_tree_item(Vcb, Vcb->chunk_root, 1, TYPE_DEV_ITEM, device->devitem.dev_id, di, sizeof(DEV_ITEM), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(di); return Status; } return STATUS_SUCCESS; } static void regen_bootstrap(device_extension* Vcb) { sys_chunk* sc2; USHORT i = 0; LIST_ENTRY* le; i = 0; le = Vcb->sys_chunks.Flink; while (le != &Vcb->sys_chunks) { sc2 = CONTAINING_RECORD(le, sys_chunk, list_entry); TRACE("%I64x,%x,%I64x\n", sc2->key.obj_id, sc2->key.obj_type, sc2->key.offset); RtlCopyMemory(&Vcb->superblock.sys_chunk_array[i], &sc2->key, sizeof(KEY)); i += sizeof(KEY); RtlCopyMemory(&Vcb->superblock.sys_chunk_array[i], sc2->data, sc2->size); i += sc2->size; le = le->Flink; } } static NTSTATUS add_to_bootstrap(device_extension* Vcb, uint64_t obj_id, uint8_t obj_type, uint64_t offset, void* data, uint16_t size) { sys_chunk* sc; LIST_ENTRY* le; if (Vcb->superblock.n + sizeof(KEY) + size > SYS_CHUNK_ARRAY_SIZE) { ERR("error - bootstrap is full\n"); return STATUS_INTERNAL_ERROR; } sc = ExAllocatePoolWithTag(PagedPool, sizeof(sys_chunk), ALLOC_TAG); if (!sc) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } sc->key.obj_id = obj_id; sc->key.obj_type = obj_type; sc->key.offset = offset; sc->size = size; sc->data = ExAllocatePoolWithTag(PagedPool, sc->size, ALLOC_TAG); if (!sc->data) { ERR("out of memory\n"); ExFreePool(sc); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(sc->data, data, sc->size); le = Vcb->sys_chunks.Flink; while (le != &Vcb->sys_chunks) { sys_chunk* sc2 = CONTAINING_RECORD(le, sys_chunk, list_entry); if (keycmp(sc2->key, sc->key) == 1) break; le = le->Flink; } InsertTailList(le, &sc->list_entry); Vcb->superblock.n += sizeof(KEY) + size; regen_bootstrap(Vcb); return STATUS_SUCCESS; } static NTSTATUS create_chunk(device_extension* Vcb, chunk* c, PIRP Irp) { CHUNK_ITEM* ci; CHUNK_ITEM_STRIPE* cis; BLOCK_GROUP_ITEM* bgi; uint16_t i, factor; NTSTATUS Status; ci = ExAllocatePoolWithTag(PagedPool, c->size, ALLOC_TAG); if (!ci) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(ci, c->chunk_item, c->size); Status = insert_tree_item(Vcb, Vcb->chunk_root, 0x100, TYPE_CHUNK_ITEM, c->offset, ci, c->size, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item failed\n"); ExFreePool(ci); return Status; } if (c->chunk_item->type & BLOCK_FLAG_SYSTEM) { Status = add_to_bootstrap(Vcb, 0x100, TYPE_CHUNK_ITEM, c->offset, ci, c->size); if (!NT_SUCCESS(Status)) { ERR("add_to_bootstrap returned %08lx\n", Status); return Status; } } // add BLOCK_GROUP_ITEM to tree 2 bgi = ExAllocatePoolWithTag(PagedPool, sizeof(BLOCK_GROUP_ITEM), ALLOC_TAG); if (!bgi) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } bgi->used = c->used; bgi->chunk_tree = 0x100; bgi->flags = c->chunk_item->type; Status = insert_tree_item(Vcb, Vcb->block_group_root ? Vcb->block_group_root : Vcb->extent_root, c->offset, TYPE_BLOCK_GROUP_ITEM, c->chunk_item->size, bgi, sizeof(BLOCK_GROUP_ITEM), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item failed\n"); ExFreePool(bgi); return Status; } if (c->chunk_item->type & BLOCK_FLAG_RAID0) factor = c->chunk_item->num_stripes; else if (c->chunk_item->type & BLOCK_FLAG_RAID10) factor = c->chunk_item->num_stripes / c->chunk_item->sub_stripes; else if (c->chunk_item->type & BLOCK_FLAG_RAID5) factor = c->chunk_item->num_stripes - 1; else if (c->chunk_item->type & BLOCK_FLAG_RAID6) factor = c->chunk_item->num_stripes - 2; else // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4 factor = 1; // add DEV_EXTENTs to tree 4 cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; for (i = 0; i < c->chunk_item->num_stripes; i++) { DEV_EXTENT* de; de = ExAllocatePoolWithTag(PagedPool, sizeof(DEV_EXTENT), ALLOC_TAG); if (!de) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } de->chunktree = Vcb->chunk_root->id; de->objid = 0x100; de->address = c->offset; de->length = c->chunk_item->size / factor; de->chunktree_uuid = Vcb->chunk_root->treeholder.tree->header.chunk_tree_uuid; 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(de); return Status; } // FIXME - no point in calling this twice for the same device Status = update_dev_item(Vcb, c->devices[i], Irp); if (!NT_SUCCESS(Status)) { ERR("update_dev_item returned %08lx\n", Status); return Status; } } c->created = false; c->oldused = c->used; Vcb->superblock.bytes_used += c->used; return STATUS_SUCCESS; } static void remove_from_bootstrap(device_extension* Vcb, uint64_t obj_id, uint8_t obj_type, uint64_t offset) { sys_chunk* sc2; LIST_ENTRY* le; le = Vcb->sys_chunks.Flink; while (le != &Vcb->sys_chunks) { sc2 = CONTAINING_RECORD(le, sys_chunk, list_entry); if (sc2->key.obj_id == obj_id && sc2->key.obj_type == obj_type && sc2->key.offset == offset) { RemoveEntryList(&sc2->list_entry); Vcb->superblock.n -= sizeof(KEY) + sc2->size; ExFreePool(sc2->data); ExFreePool(sc2); regen_bootstrap(Vcb); return; } le = le->Flink; } } static NTSTATUS set_xattr(device_extension* Vcb, LIST_ENTRY* batchlist, root* subvol, uint64_t inode, char* name, uint16_t namelen, uint32_t crc32, uint8_t* data, uint16_t datalen) { NTSTATUS Status; uint16_t xasize; DIR_ITEM* xa; TRACE("(%p, %I64x, %I64x, %.*s, %08x, %p, %u)\n", Vcb, subvol->id, inode, namelen, name, crc32, data, datalen); xasize = (uint16_t)offsetof(DIR_ITEM, name[0]) + namelen + datalen; xa = ExAllocatePoolWithTag(PagedPool, xasize, ALLOC_TAG); if (!xa) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } xa->key.obj_id = 0; xa->key.obj_type = 0; xa->key.offset = 0; xa->transid = Vcb->superblock.generation; xa->m = datalen; xa->n = namelen; xa->type = BTRFS_TYPE_EA; RtlCopyMemory(xa->name, name, namelen); RtlCopyMemory(xa->name + namelen, data, datalen); Status = insert_tree_item_batch(batchlist, Vcb, subvol, inode, TYPE_XATTR_ITEM, crc32, xa, xasize, Batch_SetXattr); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); ExFreePool(xa); return Status; } return STATUS_SUCCESS; } static NTSTATUS delete_xattr(device_extension* Vcb, LIST_ENTRY* batchlist, root* subvol, uint64_t inode, char* name, uint16_t namelen, uint32_t crc32) { NTSTATUS Status; uint16_t xasize; DIR_ITEM* xa; TRACE("(%p, %I64x, %I64x, %.*s, %08x)\n", Vcb, subvol->id, inode, namelen, name, crc32); xasize = (uint16_t)offsetof(DIR_ITEM, name[0]) + namelen; xa = ExAllocatePoolWithTag(PagedPool, xasize, ALLOC_TAG); if (!xa) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } xa->key.obj_id = 0; xa->key.obj_type = 0; xa->key.offset = 0; xa->transid = Vcb->superblock.generation; xa->m = 0; xa->n = namelen; xa->type = BTRFS_TYPE_EA; RtlCopyMemory(xa->name, name, namelen); Status = insert_tree_item_batch(batchlist, Vcb, subvol, inode, TYPE_XATTR_ITEM, crc32, xa, xasize, Batch_DeleteXattr); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); ExFreePool(xa); return Status; } return STATUS_SUCCESS; } static NTSTATUS insert_sparse_extent(fcb* fcb, LIST_ENTRY* batchlist, uint64_t start, uint64_t length) { NTSTATUS Status; EXTENT_DATA* ed; EXTENT_DATA2* ed2; TRACE("((%I64x, %I64x), %I64x, %I64x)\n", fcb->subvol->id, fcb->inode, start, length); ed = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG); if (!ed) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ed->generation = fcb->Vcb->superblock.generation; ed->decoded_size = length; ed->compression = BTRFS_COMPRESSION_NONE; ed->encryption = BTRFS_ENCRYPTION_NONE; ed->encoding = BTRFS_ENCODING_NONE; ed->type = EXTENT_TYPE_REGULAR; ed2 = (EXTENT_DATA2*)ed->data; ed2->address = 0; ed2->size = 0; ed2->offset = 0; ed2->num_bytes = length; 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); ExFreePool(ed); return Status; } return STATUS_SUCCESS; } static NTSTATUS split_batch_item_list(batch_item_ind* bii) { LIST_ENTRY* le; unsigned int i = 0; LIST_ENTRY* midpoint = NULL; batch_item_ind* bii2; batch_item* midpoint_item; LIST_ENTRY* before_midpoint; le = bii->items.Flink; while (le != &bii->items) { if (i >= bii->num_items / 2) { midpoint = le; break; } i++; le = le->Flink; } if (!midpoint) return STATUS_SUCCESS; // make sure items on either side of split don't have same key while (midpoint->Blink != &bii->items) { batch_item* item = CONTAINING_RECORD(midpoint, batch_item, list_entry); batch_item* prev = CONTAINING_RECORD(midpoint->Blink, batch_item, list_entry); if (item->key.obj_id != prev->key.obj_id) break; if (item->key.obj_type != prev->key.obj_type) break; if (item->key.offset != prev->key.offset) break; midpoint = midpoint->Blink; i--; } if (midpoint->Blink == &bii->items) return STATUS_SUCCESS; bii2 = ExAllocatePoolWithTag(PagedPool, sizeof(batch_item_ind), ALLOC_TAG); if (!bii2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } midpoint_item = CONTAINING_RECORD(midpoint, batch_item, list_entry); bii2->key.obj_id = midpoint_item->key.obj_id; bii2->key.obj_type = midpoint_item->key.obj_type; bii2->key.offset = midpoint_item->key.offset; bii2->num_items = bii->num_items - i; bii->num_items = i; before_midpoint = midpoint->Blink; bii2->items.Flink = midpoint; midpoint->Blink = &bii2->items; bii2->items.Blink = bii->items.Blink; bii->items.Blink->Flink = &bii2->items; bii->items.Blink = before_midpoint; before_midpoint->Flink = &bii->items; InsertHeadList(&bii->list_entry, &bii2->list_entry); return STATUS_SUCCESS; } #ifdef _MSC_VER #pragma warning(push) #pragma warning(suppress: 28194) #endif static NTSTATUS insert_tree_item_batch(LIST_ENTRY* batchlist, device_extension* Vcb, root* r, uint64_t objid, uint8_t objtype, uint64_t offset, _In_opt_ _When_(return >= 0, __drv_aliasesMem) void* data, uint16_t datalen, enum batch_operation operation) { LIST_ENTRY* le; batch_root* br = NULL; batch_item* bi; le = batchlist->Flink; while (le != batchlist) { batch_root* br2 = CONTAINING_RECORD(le, batch_root, list_entry); if (br2->r == r) { br = br2; break; } le = le->Flink; } if (!br) { br = ExAllocatePoolWithTag(PagedPool, sizeof(batch_root), ALLOC_TAG); if (!br) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } br->r = r; InitializeListHead(&br->items_ind); InsertTailList(batchlist, &br->list_entry); } if (IsListEmpty(&br->items_ind)) { batch_item_ind* bii; bii = ExAllocatePoolWithTag(PagedPool, sizeof(batch_item_ind), ALLOC_TAG); if (!bii) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } bii->key.obj_id = 0; bii->key.obj_type = 0; bii->key.offset = 0; InitializeListHead(&bii->items); bii->num_items = 0; InsertTailList(&br->items_ind, &bii->list_entry); } bi = ExAllocateFromPagedLookasideList(&Vcb->batch_item_lookaside); if (!bi) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } bi->key.obj_id = objid; bi->key.obj_type = objtype; bi->key.offset = offset; bi->data = data; bi->datalen = datalen; bi->operation = operation; le = br->items_ind.Blink; while (le != &br->items_ind) { LIST_ENTRY* le2; batch_item_ind* bii = CONTAINING_RECORD(le, batch_item_ind, list_entry); if (keycmp(bii->key, bi->key) == 1) { le = le->Blink; continue; } le2 = bii->items.Blink; while (le2 != &bii->items) { batch_item* bi2 = CONTAINING_RECORD(le2, batch_item, list_entry); int cmp = keycmp(bi2->key, bi->key); if (cmp == -1 || (cmp == 0 && bi->operation >= bi2->operation)) { InsertHeadList(&bi2->list_entry, &bi->list_entry); bii->num_items++; goto end; } le2 = le2->Blink; } InsertHeadList(&bii->items, &bi->list_entry); bii->num_items++; end: if (bii->num_items > BATCH_ITEM_LIMIT) return split_batch_item_list(bii); return STATUS_SUCCESS; } return STATUS_INTERNAL_ERROR; } #ifdef _MSC_VER #pragma warning(pop) #endif typedef struct { uint64_t address; uint64_t length; uint64_t offset; bool changed; chunk* chunk; uint64_t skip_start; uint64_t skip_end; LIST_ENTRY list_entry; } extent_range; static void rationalize_extents(fcb* fcb, PIRP Irp) { LIST_ENTRY* le; LIST_ENTRY extent_ranges; extent_range* er; bool changed = false, truncating = false; uint32_t num_extents = 0; InitializeListHead(&extent_ranges); le = fcb->extents.Flink; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->unique) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data; if (ed2->size != 0) { LIST_ENTRY* le2; le2 = extent_ranges.Flink; while (le2 != &extent_ranges) { extent_range* er2 = CONTAINING_RECORD(le2, extent_range, list_entry); if (er2->address == ed2->address) { er2->skip_start = min(er2->skip_start, ed2->offset); er2->skip_end = min(er2->skip_end, ed2->size - ed2->offset - ed2->num_bytes); goto cont; } else if (er2->address > ed2->address) break; le2 = le2->Flink; } er = ExAllocatePoolWithTag(PagedPool, sizeof(extent_range), ALLOC_TAG); // FIXME - should be from lookaside? if (!er) { ERR("out of memory\n"); goto end; } er->address = ed2->address; er->length = ed2->size; er->offset = ext->offset - ed2->offset; er->changed = false; er->chunk = NULL; er->skip_start = ed2->offset; er->skip_end = ed2->size - ed2->offset - ed2->num_bytes; if (er->skip_start != 0 || er->skip_end != 0) truncating = true; InsertHeadList(le2->Blink, &er->list_entry); num_extents++; } } cont: le = le->Flink; } if (num_extents == 0 || (num_extents == 1 && !truncating)) goto end; le = extent_ranges.Flink; while (le != &extent_ranges) { er = CONTAINING_RECORD(le, extent_range, list_entry); if (!er->chunk) { LIST_ENTRY* le2; er->chunk = get_chunk_from_address(fcb->Vcb, er->address); if (!er->chunk) { ERR("get_chunk_from_address(%I64x) failed\n", er->address); goto end; } le2 = le->Flink; while (le2 != &extent_ranges) { extent_range* er2 = CONTAINING_RECORD(le2, extent_range, list_entry); if (!er2->chunk && er2->address >= er->chunk->offset && er2->address < er->chunk->offset + er->chunk->chunk_item->size) er2->chunk = er->chunk; le2 = le2->Flink; } } le = le->Flink; } if (truncating) { // truncate beginning or end of extent if unused le = extent_ranges.Flink; while (le != &extent_ranges) { er = CONTAINING_RECORD(le, extent_range, list_entry); if (er->skip_start > 0) { LIST_ENTRY* le2 = fcb->extents.Flink; while (le2 != &fcb->extents) { extent* ext = CONTAINING_RECORD(le2, extent, list_entry); if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->unique) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data; if (ed2->size != 0 && ed2->address == er->address) { NTSTATUS Status; Status = update_changed_extent_ref(fcb->Vcb, er->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, -1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, true, Irp); if (!NT_SUCCESS(Status)) { ERR("update_changed_extent_ref returned %08lx\n", Status); goto end; } ext->extent_data.decoded_size -= er->skip_start; ed2->size -= er->skip_start; ed2->address += er->skip_start; ed2->offset -= er->skip_start; add_changed_extent_ref(er->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, 1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM); } } le2 = le2->Flink; } if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) add_checksum_entry(fcb->Vcb, er->address, (ULONG)(er->skip_start >> fcb->Vcb->sector_shift), NULL, NULL); acquire_chunk_lock(er->chunk, fcb->Vcb); if (!er->chunk->cache_loaded) { NTSTATUS Status = load_cache_chunk(fcb->Vcb, er->chunk, NULL); if (!NT_SUCCESS(Status)) { ERR("load_cache_chunk returned %08lx\n", Status); release_chunk_lock(er->chunk, fcb->Vcb); goto end; } } er->chunk->used -= er->skip_start; space_list_add(er->chunk, er->address, er->skip_start, NULL); release_chunk_lock(er->chunk, fcb->Vcb); er->address += er->skip_start; er->length -= er->skip_start; } if (er->skip_end > 0) { LIST_ENTRY* le2 = fcb->extents.Flink; while (le2 != &fcb->extents) { extent* ext = CONTAINING_RECORD(le2, extent, list_entry); if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->unique) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data; if (ed2->size != 0 && ed2->address == er->address) { NTSTATUS Status; Status = update_changed_extent_ref(fcb->Vcb, er->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, -1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, true, Irp); if (!NT_SUCCESS(Status)) { ERR("update_changed_extent_ref returned %08lx\n", Status); goto end; } ext->extent_data.decoded_size -= er->skip_end; ed2->size -= er->skip_end; add_changed_extent_ref(er->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, 1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM); } } le2 = le2->Flink; } if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) add_checksum_entry(fcb->Vcb, er->address + er->length - er->skip_end, (ULONG)(er->skip_end >> fcb->Vcb->sector_shift), NULL, NULL); acquire_chunk_lock(er->chunk, fcb->Vcb); if (!er->chunk->cache_loaded) { NTSTATUS Status = load_cache_chunk(fcb->Vcb, er->chunk, NULL); if (!NT_SUCCESS(Status)) { ERR("load_cache_chunk returned %08lx\n", Status); release_chunk_lock(er->chunk, fcb->Vcb); goto end; } } er->chunk->used -= er->skip_end; space_list_add(er->chunk, er->address + er->length - er->skip_end, er->skip_end, NULL); release_chunk_lock(er->chunk, fcb->Vcb); er->length -= er->skip_end; } le = le->Flink; } } if (num_extents < 2) goto end; // merge together adjacent extents le = extent_ranges.Flink; while (le != &extent_ranges) { er = CONTAINING_RECORD(le, extent_range, list_entry); if (le->Flink != &extent_ranges && er->length < MAX_EXTENT_SIZE) { extent_range* er2 = CONTAINING_RECORD(le->Flink, extent_range, list_entry); if (er->chunk == er2->chunk) { if (er2->address == er->address + er->length && er2->offset >= er->offset + er->length) { if (er->length + er2->length <= MAX_EXTENT_SIZE) { er->length += er2->length; er->changed = true; RemoveEntryList(&er2->list_entry); ExFreePool(er2); changed = true; continue; } } } } le = le->Flink; } if (!changed) goto end; le = fcb->extents.Flink; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->unique) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data; if (ed2->size != 0) { LIST_ENTRY* le2; le2 = extent_ranges.Flink; while (le2 != &extent_ranges) { extent_range* er2 = CONTAINING_RECORD(le2, extent_range, list_entry); if (ed2->address >= er2->address && ed2->address + ed2->size <= er2->address + er2->length && er2->changed) { NTSTATUS Status; Status = update_changed_extent_ref(fcb->Vcb, er2->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, -1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, true, Irp); if (!NT_SUCCESS(Status)) { ERR("update_changed_extent_ref returned %08lx\n", Status); goto end; } ed2->offset += ed2->address - er2->address; ed2->address = er2->address; ed2->size = er2->length; ext->extent_data.decoded_size = ed2->size; add_changed_extent_ref(er2->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, 1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM); break; } le2 = le2->Flink; } } } le = le->Flink; } end: while (!IsListEmpty(&extent_ranges)) { le = RemoveHeadList(&extent_ranges); er = CONTAINING_RECORD(le, extent_range, list_entry); ExFreePool(er); } } NTSTATUS flush_fcb(fcb* fcb, bool cache, LIST_ENTRY* batchlist, PIRP Irp) { traverse_ptr tp; KEY searchkey; NTSTATUS Status; INODE_ITEM* ii; uint64_t ii_offset; #ifdef DEBUG_PARANOID uint64_t old_size = 0; bool extents_changed; #endif if (fcb->ads) { if (fcb->deleted) { Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adsxattr.Length, fcb->adshash); if (!NT_SUCCESS(Status)) { ERR("delete_xattr returned %08lx\n", Status); goto end; } } else { Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adsxattr.Length, fcb->adshash, (uint8_t*)fcb->adsdata.Buffer, fcb->adsdata.Length); if (!NT_SUCCESS(Status)) { ERR("set_xattr returned %08lx\n", Status); goto end; } } Status = STATUS_SUCCESS; goto end; } if (fcb->deleted) { Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0xffffffffffffffff, NULL, 0, Batch_DeleteInode); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); goto end; } if (fcb->marked_as_orphan) { Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, BTRFS_ORPHAN_INODE_OBJID, TYPE_ORPHAN_INODE, fcb->inode, NULL, 0, Batch_Delete); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); goto end; } } Status = STATUS_SUCCESS; goto end; } #ifdef DEBUG_PARANOID extents_changed = fcb->extents_changed; #endif if (fcb->extents_changed) { LIST_ENTRY* le; bool prealloc = false, extents_inline = false; uint64_t last_end; // delete ignored extent items le = fcb->extents.Flink; while (le != &fcb->extents) { LIST_ENTRY* le2 = le->Flink; extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (ext->ignore) { RemoveEntryList(&ext->list_entry); if (ext->csum) ExFreePool(ext->csum); ExFreePool(ext); } le = le2; } le = fcb->extents.Flink; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (ext->inserted && ext->csum && ext->extent_data.type == EXTENT_TYPE_REGULAR) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data; if (ed2->size > 0) { // not sparse if (ext->extent_data.compression == BTRFS_COMPRESSION_NONE) add_checksum_entry(fcb->Vcb, ed2->address + ed2->offset, (ULONG)(ed2->num_bytes >> fcb->Vcb->sector_shift), ext->csum, Irp); else add_checksum_entry(fcb->Vcb, ed2->address, (ULONG)(ed2->size >> fcb->Vcb->sector_shift), ext->csum, Irp); } } le = le->Flink; } if (!IsListEmpty(&fcb->extents)) { rationalize_extents(fcb, Irp); // merge together adjacent EXTENT_DATAs pointing to same extent le = fcb->extents.Flink; while (le != &fcb->extents) { LIST_ENTRY* le2 = le->Flink; extent* ext = CONTAINING_RECORD(le, extent, list_entry); if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && le->Flink != &fcb->extents) { extent* nextext = CONTAINING_RECORD(le->Flink, extent, list_entry); if (ext->extent_data.type == nextext->extent_data.type) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data; EXTENT_DATA2* ned2 = (EXTENT_DATA2*)nextext->extent_data.data; if (ed2->size != 0 && ed2->address == ned2->address && ed2->size == ned2->size && nextext->offset == ext->offset + ed2->num_bytes && ned2->offset == ed2->offset + ed2->num_bytes) { chunk* c; if (ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->csum) { ULONG len = (ULONG)((ed2->num_bytes + ned2->num_bytes) >> fcb->Vcb->sector_shift); void* csum; csum = ExAllocatePoolWithTag(NonPagedPool, len * fcb->Vcb->csum_size, ALLOC_TAG); if (!csum) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(csum, ext->csum, (ULONG)((ed2->num_bytes * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift)); RtlCopyMemory((uint8_t*)csum + ((ed2->num_bytes * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift), nextext->csum, (ULONG)((ned2->num_bytes * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift)); ExFreePool(ext->csum); ext->csum = csum; } ext->extent_data.generation = fcb->Vcb->superblock.generation; ed2->num_bytes += ned2->num_bytes; RemoveEntryList(&nextext->list_entry); if (nextext->csum) ExFreePool(nextext->csum); ExFreePool(nextext); c = get_chunk_from_address(fcb->Vcb, ed2->address); if (!c) { ERR("get_chunk_from_address(%I64x) failed\n", ed2->address); } else { Status = update_changed_extent_ref(fcb->Vcb, c, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, -1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp); if (!NT_SUCCESS(Status)) { ERR("update_changed_extent_ref returned %08lx\n", Status); goto end; } } le2 = le; } } } le = le2; } } if (!fcb->created) { // delete existing EXTENT_DATA items Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, 0, NULL, 0, Batch_DeleteExtentData); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); goto end; } } // add new EXTENT_DATAs last_end = 0; le = fcb->extents.Flink; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); EXTENT_DATA* ed; ext->inserted = false; if (!(fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_NO_HOLES) && ext->offset > last_end) { Status = insert_sparse_extent(fcb, batchlist, last_end, ext->offset - last_end); if (!NT_SUCCESS(Status)) { ERR("insert_sparse_extent returned %08lx\n", Status); goto end; } } ed = ExAllocatePoolWithTag(PagedPool, ext->datalen, ALLOC_TAG); if (!ed) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(ed, &ext->extent_data, ext->datalen); Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, ext->offset, ed, ext->datalen, Batch_Insert); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); goto end; } if (ed->type == EXTENT_TYPE_PREALLOC) prealloc = true; if (ed->type == EXTENT_TYPE_INLINE) extents_inline = true; if (!(fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_NO_HOLES)) { if (ed->type == EXTENT_TYPE_INLINE) last_end = ext->offset + ed->decoded_size; else { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; last_end = ext->offset + ed2->num_bytes; } } le = le->Flink; } if (!(fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_NO_HOLES) && !extents_inline && sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size) > last_end) { Status = insert_sparse_extent(fcb, batchlist, last_end, sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size) - last_end); if (!NT_SUCCESS(Status)) { ERR("insert_sparse_extent returned %08lx\n", Status); goto end; } } // update prealloc flag in INODE_ITEM if (!prealloc) fcb->inode_item.flags &= ~BTRFS_INODE_PREALLOC; else fcb->inode_item.flags |= BTRFS_INODE_PREALLOC; fcb->inode_item_changed = true; fcb->extents_changed = false; } if ((!fcb->created && fcb->inode_item_changed) || cache) { searchkey.obj_id = fcb->inode; searchkey.obj_type = TYPE_INODE_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); goto end; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { if (cache) { ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG); if (!ii) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM)); Status = insert_tree_item(fcb->Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); goto end; } ii_offset = 0; } else { ERR("could not find INODE_ITEM for inode %I64x in subvol %I64x\n", fcb->inode, fcb->subvol->id); Status = STATUS_INTERNAL_ERROR; goto end; } } else { #ifdef DEBUG_PARANOID INODE_ITEM* ii2 = (INODE_ITEM*)tp.item->data; old_size = ii2->st_size; #endif ii_offset = tp.item->key.offset; } if (!cache) { Status = delete_tree_item(fcb->Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); goto end; } } else { searchkey.obj_id = fcb->inode; searchkey.obj_type = TYPE_INODE_ITEM; searchkey.offset = ii_offset; Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); goto end; } if (keycmp(tp.item->key, searchkey)) { ERR("could not find INODE_ITEM for inode %I64x in subvol %I64x\n", fcb->inode, fcb->subvol->id); Status = STATUS_INTERNAL_ERROR; goto end; } else RtlCopyMemory(tp.item->data, &fcb->inode_item, min(tp.item->size, sizeof(INODE_ITEM))); } #ifdef DEBUG_PARANOID if (!extents_changed && fcb->type != BTRFS_TYPE_DIRECTORY && old_size != fcb->inode_item.st_size) { ERR("error - size has changed but extents not marked as changed\n"); int3; } #endif } else ii_offset = 0; fcb->created = false; if (!cache && fcb->inode_item_changed) { ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG); if (!ii) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM)); Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, ii_offset, ii, sizeof(INODE_ITEM), Batch_Insert); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); goto end; } fcb->inode_item_changed = false; } if (fcb->sd_dirty) { if (!fcb->sd_deleted) { Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_NTACL, sizeof(EA_NTACL) - 1, EA_NTACL_HASH, (uint8_t*)fcb->sd, (uint16_t)RtlLengthSecurityDescriptor(fcb->sd)); if (!NT_SUCCESS(Status)) { ERR("set_xattr returned %08lx\n", Status); goto end; } } else { Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_NTACL, sizeof(EA_NTACL) - 1, EA_NTACL_HASH); if (!NT_SUCCESS(Status)) { ERR("delete_xattr returned %08lx\n", Status); goto end; } } fcb->sd_deleted = false; fcb->sd_dirty = false; } if (fcb->atts_changed) { if (!fcb->atts_deleted) { uint8_t val[16], *val2; ULONG atts = fcb->atts; TRACE("inserting new DOSATTRIB xattr\n"); if (fcb->inode == SUBVOL_ROOT_INODE) atts &= ~FILE_ATTRIBUTE_READONLY; val2 = &val[sizeof(val) - 1]; do { uint8_t c = atts % 16; *val2 = c <= 9 ? (c + '0') : (c - 0xa + 'a'); val2--; atts >>= 4; } while (atts != 0); *val2 = 'x'; val2--; *val2 = '0'; Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_DOSATTRIB, sizeof(EA_DOSATTRIB) - 1, EA_DOSATTRIB_HASH, val2, (uint16_t)(val + sizeof(val) - val2)); if (!NT_SUCCESS(Status)) { ERR("set_xattr returned %08lx\n", Status); goto end; } } else { Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_DOSATTRIB, sizeof(EA_DOSATTRIB) - 1, EA_DOSATTRIB_HASH); if (!NT_SUCCESS(Status)) { ERR("delete_xattr returned %08lx\n", Status); goto end; } } fcb->atts_changed = false; fcb->atts_deleted = false; } if (fcb->reparse_xattr_changed) { if (fcb->reparse_xattr.Buffer && fcb->reparse_xattr.Length > 0) { Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_REPARSE, sizeof(EA_REPARSE) - 1, EA_REPARSE_HASH, (uint8_t*)fcb->reparse_xattr.Buffer, (uint16_t)fcb->reparse_xattr.Length); if (!NT_SUCCESS(Status)) { ERR("set_xattr returned %08lx\n", Status); goto end; } } else { Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_REPARSE, sizeof(EA_REPARSE) - 1, EA_REPARSE_HASH); if (!NT_SUCCESS(Status)) { ERR("delete_xattr returned %08lx\n", Status); goto end; } } fcb->reparse_xattr_changed = false; } if (fcb->ea_changed) { if (fcb->ea_xattr.Buffer && fcb->ea_xattr.Length > 0) { Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_EA, sizeof(EA_EA) - 1, EA_EA_HASH, (uint8_t*)fcb->ea_xattr.Buffer, (uint16_t)fcb->ea_xattr.Length); if (!NT_SUCCESS(Status)) { ERR("set_xattr returned %08lx\n", Status); goto end; } } else { Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_EA, sizeof(EA_EA) - 1, EA_EA_HASH); if (!NT_SUCCESS(Status)) { ERR("delete_xattr returned %08lx\n", Status); goto end; } } fcb->ea_changed = false; } if (fcb->prop_compression_changed) { if (fcb->prop_compression == PropCompression_None) { Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_PROP_COMPRESSION, sizeof(EA_PROP_COMPRESSION) - 1, EA_PROP_COMPRESSION_HASH); if (!NT_SUCCESS(Status)) { ERR("delete_xattr returned %08lx\n", Status); goto end; } } else if (fcb->prop_compression == PropCompression_Zlib) { static const char zlib[] = "zlib"; Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_PROP_COMPRESSION, sizeof(EA_PROP_COMPRESSION) - 1, EA_PROP_COMPRESSION_HASH, (uint8_t*)zlib, sizeof(zlib) - 1); if (!NT_SUCCESS(Status)) { ERR("set_xattr returned %08lx\n", Status); goto end; } } else if (fcb->prop_compression == PropCompression_LZO) { static const char lzo[] = "lzo"; Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_PROP_COMPRESSION, sizeof(EA_PROP_COMPRESSION) - 1, EA_PROP_COMPRESSION_HASH, (uint8_t*)lzo, sizeof(lzo) - 1); if (!NT_SUCCESS(Status)) { ERR("set_xattr returned %08lx\n", Status); goto end; } } else if (fcb->prop_compression == PropCompression_ZSTD) { static const char zstd[] = "zstd"; Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_PROP_COMPRESSION, sizeof(EA_PROP_COMPRESSION) - 1, EA_PROP_COMPRESSION_HASH, (uint8_t*)zstd, sizeof(zstd) - 1); if (!NT_SUCCESS(Status)) { ERR("set_xattr returned %08lx\n", Status); goto end; } } fcb->prop_compression_changed = false; } if (fcb->xattrs_changed) { LIST_ENTRY* le; le = fcb->xattrs.Flink; while (le != &fcb->xattrs) { xattr* xa = CONTAINING_RECORD(le, xattr, list_entry); LIST_ENTRY* le2 = le->Flink; if (xa->dirty) { uint32_t hash = calc_crc32c(0xfffffffe, (uint8_t*)xa->data, xa->namelen); if (xa->valuelen == 0) { Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, xa->data, xa->namelen, hash); if (!NT_SUCCESS(Status)) { ERR("delete_xattr returned %08lx\n", Status); goto end; } RemoveEntryList(&xa->list_entry); ExFreePool(xa); } else { Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, xa->data, xa->namelen, hash, (uint8_t*)&xa->data[xa->namelen], xa->valuelen); if (!NT_SUCCESS(Status)) { ERR("set_xattr returned %08lx\n", Status); goto end; } xa->dirty = false; } } le = le2; } fcb->xattrs_changed = false; } if ((fcb->case_sensitive_set && !fcb->case_sensitive)) { Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_CASE_SENSITIVE, sizeof(EA_CASE_SENSITIVE) - 1, EA_CASE_SENSITIVE_HASH); if (!NT_SUCCESS(Status)) { ERR("delete_xattr returned %08lx\n", Status); goto end; } fcb->case_sensitive_set = false; } else if ((!fcb->case_sensitive_set && fcb->case_sensitive)) { Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_CASE_SENSITIVE, sizeof(EA_CASE_SENSITIVE) - 1, EA_CASE_SENSITIVE_HASH, (uint8_t*)"1", 1); if (!NT_SUCCESS(Status)) { ERR("set_xattr returned %08lx\n", Status); goto end; } fcb->case_sensitive_set = true; } if (fcb->inode_item.st_nlink == 0 && !fcb->marked_as_orphan) { // mark as orphan Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, BTRFS_ORPHAN_INODE_OBJID, TYPE_ORPHAN_INODE, fcb->inode, NULL, 0, Batch_Insert); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); goto end; } fcb->marked_as_orphan = true; } Status = STATUS_SUCCESS; end: if (fcb->dirty) { bool lock = false; fcb->dirty = false; if (!ExIsResourceAcquiredExclusiveLite(&fcb->Vcb->dirty_fcbs_lock)) { ExAcquireResourceExclusiveLite(&fcb->Vcb->dirty_fcbs_lock, true); lock = true; } RemoveEntryList(&fcb->list_entry_dirty); if (lock) ExReleaseResourceLite(&fcb->Vcb->dirty_fcbs_lock); } return Status; } void add_trim_entry_avoid_sb(device_extension* Vcb, device* dev, uint64_t address, uint64_t size) { int i; ULONG sblen = (ULONG)sector_align(sizeof(superblock), Vcb->superblock.sector_size); i = 0; while (superblock_addrs[i] != 0) { if (superblock_addrs[i] + sblen >= address && superblock_addrs[i] < address + size) { if (superblock_addrs[i] > address) add_trim_entry(dev, address, superblock_addrs[i] - address); if (size <= superblock_addrs[i] + sblen - address) return; size -= superblock_addrs[i] + sblen - address; address = superblock_addrs[i] + sblen; } else if (superblock_addrs[i] > address + size) break; i++; } add_trim_entry(dev, address, size); } static NTSTATUS drop_chunk(device_extension* Vcb, chunk* c, LIST_ENTRY* batchlist, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; uint64_t i, factor; CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];; TRACE("dropping chunk %I64x\n", c->offset); if (c->chunk_item->type & BLOCK_FLAG_RAID0) factor = c->chunk_item->num_stripes; else if (c->chunk_item->type & BLOCK_FLAG_RAID10) factor = c->chunk_item->num_stripes / c->chunk_item->sub_stripes; else if (c->chunk_item->type & BLOCK_FLAG_RAID5) factor = c->chunk_item->num_stripes - 1; else if (c->chunk_item->type & BLOCK_FLAG_RAID6) factor = c->chunk_item->num_stripes - 2; else // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4 factor = 1; // do TRIM if (Vcb->trim && !Vcb->options.no_trim) { uint64_t len = c->chunk_item->size / factor; for (i = 0; i < c->chunk_item->num_stripes; i++) { if (c->devices[i] && c->devices[i]->devobj && !c->devices[i]->readonly && c->devices[i]->trim) add_trim_entry_avoid_sb(Vcb, c->devices[i], cis[i].offset, len); } } if (!c->cache) { Status = load_stored_free_space_cache(Vcb, c, true, Irp); if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) WARN("load_stored_free_space_cache returned %08lx\n", Status); } // remove free space cache if (c->cache) { c->cache->deleted = true; Status = excise_extents(Vcb, c->cache, 0, c->cache->inode_item.st_size, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); return Status; } Status = flush_fcb(c->cache, true, batchlist, Irp); free_fcb(c->cache); if (c->cache->refcount == 0) reap_fcb(c->cache); if (!NT_SUCCESS(Status)) { ERR("flush_fcb returned %08lx\n", Status); return Status; } searchkey.obj_id = FREE_SPACE_CACHE_ID; searchkey.obj_type = 0; searchkey.offset = c->offset; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (!keycmp(tp.item->key, searchkey)) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } } if (Vcb->space_root) { Status = insert_tree_item_batch(batchlist, Vcb, Vcb->space_root, c->offset, TYPE_FREE_SPACE_INFO, c->chunk_item->size, NULL, 0, Batch_DeleteFreeSpace); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); return Status; } } for (i = 0; i < c->chunk_item->num_stripes; i++) { if (!c->created) { // remove DEV_EXTENTs from tree 4 searchkey.obj_id = cis[i].dev_id; searchkey.obj_type = TYPE_DEV_EXTENT; searchkey.offset = cis[i].offset; Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (!keycmp(tp.item->key, searchkey)) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } if (tp.item->size >= sizeof(DEV_EXTENT)) { DEV_EXTENT* de = (DEV_EXTENT*)tp.item->data; c->devices[i]->devitem.bytes_used -= de->length; if (Vcb->balance.thread && Vcb->balance.shrinking && Vcb->balance.opts[0].devid == c->devices[i]->devitem.dev_id) { if (cis[i].offset < Vcb->balance.opts[0].drange_start && cis[i].offset + de->length > Vcb->balance.opts[0].drange_start) space_list_add2(&c->devices[i]->space, NULL, cis[i].offset, Vcb->balance.opts[0].drange_start - cis[i].offset, NULL, rollback); } else space_list_add2(&c->devices[i]->space, NULL, cis[i].offset, de->length, NULL, rollback); } } else WARN("could not find (%I64x,%x,%I64x) in dev tree\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); } else { uint64_t len = c->chunk_item->size / factor; c->devices[i]->devitem.bytes_used -= len; if (Vcb->balance.thread && Vcb->balance.shrinking && Vcb->balance.opts[0].devid == c->devices[i]->devitem.dev_id) { if (cis[i].offset < Vcb->balance.opts[0].drange_start && cis[i].offset + len > Vcb->balance.opts[0].drange_start) space_list_add2(&c->devices[i]->space, NULL, cis[i].offset, Vcb->balance.opts[0].drange_start - cis[i].offset, NULL, rollback); } else space_list_add2(&c->devices[i]->space, NULL, cis[i].offset, len, NULL, rollback); } } // modify DEV_ITEMs in chunk tree for (i = 0; i < c->chunk_item->num_stripes; i++) { if (c->devices[i]) { uint64_t j; DEV_ITEM* di; searchkey.obj_id = 1; searchkey.obj_type = TYPE_DEV_ITEM; searchkey.offset = c->devices[i]->devitem.dev_id; Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (!keycmp(tp.item->key, searchkey)) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } di = ExAllocatePoolWithTag(PagedPool, sizeof(DEV_ITEM), ALLOC_TAG); if (!di) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(di, &c->devices[i]->devitem, sizeof(DEV_ITEM)); Status = insert_tree_item(Vcb, Vcb->chunk_root, 1, TYPE_DEV_ITEM, c->devices[i]->devitem.dev_id, di, sizeof(DEV_ITEM), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } } for (j = i + 1; j < c->chunk_item->num_stripes; j++) { if (c->devices[j] == c->devices[i]) c->devices[j] = NULL; } } } if (!c->created) { // remove CHUNK_ITEM from chunk tree searchkey.obj_id = 0x100; searchkey.obj_type = TYPE_CHUNK_ITEM; searchkey.offset = c->offset; Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (!keycmp(tp.item->key, searchkey)) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } else WARN("could not find CHUNK_ITEM for chunk %I64x\n", c->offset); // remove BLOCK_GROUP_ITEM from extent tree searchkey.obj_id = c->offset; searchkey.obj_type = TYPE_BLOCK_GROUP_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->block_group_root ? Vcb->block_group_root : Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } else WARN("could not find BLOCK_GROUP_ITEM for chunk %I64x\n", c->offset); } if (c->chunk_item->type & BLOCK_FLAG_SYSTEM) remove_from_bootstrap(Vcb, 0x100, TYPE_CHUNK_ITEM, c->offset); RemoveEntryList(&c->list_entry); // clear raid56 incompat flag if dropping last RAID5/6 chunk if (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6) { LIST_ENTRY* le; bool clear_flag = true; le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c2 = CONTAINING_RECORD(le, chunk, list_entry); if (c2->chunk_item->type & BLOCK_FLAG_RAID5 || c2->chunk_item->type & BLOCK_FLAG_RAID6) { clear_flag = false; break; } le = le->Flink; } if (clear_flag) Vcb->superblock.incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_RAID56; } // clear raid1c34 incompat flag if dropping last RAID5/6 chunk if (c->chunk_item->type & BLOCK_FLAG_RAID1C3 || c->chunk_item->type & BLOCK_FLAG_RAID1C4) { LIST_ENTRY* le; bool clear_flag = true; le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c2 = CONTAINING_RECORD(le, chunk, list_entry); if (c2->chunk_item->type & BLOCK_FLAG_RAID1C3 || c2->chunk_item->type & BLOCK_FLAG_RAID1C4) { clear_flag = false; break; } le = le->Flink; } if (clear_flag) Vcb->superblock.incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_RAID1C34; } Vcb->superblock.bytes_used -= c->oldused; ExFreePool(c->chunk_item); ExFreePool(c->devices); while (!IsListEmpty(&c->space)) { space* s = CONTAINING_RECORD(c->space.Flink, space, list_entry); RemoveEntryList(&s->list_entry); ExFreePool(s); } while (!IsListEmpty(&c->deleting)) { space* s = CONTAINING_RECORD(c->deleting.Flink, space, list_entry); RemoveEntryList(&s->list_entry); ExFreePool(s); } release_chunk_lock(c, Vcb); ExDeleteResourceLite(&c->partial_stripes_lock); ExDeleteResourceLite(&c->range_locks_lock); ExDeleteResourceLite(&c->lock); ExDeleteResourceLite(&c->changed_extents_lock); ExFreePool(c); return STATUS_SUCCESS; } static NTSTATUS partial_stripe_read(device_extension* Vcb, chunk* c, partial_stripe* ps, uint64_t startoff, uint16_t parity, ULONG offset, ULONG len) { NTSTATUS Status; ULONG sl = (ULONG)(c->chunk_item->stripe_length >> Vcb->sector_shift); CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; while (len > 0) { ULONG readlen = min(offset + len, offset + (sl - (offset % sl))) - offset; uint16_t stripe; stripe = (parity + (offset / sl) + 1) % c->chunk_item->num_stripes; if (c->devices[stripe]->devobj) { Status = sync_read_phys(c->devices[stripe]->devobj, c->devices[stripe]->fileobj, cis[stripe].offset + startoff + ((offset % sl) << Vcb->sector_shift), readlen << Vcb->sector_shift, ps->data + (offset << Vcb->sector_shift), false); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); return Status; } } else if (c->chunk_item->type & BLOCK_FLAG_RAID5) { uint16_t i; uint8_t* scratch; scratch = ExAllocatePoolWithTag(NonPagedPool, readlen << Vcb->sector_shift, ALLOC_TAG); if (!scratch) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } for (i = 0; i < c->chunk_item->num_stripes; i++) { if (i != stripe) { if (!c->devices[i]->devobj) { ExFreePool(scratch); return STATUS_UNEXPECTED_IO_ERROR; } if (i == 0 || (stripe == 0 && i == 1)) { Status = sync_read_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + startoff + ((offset % sl) << Vcb->sector_shift), readlen << Vcb->sector_shift, ps->data + (offset << Vcb->sector_shift), false); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); ExFreePool(scratch); return Status; } } else { Status = sync_read_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + startoff + ((offset % sl) << Vcb->sector_shift), readlen << Vcb->sector_shift, scratch, false); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); ExFreePool(scratch); return Status; } do_xor(ps->data + (offset << Vcb->sector_shift), scratch, readlen << Vcb->sector_shift); } } } ExFreePool(scratch); } else { uint8_t* scratch; uint16_t k, i, logstripe, error_stripe, num_errors = 0; scratch = ExAllocatePoolWithTag(NonPagedPool, (c->chunk_item->num_stripes + 2) * readlen << Vcb->sector_shift, ALLOC_TAG); if (!scratch) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } i = (parity + 1) % c->chunk_item->num_stripes; logstripe = (c->chunk_item->num_stripes + c->chunk_item->num_stripes - 1 - parity + stripe) % c->chunk_item->num_stripes; for (k = 0; k < c->chunk_item->num_stripes; k++) { if (i != stripe) { if (c->devices[i]->devobj) { Status = sync_read_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + startoff + ((offset % sl) << Vcb->sector_shift), readlen << Vcb->sector_shift, scratch + (k * readlen << Vcb->sector_shift), false); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); num_errors++; error_stripe = k; } } else { num_errors++; error_stripe = k; } if (num_errors > 1) { ExFreePool(scratch); return STATUS_UNEXPECTED_IO_ERROR; } } i = (i + 1) % c->chunk_item->num_stripes; } if (num_errors == 0 || error_stripe == c->chunk_item->num_stripes - 1) { for (k = 0; k < c->chunk_item->num_stripes - 1; k++) { if (k != logstripe) { if (k == 0 || (k == 1 && logstripe == 0)) { RtlCopyMemory(ps->data + (offset << Vcb->sector_shift), scratch + (k * readlen << Vcb->sector_shift), readlen << Vcb->sector_shift); } else { do_xor(ps->data + (offset << Vcb->sector_shift), scratch + (k * readlen << Vcb->sector_shift), readlen << Vcb->sector_shift); } } } } else { raid6_recover2(scratch, c->chunk_item->num_stripes, readlen << Vcb->sector_shift, logstripe, error_stripe, scratch + (c->chunk_item->num_stripes * readlen << Vcb->sector_shift)); RtlCopyMemory(ps->data + (offset << Vcb->sector_shift), scratch + (c->chunk_item->num_stripes * readlen << Vcb->sector_shift), readlen << Vcb->sector_shift); } ExFreePool(scratch); } offset += readlen; len -= readlen; } return STATUS_SUCCESS; } NTSTATUS flush_partial_stripe(device_extension* Vcb, chunk* c, partial_stripe* ps) { NTSTATUS Status; uint16_t parity2, stripe, startoffstripe; uint8_t* data; uint64_t startoff; ULONG runlength, index, last1; CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; LIST_ENTRY* le; uint16_t k, num_data_stripes = c->chunk_item->num_stripes - (c->chunk_item->type & BLOCK_FLAG_RAID5 ? 1 : 2); uint64_t ps_length = num_data_stripes * c->chunk_item->stripe_length; ULONG stripe_length = (ULONG)c->chunk_item->stripe_length; // FIXME - do writes asynchronously? get_raid0_offset(ps->address - c->offset, stripe_length, num_data_stripes, &startoff, &startoffstripe); parity2 = (((ps->address - c->offset) / ps_length) + c->chunk_item->num_stripes - 1) % c->chunk_item->num_stripes; // read data (or reconstruct if degraded) runlength = RtlFindFirstRunClear(&ps->bmp, &index); last1 = 0; while (runlength != 0) { if (index >= ps->bmplen) break; if (index + runlength >= ps->bmplen) { runlength = ps->bmplen - index; if (runlength == 0) break; } if (index > last1) { Status = partial_stripe_read(Vcb, c, ps, startoff, parity2, last1, index - last1); if (!NT_SUCCESS(Status)) { ERR("partial_stripe_read returned %08lx\n", Status); return Status; } } last1 = index + runlength; runlength = RtlFindNextForwardRunClear(&ps->bmp, index + runlength, &index); } if (last1 < ps_length >> Vcb->sector_shift) { Status = partial_stripe_read(Vcb, c, ps, startoff, parity2, last1, (ULONG)((ps_length >> Vcb->sector_shift) - last1)); if (!NT_SUCCESS(Status)) { ERR("partial_stripe_read returned %08lx\n", Status); return Status; } } // set unallocated data to 0 le = c->space.Flink; while (le != &c->space) { space* s = CONTAINING_RECORD(le, space, list_entry); if (s->address + s->size > ps->address && s->address < ps->address + ps_length) { uint64_t start = max(ps->address, s->address); uint64_t end = min(ps->address + ps_length, s->address + s->size); RtlZeroMemory(ps->data + start - ps->address, (ULONG)(end - start)); } else if (s->address >= ps->address + ps_length) break; le = le->Flink; } le = c->deleting.Flink; while (le != &c->deleting) { space* s = CONTAINING_RECORD(le, space, list_entry); if (s->address + s->size > ps->address && s->address < ps->address + ps_length) { uint64_t start = max(ps->address, s->address); uint64_t end = min(ps->address + ps_length, s->address + s->size); RtlZeroMemory(ps->data + start - ps->address, (ULONG)(end - start)); } else if (s->address >= ps->address + ps_length) break; le = le->Flink; } stripe = (parity2 + 1) % c->chunk_item->num_stripes; data = ps->data; for (k = 0; k < num_data_stripes; k++) { if (c->devices[stripe]->devobj) { Status = write_data_phys(c->devices[stripe]->devobj, c->devices[stripe]->fileobj, cis[stripe].offset + startoff, data, stripe_length); if (!NT_SUCCESS(Status)) { ERR("write_data_phys returned %08lx\n", Status); return Status; } } data += stripe_length; stripe = (stripe + 1) % c->chunk_item->num_stripes; } // write parity if (c->chunk_item->type & BLOCK_FLAG_RAID5) { if (c->devices[parity2]->devobj) { uint16_t i; for (i = 1; i < c->chunk_item->num_stripes - 1; i++) { do_xor(ps->data, ps->data + (i * stripe_length), stripe_length); } Status = write_data_phys(c->devices[parity2]->devobj, c->devices[parity2]->fileobj, cis[parity2].offset + startoff, ps->data, stripe_length); if (!NT_SUCCESS(Status)) { ERR("write_data_phys returned %08lx\n", Status); return Status; } } } else { uint16_t parity1 = (parity2 + c->chunk_item->num_stripes - 1) % c->chunk_item->num_stripes; if (c->devices[parity1]->devobj || c->devices[parity2]->devobj) { uint8_t* scratch; uint16_t i; scratch = ExAllocatePoolWithTag(NonPagedPool, stripe_length * 2, ALLOC_TAG); if (!scratch) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } i = c->chunk_item->num_stripes - 3; while (true) { if (i == c->chunk_item->num_stripes - 3) { RtlCopyMemory(scratch, ps->data + (i * stripe_length), stripe_length); RtlCopyMemory(scratch + stripe_length, ps->data + (i * stripe_length), stripe_length); } else { do_xor(scratch, ps->data + (i * stripe_length), stripe_length); galois_double(scratch + stripe_length, stripe_length); do_xor(scratch + stripe_length, ps->data + (i * stripe_length), stripe_length); } if (i == 0) break; i--; } if (c->devices[parity1]->devobj) { Status = write_data_phys(c->devices[parity1]->devobj, c->devices[parity1]->fileobj, cis[parity1].offset + startoff, scratch, stripe_length); if (!NT_SUCCESS(Status)) { ERR("write_data_phys returned %08lx\n", Status); ExFreePool(scratch); return Status; } } if (c->devices[parity2]->devobj) { Status = write_data_phys(c->devices[parity2]->devobj, c->devices[parity2]->fileobj, cis[parity2].offset + startoff, scratch + stripe_length, stripe_length); if (!NT_SUCCESS(Status)) { ERR("write_data_phys returned %08lx\n", Status); ExFreePool(scratch); return Status; } } ExFreePool(scratch); } } return STATUS_SUCCESS; } static NTSTATUS update_chunks(device_extension* Vcb, LIST_ENTRY* batchlist, PIRP Irp, LIST_ENTRY* rollback) { LIST_ENTRY *le, *le2; NTSTATUS Status; uint64_t used_minus_cache; ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true); // FIXME - do tree chunks before data chunks le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry); le2 = le->Flink; if (c->changed) { acquire_chunk_lock(c, Vcb); // flush partial stripes if (!Vcb->readonly && (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6)) { ExAcquireResourceExclusiveLite(&c->partial_stripes_lock, true); while (!IsListEmpty(&c->partial_stripes)) { partial_stripe* ps = CONTAINING_RECORD(RemoveHeadList(&c->partial_stripes), partial_stripe, list_entry); Status = flush_partial_stripe(Vcb, c, ps); if (ps->bmparr) ExFreePool(ps->bmparr); ExFreePool(ps); if (!NT_SUCCESS(Status)) { ERR("flush_partial_stripe returned %08lx\n", Status); ExReleaseResourceLite(&c->partial_stripes_lock); release_chunk_lock(c, Vcb); ExReleaseResourceLite(&Vcb->chunk_lock); return Status; } } ExReleaseResourceLite(&c->partial_stripes_lock); } if (c->list_entry_balance.Flink) { release_chunk_lock(c, Vcb); le = le2; continue; } if (c->space_changed || c->created) { bool created = c->created; used_minus_cache = c->used; // subtract self-hosted cache if (used_minus_cache > 0 && c->chunk_item->type & BLOCK_FLAG_DATA && c->cache && c->cache->inode_item.st_size == c->used) { LIST_ENTRY* le3; le3 = c->cache->extents.Flink; while (le3 != &c->cache->extents) { extent* ext = CONTAINING_RECORD(le3, extent, list_entry); EXTENT_DATA* ed = &ext->extent_data; if (!ext->ignore) { if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; if (ed2->size != 0 && ed2->address >= c->offset && ed2->address + ed2->size <= c->offset + c->chunk_item->size) used_minus_cache -= ed2->size; } } le3 = le3->Flink; } } if (used_minus_cache == 0) { Status = drop_chunk(Vcb, c, batchlist, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("drop_chunk returned %08lx\n", Status); release_chunk_lock(c, Vcb); ExReleaseResourceLite(&Vcb->chunk_lock); return Status; } // c is now freed, so avoid releasing non-existent lock le = le2; continue; } else if (c->created) { Status = create_chunk(Vcb, c, Irp); if (!NT_SUCCESS(Status)) { ERR("create_chunk returned %08lx\n", Status); release_chunk_lock(c, Vcb); ExReleaseResourceLite(&Vcb->chunk_lock); return Status; } } if (used_minus_cache > 0 || created) release_chunk_lock(c, Vcb); } else release_chunk_lock(c, Vcb); } le = le2; } ExReleaseResourceLite(&Vcb->chunk_lock); return STATUS_SUCCESS; } static NTSTATUS delete_root_ref(device_extension* Vcb, uint64_t subvolid, uint64_t parsubvolid, uint64_t parinode, PANSI_STRING utf8, PIRP Irp) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; searchkey.obj_id = parsubvolid; searchkey.obj_type = TYPE_ROOT_REF; searchkey.offset = subvolid; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (!keycmp(searchkey, tp.item->key)) { if (tp.item->size < sizeof(ROOT_REF)) { 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)); return STATUS_INTERNAL_ERROR; } else { ROOT_REF* rr; ULONG len; rr = (ROOT_REF*)tp.item->data; len = tp.item->size; do { uint16_t itemlen; if (len < sizeof(ROOT_REF) || len < offsetof(ROOT_REF, name[0]) + rr->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); break; } itemlen = (uint16_t)offsetof(ROOT_REF, name[0]) + rr->n; if (rr->dir == parinode && rr->n == utf8->Length && RtlCompareMemory(rr->name, utf8->Buffer, rr->n) == rr->n) { uint16_t newlen = tp.item->size - itemlen; Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } if (newlen == 0) { TRACE("deleting (%I64x,%x,%I64x)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); } else { uint8_t *newrr = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *rroff; if (!newrr) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } TRACE("modifying (%I64x,%x,%I64x)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); if ((uint8_t*)rr > tp.item->data) { RtlCopyMemory(newrr, tp.item->data, (uint8_t*)rr - tp.item->data); rroff = newrr + ((uint8_t*)rr - tp.item->data); } else { rroff = newrr; } if ((uint8_t*)&rr->name[rr->n] < tp.item->data + tp.item->size) RtlCopyMemory(rroff, &rr->name[rr->n], tp.item->size - ((uint8_t*)&rr->name[rr->n] - tp.item->data)); 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(newrr); return Status; } } break; } if (len > itemlen) { len -= itemlen; rr = (ROOT_REF*)&rr->name[rr->n]; } else break; } while (len > 0); } } else { WARN("could not find ROOT_REF entry for subvol %I64x in %I64x\n", searchkey.offset, searchkey.obj_id); return STATUS_NOT_FOUND; } return STATUS_SUCCESS; } #ifdef _MSC_VER #pragma warning(push) #pragma warning(suppress: 28194) #endif static 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) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; searchkey.obj_id = parsubvolid; searchkey.obj_type = TYPE_ROOT_REF; searchkey.offset = subvolid; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (!keycmp(searchkey, tp.item->key)) { uint16_t rrsize = tp.item->size + (uint16_t)offsetof(ROOT_REF, name[0]) + rr->n; uint8_t* rr2; rr2 = ExAllocatePoolWithTag(PagedPool, rrsize, ALLOC_TAG); if (!rr2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } if (tp.item->size > 0) RtlCopyMemory(rr2, tp.item->data, tp.item->size); RtlCopyMemory(rr2 + tp.item->size, rr, offsetof(ROOT_REF, name[0]) + rr->n); ExFreePool(rr); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); ExFreePool(rr2); return Status; } Status = insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, rr2, rrsize, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(rr2); return Status; } } else { 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(rr); return Status; } } return STATUS_SUCCESS; } #ifdef _MSC_VER #pragma warning(pop) #endif static NTSTATUS update_root_backref(device_extension* Vcb, uint64_t subvolid, uint64_t parsubvolid, PIRP Irp) { KEY searchkey; traverse_ptr tp; uint8_t* data; uint16_t datalen; NTSTATUS Status; searchkey.obj_id = parsubvolid; searchkey.obj_type = TYPE_ROOT_REF; searchkey.offset = subvolid; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (!keycmp(tp.item->key, searchkey) && tp.item->size > 0) { datalen = tp.item->size; data = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG); if (!data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(data, tp.item->data, datalen); } else { datalen = 0; data = NULL; } searchkey.obj_id = subvolid; searchkey.obj_type = TYPE_ROOT_BACKREF; searchkey.offset = parsubvolid; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); if (datalen > 0) ExFreePool(data); return Status; } if (!keycmp(tp.item->key, searchkey)) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); if (datalen > 0) ExFreePool(data); return Status; } } if (datalen > 0) { Status = insert_tree_item(Vcb, Vcb->root_root, subvolid, TYPE_ROOT_BACKREF, parsubvolid, data, datalen, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(data); return Status; } } return STATUS_SUCCESS; } static NTSTATUS add_root_item_to_cache(device_extension* Vcb, uint64_t root, PIRP Irp) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; searchkey.obj_id = root; searchkey.obj_type = TYPE_ROOT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { ERR("could not find ROOT_ITEM for tree %I64x\n", searchkey.obj_id); return STATUS_INTERNAL_ERROR; } if (tp.item->size < sizeof(ROOT_ITEM)) { // if not full length, create new entry with new bits zeroed ROOT_ITEM* ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG); if (!ri) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } if (tp.item->size > 0) RtlCopyMemory(ri, tp.item->data, tp.item->size); RtlZeroMemory(((uint8_t*)ri) + tp.item->size, sizeof(ROOT_ITEM) - tp.item->size); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); ExFreePool(ri); return Status; } Status = insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, tp.item->key.offset, ri, sizeof(ROOT_ITEM), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(ri); return Status; } } else { tp.tree->write = true; } return STATUS_SUCCESS; } static NTSTATUS flush_fileref(file_ref* fileref, LIST_ENTRY* batchlist, PIRP Irp) { NTSTATUS Status; // if fileref created and then immediately deleted, do nothing if (fileref->created && fileref->deleted) { fileref->dirty = false; return STATUS_SUCCESS; } if (fileref->fcb->ads) { fileref->dirty = false; return STATUS_SUCCESS; } if (fileref->created) { uint16_t disize; DIR_ITEM *di, *di2; uint32_t crc32; crc32 = calc_crc32c(0xfffffffe, (uint8_t*)fileref->dc->utf8.Buffer, fileref->dc->utf8.Length); disize = (uint16_t)(offsetof(DIR_ITEM, name[0]) + fileref->dc->utf8.Length); di = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG); if (!di) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } if (fileref->parent->fcb->subvol == fileref->fcb->subvol) { di->key.obj_id = fileref->fcb->inode; di->key.obj_type = TYPE_INODE_ITEM; di->key.offset = 0; } else { // subvolume di->key.obj_id = fileref->fcb->subvol->id; di->key.obj_type = TYPE_ROOT_ITEM; di->key.offset = 0xffffffffffffffff; } di->transid = fileref->fcb->Vcb->superblock.generation; di->m = 0; di->n = (uint16_t)fileref->dc->utf8.Length; di->type = fileref->fcb->type; RtlCopyMemory(di->name, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length); di2 = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG); if (!di2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(di2, di, disize); Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_INDEX, fileref->dc->index, di, disize, Batch_Insert); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); return Status; } Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_ITEM, crc32, di2, disize, Batch_DirItem); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); return Status; } if (fileref->parent->fcb->subvol == fileref->fcb->subvol) { INODE_REF* ir; ir = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + fileref->dc->utf8.Length, ALLOC_TAG); if (!ir) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ir->index = fileref->dc->index; ir->n = fileref->dc->utf8.Length; RtlCopyMemory(ir->name, fileref->dc->utf8.Buffer, ir->n); Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->fcb->subvol, fileref->fcb->inode, TYPE_INODE_REF, fileref->parent->fcb->inode, ir, sizeof(INODE_REF) - 1 + ir->n, Batch_InodeRef); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); return Status; } } else if (fileref->fcb != fileref->fcb->Vcb->dummy_fcb) { ULONG rrlen; ROOT_REF* rr; rrlen = sizeof(ROOT_REF) - 1 + fileref->dc->utf8.Length; rr = ExAllocatePoolWithTag(PagedPool, rrlen, ALLOC_TAG); if (!rr) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } rr->dir = fileref->parent->fcb->inode; rr->index = fileref->dc->index; rr->n = fileref->dc->utf8.Length; RtlCopyMemory(rr->name, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length); Status = add_root_ref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, rr, Irp); if (!NT_SUCCESS(Status)) { ERR("add_root_ref returned %08lx\n", Status); return Status; } Status = update_root_backref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, Irp); if (!NT_SUCCESS(Status)) { ERR("update_root_backref returned %08lx\n", Status); return Status; } } fileref->created = false; } else if (fileref->deleted) { uint32_t crc32; ANSI_STRING* name; DIR_ITEM* di; name = &fileref->oldutf8; crc32 = calc_crc32c(0xfffffffe, (uint8_t*)name->Buffer, name->Length); di = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + name->Length, ALLOC_TAG); if (!di) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } di->m = 0; di->n = name->Length; RtlCopyMemory(di->name, name->Buffer, name->Length); // delete DIR_ITEM (0x54) Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_ITEM, crc32, di, sizeof(DIR_ITEM) - 1 + name->Length, Batch_DeleteDirItem); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); return Status; } if (fileref->parent->fcb->subvol == fileref->fcb->subvol) { INODE_REF* ir; // delete INODE_REF (0xc) ir = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + name->Length, ALLOC_TAG); if (!ir) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ir->index = fileref->oldindex; ir->n = name->Length; RtlCopyMemory(ir->name, name->Buffer, name->Length); Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->fcb->inode, TYPE_INODE_REF, fileref->parent->fcb->inode, ir, sizeof(INODE_REF) - 1 + name->Length, Batch_DeleteInodeRef); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); return Status; } } else if (fileref->fcb != fileref->fcb->Vcb->dummy_fcb) { // subvolume Status = delete_root_ref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, fileref->parent->fcb->inode, name, Irp); if (!NT_SUCCESS(Status)) { ERR("delete_root_ref returned %08lx\n", Status); return Status; } Status = update_root_backref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, Irp); if (!NT_SUCCESS(Status)) { ERR("update_root_backref returned %08lx\n", Status); return Status; } } // delete DIR_INDEX (0x60) Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_INDEX, fileref->oldindex, NULL, 0, Batch_Delete); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); return Status; } if (fileref->oldutf8.Buffer) { ExFreePool(fileref->oldutf8.Buffer); fileref->oldutf8.Buffer = NULL; } } else { // rename or change type PANSI_STRING oldutf8 = fileref->oldutf8.Buffer ? &fileref->oldutf8 : &fileref->dc->utf8; uint32_t crc32, oldcrc32; uint16_t disize; DIR_ITEM *olddi, *di, *di2; crc32 = calc_crc32c(0xfffffffe, (uint8_t*)fileref->dc->utf8.Buffer, fileref->dc->utf8.Length); if (!fileref->oldutf8.Buffer) oldcrc32 = crc32; else oldcrc32 = calc_crc32c(0xfffffffe, (uint8_t*)fileref->oldutf8.Buffer, fileref->oldutf8.Length); olddi = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + oldutf8->Length, ALLOC_TAG); if (!olddi) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } olddi->m = 0; olddi->n = (uint16_t)oldutf8->Length; RtlCopyMemory(olddi->name, oldutf8->Buffer, oldutf8->Length); // delete DIR_ITEM (0x54) Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_ITEM, oldcrc32, olddi, sizeof(DIR_ITEM) - 1 + oldutf8->Length, Batch_DeleteDirItem); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); ExFreePool(olddi); return Status; } // add DIR_ITEM (0x54) disize = (uint16_t)(offsetof(DIR_ITEM, name[0]) + fileref->dc->utf8.Length); di = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG); if (!di) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } di2 = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG); if (!di2) { ERR("out of memory\n"); ExFreePool(di); return STATUS_INSUFFICIENT_RESOURCES; } if (fileref->dc) di->key = fileref->dc->key; else if (fileref->parent->fcb->subvol == fileref->fcb->subvol) { di->key.obj_id = fileref->fcb->inode; di->key.obj_type = TYPE_INODE_ITEM; di->key.offset = 0; } else { // subvolume di->key.obj_id = fileref->fcb->subvol->id; di->key.obj_type = TYPE_ROOT_ITEM; di->key.offset = 0xffffffffffffffff; } di->transid = fileref->fcb->Vcb->superblock.generation; di->m = 0; di->n = (uint16_t)fileref->dc->utf8.Length; di->type = fileref->fcb->type; RtlCopyMemory(di->name, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length); RtlCopyMemory(di2, di, disize); Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_ITEM, crc32, di, disize, Batch_DirItem); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); ExFreePool(di2); ExFreePool(di); return Status; } if (fileref->parent->fcb->subvol == fileref->fcb->subvol) { INODE_REF *ir, *ir2; // delete INODE_REF (0xc) ir = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + oldutf8->Length, ALLOC_TAG); if (!ir) { ERR("out of memory\n"); ExFreePool(di2); return STATUS_INSUFFICIENT_RESOURCES; } ir->index = fileref->dc->index; ir->n = oldutf8->Length; RtlCopyMemory(ir->name, oldutf8->Buffer, ir->n); Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->fcb->subvol, fileref->fcb->inode, TYPE_INODE_REF, fileref->parent->fcb->inode, ir, sizeof(INODE_REF) - 1 + ir->n, Batch_DeleteInodeRef); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); ExFreePool(ir); ExFreePool(di2); return Status; } // add INODE_REF (0xc) ir2 = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + fileref->dc->utf8.Length, ALLOC_TAG); if (!ir2) { ERR("out of memory\n"); ExFreePool(di2); return STATUS_INSUFFICIENT_RESOURCES; } ir2->index = fileref->dc->index; ir2->n = fileref->dc->utf8.Length; RtlCopyMemory(ir2->name, fileref->dc->utf8.Buffer, ir2->n); Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->fcb->subvol, fileref->fcb->inode, TYPE_INODE_REF, fileref->parent->fcb->inode, ir2, sizeof(INODE_REF) - 1 + ir2->n, Batch_InodeRef); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); ExFreePool(ir2); ExFreePool(di2); return Status; } } else if (fileref->fcb != fileref->fcb->Vcb->dummy_fcb) { // subvolume ULONG rrlen; ROOT_REF* rr; Status = delete_root_ref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, fileref->parent->fcb->inode, oldutf8, Irp); if (!NT_SUCCESS(Status)) { ERR("delete_root_ref returned %08lx\n", Status); ExFreePool(di2); return Status; } rrlen = sizeof(ROOT_REF) - 1 + fileref->dc->utf8.Length; rr = ExAllocatePoolWithTag(PagedPool, rrlen, ALLOC_TAG); if (!rr) { ERR("out of memory\n"); ExFreePool(di2); return STATUS_INSUFFICIENT_RESOURCES; } rr->dir = fileref->parent->fcb->inode; rr->index = fileref->dc->index; rr->n = fileref->dc->utf8.Length; RtlCopyMemory(rr->name, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length); Status = add_root_ref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, rr, Irp); if (!NT_SUCCESS(Status)) { ERR("add_root_ref returned %08lx\n", Status); ExFreePool(di2); return Status; } Status = update_root_backref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, Irp); if (!NT_SUCCESS(Status)) { ERR("update_root_backref returned %08lx\n", Status); ExFreePool(di2); return Status; } } // delete DIR_INDEX (0x60) Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_INDEX, fileref->dc->index, NULL, 0, Batch_Delete); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); ExFreePool(di2); return Status; } // add DIR_INDEX (0x60) Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_INDEX, fileref->dc->index, di2, disize, Batch_Insert); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item_batch returned %08lx\n", Status); ExFreePool(di2); return Status; } if (fileref->oldutf8.Buffer) { ExFreePool(fileref->oldutf8.Buffer); fileref->oldutf8.Buffer = NULL; } } fileref->dirty = false; return STATUS_SUCCESS; } static void flush_disk_caches(device_extension* Vcb) { LIST_ENTRY* le; ioctl_context context; ULONG num; context.left = 0; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (dev->devobj && !dev->readonly && dev->can_flush) context.left++; le = le->Flink; } if (context.left == 0) return; num = 0; KeInitializeEvent(&context.Event, NotificationEvent, false); context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(ioctl_context_stripe) * context.left, ALLOC_TAG); if (!context.stripes) { ERR("out of memory\n"); return; } RtlZeroMemory(context.stripes, sizeof(ioctl_context_stripe) * context.left); le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (dev->devobj && !dev->readonly && dev->can_flush) { PIO_STACK_LOCATION IrpSp; ioctl_context_stripe* stripe = &context.stripes[num]; RtlZeroMemory(&stripe->apte, sizeof(ATA_PASS_THROUGH_EX)); stripe->apte.Length = sizeof(ATA_PASS_THROUGH_EX); stripe->apte.TimeOutValue = 5; stripe->apte.CurrentTaskFile[6] = IDE_COMMAND_FLUSH_CACHE; stripe->Irp = IoAllocateIrp(dev->devobj->StackSize, false); if (!stripe->Irp) { ERR("IoAllocateIrp failed\n"); goto nextdev; } IrpSp = IoGetNextIrpStackLocation(stripe->Irp); IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL; IrpSp->FileObject = dev->fileobj; IrpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_ATA_PASS_THROUGH; IrpSp->Parameters.DeviceIoControl.InputBufferLength = sizeof(ATA_PASS_THROUGH_EX); IrpSp->Parameters.DeviceIoControl.OutputBufferLength = sizeof(ATA_PASS_THROUGH_EX); stripe->Irp->AssociatedIrp.SystemBuffer = &stripe->apte; stripe->Irp->Flags |= IRP_BUFFERED_IO | IRP_INPUT_OPERATION; stripe->Irp->UserBuffer = &stripe->apte; stripe->Irp->UserIosb = &stripe->iosb; IoSetCompletionRoutine(stripe->Irp, ioctl_completion, &context, true, true, true); IoCallDriver(dev->devobj, stripe->Irp); nextdev: num++; } le = le->Flink; } KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL); for (unsigned int i = 0; i < num; i++) { if (context.stripes[i].Irp) IoFreeIrp(context.stripes[i].Irp); } ExFreePool(context.stripes); } static NTSTATUS flush_changed_dev_stats(device_extension* Vcb, device* dev, PIRP Irp) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; uint16_t statslen; uint64_t* stats; searchkey.obj_id = 0; searchkey.obj_type = TYPE_DEV_STATS; searchkey.offset = dev->devitem.dev_id; Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (!keycmp(tp.item->key, searchkey)) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } statslen = sizeof(uint64_t) * 5; stats = ExAllocatePoolWithTag(PagedPool, statslen, ALLOC_TAG); if (!stats) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(stats, dev->stats, statslen); Status = insert_tree_item(Vcb, Vcb->dev_root, 0, TYPE_DEV_STATS, dev->devitem.dev_id, stats, statslen, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(stats); return Status; } return STATUS_SUCCESS; } static NTSTATUS flush_subvol(device_extension* Vcb, root* r, PIRP Irp) { NTSTATUS Status; if (r != Vcb->root_root && r != Vcb->chunk_root) { KEY searchkey; traverse_ptr tp; ROOT_ITEM* ri; searchkey.obj_id = r->id; searchkey.obj_type = TYPE_ROOT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { ERR("could not find ROOT_ITEM for tree %I64x\n", searchkey.obj_id); return STATUS_INTERNAL_ERROR; } ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG); if (!ri) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(ri, &r->root_item, sizeof(ROOT_ITEM)); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } } if (r->received) { KEY searchkey; traverse_ptr tp; if (!Vcb->uuid_root) { root* uuid_root; TRACE("uuid root doesn't exist, creating it\n"); Status = create_root(Vcb, BTRFS_ROOT_UUID, &uuid_root, false, 0, Irp); if (!NT_SUCCESS(Status)) { ERR("create_root returned %08lx\n", Status); return Status; } Vcb->uuid_root = uuid_root; } RtlCopyMemory(&searchkey.obj_id, &r->root_item.received_uuid, sizeof(uint64_t)); searchkey.obj_type = TYPE_SUBVOL_REC_UUID; RtlCopyMemory(&searchkey.offset, &r->root_item.received_uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t)); Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (!keycmp(tp.item->key, searchkey)) { if (tp.item->size + sizeof(uint64_t) <= Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node)) { uint64_t* ids; ids = ExAllocatePoolWithTag(PagedPool, tp.item->size + sizeof(uint64_t), ALLOC_TAG); if (!ids) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(ids, tp.item->data, tp.item->size); RtlCopyMemory((uint8_t*)ids + tp.item->size, &r->id, sizeof(uint64_t)); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); ExFreePool(ids); return Status; } 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); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(ids); return Status; } } } else { uint64_t* root_num; root_num = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t), ALLOC_TAG); if (!root_num) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } *root_num = r->id; Status = insert_tree_item(Vcb, Vcb->uuid_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, root_num, sizeof(uint64_t), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(root_num); return Status; } } r->received = false; } r->dirty = false; return STATUS_SUCCESS; } static NTSTATUS test_not_full(device_extension* Vcb) { uint64_t reserve, could_alloc, free_space; LIST_ENTRY* le; // This function ensures we drop into readonly mode if we're about to leave very little // space for metadata - this is similar to the "global reserve" of the Linux driver. // Otherwise we might completely fill our space, at which point due to COW we can't // delete anything in order to fix this. reserve = Vcb->extent_root->root_item.bytes_used; reserve += Vcb->root_root->root_item.bytes_used; if (Vcb->checksum_root) reserve += Vcb->checksum_root->root_item.bytes_used; reserve = max(reserve, 0x1000000); // 16 M reserve = min(reserve, 0x20000000); // 512 M // Find out how much space would be available for new metadata chunks could_alloc = 0; if (Vcb->metadata_flags & BLOCK_FLAG_RAID5) { uint64_t s1 = 0, s2 = 0, s3 = 0; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (!dev->readonly) { uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used; if (space >= s1) { s3 = s2; s2 = s1; s1 = space; } else if (space >= s2) { s3 = s2; s2 = space; } else if (space >= s3) s3 = space; } le = le->Flink; } could_alloc = s3 * 2; } else if (Vcb->metadata_flags & (BLOCK_FLAG_RAID10 | BLOCK_FLAG_RAID6)) { uint64_t s1 = 0, s2 = 0, s3 = 0, s4 = 0; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (!dev->readonly) { uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used; if (space >= s1) { s4 = s3; s3 = s2; s2 = s1; s1 = space; } else if (space >= s2) { s4 = s3; s3 = s2; s2 = space; } else if (space >= s3) { s4 = s3; s3 = space; } else if (space >= s4) s4 = space; } le = le->Flink; } could_alloc = s4 * 2; } else if (Vcb->metadata_flags & (BLOCK_FLAG_RAID0 | BLOCK_FLAG_RAID1)) { uint64_t s1 = 0, s2 = 0; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (!dev->readonly) { uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used; if (space >= s1) { s2 = s1; s1 = space; } else if (space >= s2) s2 = space; } le = le->Flink; } if (Vcb->metadata_flags & BLOCK_FLAG_RAID1) could_alloc = s2; else // RAID0 could_alloc = s2 * 2; } else if (Vcb->metadata_flags & BLOCK_FLAG_DUPLICATE) { le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (!dev->readonly) { uint64_t space = (dev->devitem.num_bytes - dev->devitem.bytes_used) / 2; could_alloc = max(could_alloc, space); } le = le->Flink; } } else if (Vcb->metadata_flags & BLOCK_FLAG_RAID1C3) { uint64_t s1 = 0, s2 = 0, s3 = 0; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (!dev->readonly) { uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used; if (space >= s1) { s3 = s2; s2 = s1; s1 = space; } else if (space >= s2) { s3 = s2; s2 = space; } else if (space >= s3) s3 = space; } le = le->Flink; } could_alloc = s3; } else if (Vcb->metadata_flags & BLOCK_FLAG_RAID1C4) { uint64_t s1 = 0, s2 = 0, s3 = 0, s4 = 0; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (!dev->readonly) { uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used; if (space >= s1) { s4 = s3; s3 = s2; s2 = s1; s1 = space; } else if (space >= s2) { s4 = s3; s3 = s2; s2 = space; } else if (space >= s3) { s4 = s3; s3 = space; } else if (space >= s4) s4 = space; } le = le->Flink; } could_alloc = s4; } else { // SINGLE le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (!dev->readonly) { uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used; could_alloc = max(could_alloc, space); } le = le->Flink; } } if (could_alloc >= reserve) return STATUS_SUCCESS; free_space = 0; le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry); if (!c->reloc && !c->readonly && c->chunk_item->type & BLOCK_FLAG_METADATA) { free_space += c->chunk_item->size - c->used; if (free_space + could_alloc >= reserve) return STATUS_SUCCESS; } le = le->Flink; } return STATUS_DISK_FULL; } static NTSTATUS check_for_orphans_root(device_extension* Vcb, root* r, PIRP Irp) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; LIST_ENTRY rollback; TRACE("(%p, %p)\n", Vcb, r); InitializeListHead(&rollback); searchkey.obj_id = BTRFS_ORPHAN_INODE_OBJID; searchkey.obj_type = TYPE_ORPHAN_INODE; searchkey.offset = 0; Status = find_item(Vcb, r, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } do { traverse_ptr next_tp; 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)) break; if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { fcb* fcb; TRACE("removing orphaned inode %I64x\n", tp.item->key.offset); Status = open_fcb(Vcb, r, tp.item->key.offset, 0, NULL, false, NULL, &fcb, PagedPool, Irp); if (!NT_SUCCESS(Status)) ERR("open_fcb returned %08lx\n", Status); else { if (fcb->inode_item.st_nlink == 0) { if (fcb->type != BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0) { Status = excise_extents(Vcb, fcb, 0, sector_align(fcb->inode_item.st_size, Vcb->superblock.sector_size), Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); goto end; } } fcb->deleted = true; mark_fcb_dirty(fcb); } free_fcb(fcb); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); goto end; } } } if (find_next_item(Vcb, &tp, &next_tp, false, Irp)) tp = next_tp; else break; } while (true); Status = STATUS_SUCCESS; clear_rollback(&rollback); end: do_rollback(Vcb, &rollback); return Status; } static NTSTATUS check_for_orphans(device_extension* Vcb, PIRP Irp) { NTSTATUS Status; LIST_ENTRY* le; if (IsListEmpty(&Vcb->dirty_filerefs)) return STATUS_SUCCESS; le = Vcb->dirty_filerefs.Flink; while (le != &Vcb->dirty_filerefs) { file_ref* fr = CONTAINING_RECORD(le, file_ref, list_entry_dirty); if (!fr->fcb->subvol->checked_for_orphans) { Status = check_for_orphans_root(Vcb, fr->fcb->subvol, Irp); if (!NT_SUCCESS(Status)) { ERR("check_for_orphans_root returned %08lx\n", Status); return Status; } fr->fcb->subvol->checked_for_orphans = true; } le = le->Flink; } return STATUS_SUCCESS; } static NTSTATUS do_write2(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; LIST_ENTRY *le, batchlist; bool cache_changed = false; volume_device_extension* vde; bool no_cache = false; #ifdef DEBUG_FLUSH_TIMES uint64_t filerefs = 0, fcbs = 0; LARGE_INTEGER freq, time1, time2; #endif #ifdef DEBUG_WRITE_LOOPS UINT loops = 0; #endif TRACE("(%p)\n", Vcb); InitializeListHead(&batchlist); #ifdef DEBUG_FLUSH_TIMES time1 = KeQueryPerformanceCounter(&freq); #endif Status = check_for_orphans(Vcb, Irp); if (!NT_SUCCESS(Status)) { ERR("check_for_orphans returned %08lx\n", Status); return Status; } ExAcquireResourceExclusiveLite(&Vcb->dirty_filerefs_lock, true); while (!IsListEmpty(&Vcb->dirty_filerefs)) { file_ref* fr = CONTAINING_RECORD(RemoveHeadList(&Vcb->dirty_filerefs), file_ref, list_entry_dirty); flush_fileref(fr, &batchlist, Irp); free_fileref(fr); #ifdef DEBUG_FLUSH_TIMES filerefs++; #endif } ExReleaseResourceLite(&Vcb->dirty_filerefs_lock); Status = commit_batch_list(Vcb, &batchlist, Irp); if (!NT_SUCCESS(Status)) { ERR("commit_batch_list returned %08lx\n", Status); return Status; } #ifdef DEBUG_FLUSH_TIMES time2 = KeQueryPerformanceCounter(NULL); ERR("flushed %I64u filerefs in %I64u (freq = %I64u)\n", filerefs, time2.QuadPart - time1.QuadPart, freq.QuadPart); time1 = KeQueryPerformanceCounter(&freq); #endif // We process deleted streams first, so we don't run over our xattr // limit unless we absolutely have to. // We also process deleted normal files, to avoid any problems // caused by inode collisions. ExAcquireResourceExclusiveLite(&Vcb->dirty_fcbs_lock, true); le = Vcb->dirty_fcbs.Flink; while (le != &Vcb->dirty_fcbs) { fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry_dirty); LIST_ENTRY* le2 = le->Flink; if (fcb->deleted) { ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); Status = flush_fcb(fcb, false, &batchlist, Irp); ExReleaseResourceLite(fcb->Header.Resource); free_fcb(fcb); if (!NT_SUCCESS(Status)) { ERR("flush_fcb returned %08lx\n", Status); clear_batch_list(Vcb, &batchlist); ExReleaseResourceLite(&Vcb->dirty_fcbs_lock); return Status; } #ifdef DEBUG_FLUSH_TIMES fcbs++; #endif } le = le2; } Status = commit_batch_list(Vcb, &batchlist, Irp); if (!NT_SUCCESS(Status)) { ERR("commit_batch_list returned %08lx\n", Status); ExReleaseResourceLite(&Vcb->dirty_fcbs_lock); return Status; } le = Vcb->dirty_fcbs.Flink; while (le != &Vcb->dirty_fcbs) { fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry_dirty); LIST_ENTRY* le2 = le->Flink; if (fcb->subvol != Vcb->root_root) { ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); Status = flush_fcb(fcb, false, &batchlist, Irp); ExReleaseResourceLite(fcb->Header.Resource); free_fcb(fcb); if (!NT_SUCCESS(Status)) { ERR("flush_fcb returned %08lx\n", Status); ExReleaseResourceLite(&Vcb->dirty_fcbs_lock); return Status; } #ifdef DEBUG_FLUSH_TIMES fcbs++; #endif } le = le2; } ExReleaseResourceLite(&Vcb->dirty_fcbs_lock); Status = commit_batch_list(Vcb, &batchlist, Irp); if (!NT_SUCCESS(Status)) { ERR("commit_batch_list returned %08lx\n", Status); return Status; } #ifdef DEBUG_FLUSH_TIMES time2 = KeQueryPerformanceCounter(NULL); ERR("flushed %I64u fcbs in %I64u (freq = %I64u)\n", filerefs, time2.QuadPart - time1.QuadPart, freq.QuadPart); #endif // no need to get dirty_subvols_lock here, as we have tree_lock exclusively while (!IsListEmpty(&Vcb->dirty_subvols)) { root* r = CONTAINING_RECORD(RemoveHeadList(&Vcb->dirty_subvols), root, list_entry_dirty); Status = flush_subvol(Vcb, r, Irp); if (!NT_SUCCESS(Status)) { ERR("flush_subvol returned %08lx\n", Status); return Status; } } if (!IsListEmpty(&Vcb->drop_roots)) { Status = drop_roots(Vcb, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("drop_roots returned %08lx\n", Status); return Status; } } Status = update_chunks(Vcb, &batchlist, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("update_chunks returned %08lx\n", Status); return Status; } Status = commit_batch_list(Vcb, &batchlist, Irp); // If only changing superblock, e.g. changing label, we still need to rewrite // the root tree so the generations match, otherwise you won't be able to mount on Linux. if (!Vcb->root_root->treeholder.tree || !Vcb->root_root->treeholder.tree->write) { KEY searchkey; traverse_ptr tp; searchkey.obj_id = 0; searchkey.obj_type = 0; searchkey.offset = 0; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } Vcb->root_root->treeholder.tree->write = true; } // make sure we always update the extent tree Status = add_root_item_to_cache(Vcb, BTRFS_ROOT_EXTENT, Irp); if (!NT_SUCCESS(Status)) { ERR("add_root_item_to_cache returned %08lx\n", Status); return Status; } if (Vcb->stats_changed) { le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (dev->stats_changed) { Status = flush_changed_dev_stats(Vcb, dev, Irp); if (!NT_SUCCESS(Status)) { ERR("flush_changed_dev_stats returned %08lx\n", Status); return Status; } dev->stats_changed = false; } le = le->Flink; } Vcb->stats_changed = false; } do { Status = add_parents(Vcb, Irp); if (!NT_SUCCESS(Status)) { ERR("add_parents returned %08lx\n", Status); goto end; } Status = allocate_tree_extents(Vcb, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("allocate_tree_extents returned %08lx\n", Status); goto end; } Status = do_splits(Vcb, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("do_splits returned %08lx\n", Status); goto end; } Status = update_chunk_usage(Vcb, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("update_chunk_usage returned %08lx\n", Status); goto end; } if (!(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE)) { if (!no_cache) { Status = allocate_cache(Vcb, &cache_changed, Irp, rollback); if (!NT_SUCCESS(Status)) { WARN("allocate_cache returned %08lx\n", Status); no_cache = true; cache_changed = false; } } } else { Status = update_chunk_caches_tree(Vcb, Irp); if (!NT_SUCCESS(Status)) { ERR("update_chunk_caches_tree returned %08lx\n", Status); goto end; } } #ifdef DEBUG_WRITE_LOOPS loops++; if (cache_changed) ERR("cache has changed, looping again\n"); #endif } while (cache_changed || !trees_consistent(Vcb)); #ifdef DEBUG_WRITE_LOOPS ERR("%u loops\n", loops); #endif TRACE("trees consistent\n"); Status = update_root_root(Vcb, no_cache, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("update_root_root returned %08lx\n", Status); goto end; } Status = write_trees(Vcb, Irp); if (!NT_SUCCESS(Status)) { ERR("write_trees returned %08lx\n", Status); goto end; } Status = test_not_full(Vcb); if (!NT_SUCCESS(Status)) { ERR("test_not_full returned %08lx\n", Status); goto end; } #ifdef DEBUG_PARANOID le = Vcb->trees.Flink; while (le != &Vcb->trees) { tree* t = CONTAINING_RECORD(le, tree, list_entry); KEY searchkey; traverse_ptr tp; searchkey.obj_id = t->header.address; searchkey.obj_type = TYPE_METADATA_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); goto end; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { searchkey.obj_id = t->header.address; searchkey.obj_type = TYPE_EXTENT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); goto end; } if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { ERR("error - could not find entry in extent tree for tree at %I64x\n", t->header.address); Status = STATUS_INTERNAL_ERROR; goto end; } } le = le->Flink; } #endif Vcb->superblock.cache_generation = Vcb->superblock.generation; if (!Vcb->options.no_barrier) flush_disk_caches(Vcb); Status = write_superblocks(Vcb, Irp); if (!NT_SUCCESS(Status)) { ERR("write_superblocks returned %08lx\n", Status); goto end; } vde = Vcb->vde; if (vde) { pdo_device_extension* pdode = vde->pdode; ExAcquireResourceSharedLite(&pdode->child_lock, true); le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); vc->generation = Vcb->superblock.generation; le = le->Flink; } ExReleaseResourceLite(&pdode->child_lock); } clean_space_cache(Vcb); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry); c->changed = false; c->space_changed = false; le = le->Flink; } Vcb->superblock.generation++; Status = STATUS_SUCCESS; le = Vcb->trees.Flink; while (le != &Vcb->trees) { tree* t = CONTAINING_RECORD(le, tree, list_entry); t->write = false; le = le->Flink; } Vcb->need_write = false; while (!IsListEmpty(&Vcb->drop_roots)) { root* r = CONTAINING_RECORD(RemoveHeadList(&Vcb->drop_roots), root, list_entry); if (IsListEmpty(&r->fcbs)) { ExDeleteResourceLite(&r->nonpaged->load_tree_lock); ExFreePool(r->nonpaged); ExFreePool(r); } else r->dropped = true; } end: TRACE("do_write returning %08lx\n", Status); return Status; } NTSTATUS do_write(device_extension* Vcb, PIRP Irp) { LIST_ENTRY rollback; NTSTATUS Status; InitializeListHead(&rollback); Status = do_write2(Vcb, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("do_write2 returned %08lx, dropping into readonly mode\n", Status); Vcb->readonly = true; FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_FORCED_CLOSED); do_rollback(Vcb, &rollback); } else clear_rollback(&rollback); return Status; } static void do_flush(device_extension* Vcb) { NTSTATUS Status; ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); if (Vcb->need_write && !Vcb->readonly) Status = do_write(Vcb, NULL); else Status = STATUS_SUCCESS; free_trees(Vcb); if (!NT_SUCCESS(Status)) ERR("do_write returned %08lx\n", Status); ExReleaseResourceLite(&Vcb->tree_lock); } _Function_class_(KSTART_ROUTINE) void __stdcall flush_thread(void* context) { DEVICE_OBJECT* devobj = context; device_extension* Vcb = devobj->DeviceExtension; LARGE_INTEGER due_time; ObReferenceObject(devobj); KeInitializeTimer(&Vcb->flush_thread_timer); due_time.QuadPart = (uint64_t)Vcb->options.flush_interval * -10000000; KeSetTimer(&Vcb->flush_thread_timer, due_time, NULL); while (true) { KeWaitForSingleObject(&Vcb->flush_thread_timer, Executive, KernelMode, false, NULL); if (!(devobj->Vpb->Flags & VPB_MOUNTED) || Vcb->removing) break; if (!Vcb->locked) do_flush(Vcb); KeSetTimer(&Vcb->flush_thread_timer, due_time, NULL); } ObDereferenceObject(devobj); KeCancelTimer(&Vcb->flush_thread_timer); KeSetEvent(&Vcb->flush_thread_finished, 0, false); PsTerminateSystemThread(STATUS_SUCCESS); } ================================================ FILE: src/free-space.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include "crc32c.h" // Number of increments in the size of each cache inode, in sectors. Should // this be a constant number of sectors, a constant 256 KB, or what? #define CACHE_INCREMENTS 64 static NTSTATUS remove_free_space_inode(device_extension* Vcb, uint64_t inode, LIST_ENTRY* batchlist, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; fcb* fcb; Status = open_fcb(Vcb, Vcb->root_root, inode, BTRFS_TYPE_FILE, NULL, false, NULL, &fcb, PagedPool, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fcb returned %08lx\n", Status); return Status; } mark_fcb_dirty(fcb); if (fcb->inode_item.st_size > 0) { Status = excise_extents(fcb->Vcb, fcb, 0, sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size), Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); return Status; } } fcb->deleted = true; Status = flush_fcb(fcb, false, batchlist, Irp); if (!NT_SUCCESS(Status)) { ERR("flush_fcb returned %08lx\n", Status); free_fcb(fcb); return Status; } free_fcb(fcb); return STATUS_SUCCESS; } NTSTATUS clear_free_space_cache(device_extension* Vcb, LIST_ENTRY* batchlist, PIRP Irp) { KEY searchkey; traverse_ptr tp, next_tp; NTSTATUS Status; bool b; LIST_ENTRY rollback; InitializeListHead(&rollback); searchkey.obj_id = FREE_SPACE_CACHE_ID; searchkey.obj_type = 0; searchkey.offset = 0; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } do { 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)) break; if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } if (tp.item->size >= sizeof(FREE_SPACE_ITEM)) { FREE_SPACE_ITEM* fsi = (FREE_SPACE_ITEM*)tp.item->data; if (fsi->key.obj_type != TYPE_INODE_ITEM) WARN("key (%I64x,%x,%I64x) does not point to an INODE_ITEM\n", fsi->key.obj_id, fsi->key.obj_type, fsi->key.offset); else { LIST_ENTRY* le; Status = remove_free_space_inode(Vcb, fsi->key.obj_id, batchlist, Irp, &rollback); if (!NT_SUCCESS(Status)) ERR("remove_free_space_inode for (%I64x,%x,%I64x) returned %08lx\n", fsi->key.obj_id, fsi->key.obj_type, fsi->key.offset, Status); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry); if (c->offset == tp.item->key.offset && c->cache) { reap_fcb(c->cache); c->cache = NULL; } le = le->Flink; } } } else 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)); } b = find_next_item(Vcb, &tp, &next_tp, false, Irp); if (b) tp = next_tp; } while (b); Status = STATUS_SUCCESS; if (NT_SUCCESS(Status)) clear_rollback(&rollback); else do_rollback(Vcb, &rollback); if (Vcb->space_root) { searchkey.obj_id = 0; searchkey.obj_type = 0; searchkey.offset = 0; Status = find_item(Vcb, Vcb->space_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } do { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } b = find_next_item(Vcb, &tp, &next_tp, false, Irp); if (b) tp = next_tp; } while (b); } // regenerate free space tree if (Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE) { LIST_ENTRY* le; ExAcquireResourceSharedLite(&Vcb->chunk_lock, true); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry); if (!c->cache_loaded) { acquire_chunk_lock(c, Vcb); Status = load_cache_chunk(Vcb, c, NULL); if (!NT_SUCCESS(Status)) { ERR("load_cache_chunk(%I64x) returned %08lx\n", c->offset, Status); release_chunk_lock(c, Vcb); ExReleaseResourceLite(&Vcb->chunk_lock); return Status; } c->changed = true; c->space_changed = true; release_chunk_lock(c, Vcb); } le = le->Flink; } ExReleaseResourceLite(&Vcb->chunk_lock); } return Status; } NTSTATUS add_space_entry(LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t offset, uint64_t size) { space* s; s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG); if (!s) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } s->address = offset; s->size = size; if (IsListEmpty(list)) InsertTailList(list, &s->list_entry); else { space* s2 = CONTAINING_RECORD(list->Blink, space, list_entry); if (s2->address < offset) InsertTailList(list, &s->list_entry); else { LIST_ENTRY* le; le = list->Flink; while (le != list) { s2 = CONTAINING_RECORD(le, space, list_entry); if (s2->address > offset) { InsertTailList(le, &s->list_entry); goto size; } le = le->Flink; } } } size: if (!list_size) return STATUS_SUCCESS; if (IsListEmpty(list_size)) InsertTailList(list_size, &s->list_entry_size); else { space* s2 = CONTAINING_RECORD(list_size->Blink, space, list_entry_size); if (s2->size >= size) InsertTailList(list_size, &s->list_entry_size); else { LIST_ENTRY* le; le = list_size->Flink; while (le != list_size) { s2 = CONTAINING_RECORD(le, space, list_entry_size); if (s2->size <= size) { InsertHeadList(le->Blink, &s->list_entry_size); return STATUS_SUCCESS; } le = le->Flink; } } } return STATUS_SUCCESS; } static void load_free_space_bitmap(device_extension* Vcb, chunk* c, uint64_t offset, void* data, uint64_t* total_space) { RTL_BITMAP bmph; uint32_t i, len, *dwords = data; ULONG runlength, index; // flip bits for (i = 0; i < Vcb->superblock.sector_size / sizeof(uint32_t); i++) { dwords[i] = ~dwords[i]; } len = Vcb->superblock.sector_size * 8; RtlInitializeBitMap(&bmph, data, len); index = 0; runlength = RtlFindFirstRunClear(&bmph, &index); while (runlength != 0) { uint64_t addr, length; if (index >= len) break; if (index + runlength >= len) { runlength = len - index; if (runlength == 0) break; } addr = offset + (index << Vcb->sector_shift); length = runlength << Vcb->sector_shift; add_space_entry(&c->space, &c->space_size, addr, length); index += runlength; *total_space += length; runlength = RtlFindNextForwardRunClear(&bmph, index, &index); } } static void order_space_entry(space* s, LIST_ENTRY* list_size) { LIST_ENTRY* le; if (IsListEmpty(list_size)) { InsertHeadList(list_size, &s->list_entry_size); return; } le = list_size->Flink; while (le != list_size) { space* s2 = CONTAINING_RECORD(le, space, list_entry_size); if (s2->size <= s->size) { InsertHeadList(le->Blink, &s->list_entry_size); return; } le = le->Flink; } InsertTailList(list_size, &s->list_entry_size); } typedef struct { uint64_t stripe; LIST_ENTRY list_entry; } superblock_stripe; static NTSTATUS add_superblock_stripe(LIST_ENTRY* stripes, uint64_t off, uint64_t len) { uint64_t i; for (i = 0; i < len; i++) { LIST_ENTRY* le; superblock_stripe* ss; bool ignore = false; le = stripes->Flink; while (le != stripes) { ss = CONTAINING_RECORD(le, superblock_stripe, list_entry); if (ss->stripe == off + i) { ignore = true; break; } le = le->Flink; } if (ignore) continue; ss = ExAllocatePoolWithTag(PagedPool, sizeof(superblock_stripe), ALLOC_TAG); if (!ss) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ss->stripe = off + i; InsertTailList(stripes, &ss->list_entry); } return STATUS_SUCCESS; } static NTSTATUS get_superblock_size(chunk* c, uint64_t* size) { NTSTATUS Status; CHUNK_ITEM* ci = c->chunk_item; CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1]; uint64_t off_start, off_end, space = 0; uint16_t i = 0, j; LIST_ENTRY stripes; InitializeListHead(&stripes); while (superblock_addrs[i] != 0) { if (ci->type & BLOCK_FLAG_RAID0 || ci->type & BLOCK_FLAG_RAID10) { for (j = 0; j < ci->num_stripes; j++) { ULONG sub_stripes = max(ci->sub_stripes, 1); if (cis[j].offset + (ci->size * ci->num_stripes / sub_stripes) > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) { off_start = superblock_addrs[i] - cis[j].offset; off_start -= off_start % ci->stripe_length; off_start *= ci->num_stripes / sub_stripes; off_start += (j / sub_stripes) * ci->stripe_length; off_end = off_start + ci->stripe_length; Status = add_superblock_stripe(&stripes, off_start / ci->stripe_length, 1); if (!NT_SUCCESS(Status)) { ERR("add_superblock_stripe returned %08lx\n", Status); goto end; } } } } else if (ci->type & BLOCK_FLAG_RAID5) { for (j = 0; j < ci->num_stripes; j++) { uint64_t stripe_size = ci->size / (ci->num_stripes - 1); if (cis[j].offset + stripe_size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) { off_start = superblock_addrs[i] - cis[j].offset; off_start -= off_start % (ci->stripe_length * (ci->num_stripes - 1)); off_start *= ci->num_stripes - 1; off_end = off_start + (ci->stripe_length * (ci->num_stripes - 1)); Status = add_superblock_stripe(&stripes, off_start / ci->stripe_length, (off_end - off_start) / ci->stripe_length); if (!NT_SUCCESS(Status)) { ERR("add_superblock_stripe returned %08lx\n", Status); goto end; } } } } else if (ci->type & BLOCK_FLAG_RAID6) { for (j = 0; j < ci->num_stripes; j++) { uint64_t stripe_size = ci->size / (ci->num_stripes - 2); if (cis[j].offset + stripe_size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) { off_start = superblock_addrs[i] - cis[j].offset; off_start -= off_start % (ci->stripe_length * (ci->num_stripes - 2)); off_start *= ci->num_stripes - 2; off_end = off_start + (ci->stripe_length * (ci->num_stripes - 2)); Status = add_superblock_stripe(&stripes, off_start / ci->stripe_length, (off_end - off_start) / ci->stripe_length); if (!NT_SUCCESS(Status)) { ERR("add_superblock_stripe returned %08lx\n", Status); goto end; } } } } else { // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4 for (j = 0; j < ci->num_stripes; j++) { if (cis[j].offset + ci->size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) { off_start = ((superblock_addrs[i] - cis[j].offset) / c->chunk_item->stripe_length) * c->chunk_item->stripe_length; off_end = sector_align(superblock_addrs[i] - cis[j].offset + sizeof(superblock), c->chunk_item->stripe_length); Status = add_superblock_stripe(&stripes, off_start / ci->stripe_length, (off_end - off_start) / ci->stripe_length); if (!NT_SUCCESS(Status)) { ERR("add_superblock_stripe returned %08lx\n", Status); goto end; } } } } i++; } Status = STATUS_SUCCESS; end: while (!IsListEmpty(&stripes)) { LIST_ENTRY* le = RemoveHeadList(&stripes); superblock_stripe* ss = CONTAINING_RECORD(le, superblock_stripe, list_entry); space++; ExFreePool(ss); } if (NT_SUCCESS(Status)) *size = space * ci->stripe_length; return Status; } NTSTATUS load_stored_free_space_cache(device_extension* Vcb, chunk* c, bool load_only, PIRP Irp) { KEY searchkey; traverse_ptr tp; FREE_SPACE_ITEM* fsi; uint64_t inode, *generation; uint8_t* data; NTSTATUS Status; uint32_t *checksums, crc32, num_sectors, num_valid_sectors, size; FREE_SPACE_ENTRY* fse; uint64_t num_entries, num_bitmaps, extent_length, bmpnum, off, total_space = 0, superblock_size; LIST_ENTRY *le, rollback; // FIXME - does this break if Vcb->superblock.sector_size is not 4096? TRACE("(%p, %I64x)\n", Vcb, c->offset); searchkey.obj_id = FREE_SPACE_CACHE_ID; searchkey.obj_type = 0; searchkey.offset = c->offset; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (keycmp(tp.item->key, searchkey)) { TRACE("(%I64x,%x,%I64x) not found\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); return STATUS_NOT_FOUND; } if (tp.item->size < sizeof(FREE_SPACE_ITEM)) { 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)); return STATUS_NOT_FOUND; } fsi = (FREE_SPACE_ITEM*)tp.item->data; if (fsi->key.obj_type != TYPE_INODE_ITEM) { WARN("cache pointed to something other than an INODE_ITEM\n"); return STATUS_NOT_FOUND; } inode = fsi->key.obj_id; num_entries = fsi->num_entries; num_bitmaps = fsi->num_bitmaps; Status = open_fcb(Vcb, Vcb->root_root, inode, BTRFS_TYPE_FILE, NULL, false, NULL, &c->cache, PagedPool, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fcb returned %08lx\n", Status); return STATUS_NOT_FOUND; } if (load_only) return STATUS_SUCCESS; if (c->cache->inode_item.st_size == 0) { WARN("cache had zero length\n"); free_fcb(c->cache); c->cache = NULL; return STATUS_NOT_FOUND; } c->cache->inode_item.flags |= BTRFS_INODE_NODATACOW; if (num_entries == 0 && num_bitmaps == 0) return STATUS_SUCCESS; size = (uint32_t)sector_align(c->cache->inode_item.st_size, Vcb->superblock.sector_size); data = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG); if (!data) { ERR("out of memory\n"); free_fcb(c->cache); c->cache = NULL; return STATUS_INSUFFICIENT_RESOURCES; } if (c->chunk_item->size < 0x6400000) { // 100 MB WARN("deleting free space cache for chunk smaller than 100MB\n"); goto clearcache; } Status = read_file(c->cache, data, 0, c->cache->inode_item.st_size, NULL, NULL); if (!NT_SUCCESS(Status)) { ERR("read_file returned %08lx\n", Status); ExFreePool(data); c->cache->deleted = true; mark_fcb_dirty(c->cache); free_fcb(c->cache); c->cache = NULL; return STATUS_NOT_FOUND; } if (size > c->cache->inode_item.st_size) RtlZeroMemory(&data[c->cache->inode_item.st_size], (ULONG)(size - c->cache->inode_item.st_size)); num_sectors = size >> Vcb->sector_shift; generation = (uint64_t*)(data + (num_sectors * sizeof(uint32_t))); if (*generation != fsi->generation) { WARN("free space cache generation for %I64x was %I64x, expected %I64x\n", c->offset, *generation, fsi->generation); goto clearcache; } extent_length = (num_sectors * sizeof(uint32_t)) + sizeof(uint64_t) + (num_entries * sizeof(FREE_SPACE_ENTRY)); num_valid_sectors = (ULONG)((sector_align(extent_length, Vcb->superblock.sector_size) >> Vcb->sector_shift) + num_bitmaps); if (num_valid_sectors > num_sectors) { ERR("free space cache for %I64x was %u sectors, expected at least %u\n", c->offset, num_sectors, num_valid_sectors); goto clearcache; } checksums = (uint32_t*)data; for (uint32_t i = 0; i < num_valid_sectors; i++) { if (i << Vcb->sector_shift > sizeof(uint32_t) * num_sectors) crc32 = ~calc_crc32c(0xffffffff, &data[i << Vcb->sector_shift], Vcb->superblock.sector_size); else if ((i + 1) << Vcb->sector_shift < sizeof(uint32_t) * num_sectors) crc32 = 0; // FIXME - test this else crc32 = ~calc_crc32c(0xffffffff, &data[sizeof(uint32_t) * num_sectors], ((i + 1) << Vcb->sector_shift) - (sizeof(uint32_t) * num_sectors)); if (crc32 != checksums[i]) { WARN("checksum %u was %08x, expected %08x\n", i, crc32, checksums[i]); goto clearcache; } } off = (sizeof(uint32_t) * num_sectors) + sizeof(uint64_t); bmpnum = 0; for (uint32_t i = 0; i < num_entries; i++) { if ((off + sizeof(FREE_SPACE_ENTRY)) >> Vcb->sector_shift != off >> Vcb->sector_shift) off = sector_align(off, Vcb->superblock.sector_size); fse = (FREE_SPACE_ENTRY*)&data[off]; if (fse->type == FREE_SPACE_EXTENT) { Status = add_space_entry(&c->space, &c->space_size, fse->offset, fse->size); if (!NT_SUCCESS(Status)) { ERR("add_space_entry returned %08lx\n", Status); ExFreePool(data); return Status; } total_space += fse->size; } else if (fse->type != FREE_SPACE_BITMAP) { ERR("unknown free-space type %x\n", fse->type); } off += sizeof(FREE_SPACE_ENTRY); } if (num_bitmaps > 0) { bmpnum = sector_align(off, Vcb->superblock.sector_size) >> Vcb->sector_shift; off = (sizeof(uint32_t) * num_sectors) + sizeof(uint64_t); for (uint32_t i = 0; i < num_entries; i++) { if ((off + sizeof(FREE_SPACE_ENTRY)) >> Vcb->sector_shift != off >> Vcb->sector_shift) off = sector_align(off, Vcb->superblock.sector_size); fse = (FREE_SPACE_ENTRY*)&data[off]; if (fse->type == FREE_SPACE_BITMAP) { // FIXME - make sure we don't overflow the buffer here load_free_space_bitmap(Vcb, c, fse->offset, &data[bmpnum << Vcb->sector_shift], &total_space); bmpnum++; } off += sizeof(FREE_SPACE_ENTRY); } } // do sanity check Status = get_superblock_size(c, &superblock_size); if (!NT_SUCCESS(Status)) { ERR("get_superblock_size returned %08lx\n", Status); ExFreePool(data); return Status; } if (c->chunk_item->size - c->used != total_space + superblock_size) { WARN("invalidating cache for chunk %I64x: space was %I64x, expected %I64x\n", c->offset, total_space + superblock_size, c->chunk_item->size - c->used); goto clearcache; } le = c->space.Flink; while (le != &c->space) { space* s = CONTAINING_RECORD(le, space, list_entry); LIST_ENTRY* le2 = le->Flink; if (le2 != &c->space) { space* s2 = CONTAINING_RECORD(le2, space, list_entry); if (s2->address == s->address + s->size) { s->size += s2->size; RemoveEntryList(&s2->list_entry); RemoveEntryList(&s2->list_entry_size); ExFreePool(s2); RemoveEntryList(&s->list_entry_size); order_space_entry(s, &c->space_size); le2 = le; } } le = le2; } ExFreePool(data); return STATUS_SUCCESS; clearcache: ExFreePool(data); InitializeListHead(&rollback); Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } Status = excise_extents(Vcb, c->cache, 0, c->cache->inode_item.st_size, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); do_rollback(Vcb, &rollback); return Status; } clear_rollback(&rollback); c->cache->deleted = true; mark_fcb_dirty(c->cache); c->old_cache = c->cache; c->cache = NULL; le = c->space.Flink; while (le != &c->space) { space* s = CONTAINING_RECORD(le, space, list_entry); LIST_ENTRY* le2 = le->Flink; RemoveEntryList(&s->list_entry); RemoveEntryList(&s->list_entry_size); ExFreePool(s); le = le2; } return STATUS_NOT_FOUND; } static NTSTATUS load_stored_free_space_tree(device_extension* Vcb, chunk* c, PIRP Irp) { KEY searchkey; traverse_ptr tp, next_tp; NTSTATUS Status; ULONG* bmparr = NULL; ULONG bmplen = 0; LIST_ENTRY* le; TRACE("(%p, %I64x)\n", Vcb, c->offset); if (!Vcb->space_root) return STATUS_NOT_FOUND; searchkey.obj_id = c->offset; searchkey.obj_type = TYPE_FREE_SPACE_INFO; searchkey.offset = c->chunk_item->size; Status = find_item(Vcb, Vcb->space_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (keycmp(tp.item->key, searchkey)) { TRACE("(%I64x,%x,%I64x) not found\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); return STATUS_NOT_FOUND; } if (tp.item->size < sizeof(FREE_SPACE_INFO)) { 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)); return STATUS_NOT_FOUND; } while (find_next_item(Vcb, &tp, &next_tp, false, Irp)) { tp = next_tp; if (tp.item->key.obj_id >= c->offset + c->chunk_item->size) break; if (tp.item->key.obj_type == TYPE_FREE_SPACE_EXTENT) { Status = add_space_entry(&c->space, &c->space_size, tp.item->key.obj_id, tp.item->key.offset); if (!NT_SUCCESS(Status)) { ERR("add_space_entry returned %08lx\n", Status); if (bmparr) ExFreePool(bmparr); return Status; } } else if (tp.item->key.obj_type == TYPE_FREE_SPACE_BITMAP) { ULONG explen, index, runlength; RTL_BITMAP bmp; uint64_t lastoff; ULONG bmpl; explen = (ULONG)(tp.item->key.offset >> Vcb->sector_shift) / 8; if (tp.item->size < explen) { 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); return STATUS_NOT_FOUND; } else if (tp.item->size == 0) { WARN("(%I64x,%x,%I64x) has size of 0\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); return STATUS_NOT_FOUND; } if (bmplen < tp.item->size) { if (bmparr) ExFreePool(bmparr); bmplen = (ULONG)sector_align(tp.item->size, sizeof(ULONG)); bmparr = ExAllocatePoolWithTag(PagedPool, bmplen, ALLOC_TAG); if (!bmparr) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } } // We copy the bitmap because it supposedly has to be ULONG-aligned RtlCopyMemory(bmparr, tp.item->data, tp.item->size); bmpl = (ULONG)tp.item->key.offset >> Vcb->sector_shift; RtlInitializeBitMap(&bmp, bmparr, bmpl); lastoff = tp.item->key.obj_id; runlength = RtlFindFirstRunClear(&bmp, &index); while (runlength != 0) { uint64_t runstart, runend; if (index >= bmpl) break; if (index + runlength >= bmpl) { runlength = bmpl - index; if (runlength == 0) break; } runstart = tp.item->key.obj_id + (index << Vcb->sector_shift); runend = runstart + (runlength << Vcb->sector_shift); if (runstart > lastoff) { Status = add_space_entry(&c->space, &c->space_size, lastoff, runstart - lastoff); if (!NT_SUCCESS(Status)) { ERR("add_space_entry returned %08lx\n", Status); if (bmparr) ExFreePool(bmparr); return Status; } } lastoff = runend; runlength = RtlFindNextForwardRunClear(&bmp, index + runlength, &index); } if (lastoff < tp.item->key.obj_id + tp.item->key.offset) { Status = add_space_entry(&c->space, &c->space_size, lastoff, tp.item->key.obj_id + tp.item->key.offset - lastoff); if (!NT_SUCCESS(Status)) { ERR("add_space_entry returned %08lx\n", Status); if (bmparr) ExFreePool(bmparr); return Status; } } } } if (bmparr) ExFreePool(bmparr); le = c->space.Flink; while (le != &c->space) { space* s = CONTAINING_RECORD(le, space, list_entry); LIST_ENTRY* le2 = le->Flink; if (le2 != &c->space) { space* s2 = CONTAINING_RECORD(le2, space, list_entry); if (s2->address == s->address + s->size) { s->size += s2->size; RemoveEntryList(&s2->list_entry); RemoveEntryList(&s2->list_entry_size); ExFreePool(s2); RemoveEntryList(&s->list_entry_size); order_space_entry(s, &c->space_size); le2 = le; } } le = le2; } return STATUS_SUCCESS; } static NTSTATUS load_free_space_cache(device_extension* Vcb, chunk* c, PIRP Irp) { traverse_ptr tp, next_tp; KEY searchkey; uint64_t lastaddr; bool b; space* s; NTSTATUS Status; 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) { Status = load_stored_free_space_tree(Vcb, c, Irp); if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) { ERR("load_stored_free_space_tree returned %08lx\n", Status); return Status; } } else if (Vcb->superblock.generation - 1 == Vcb->superblock.cache_generation) { Status = load_stored_free_space_cache(Vcb, c, false, Irp); if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) { ERR("load_stored_free_space_cache returned %08lx\n", Status); return Status; } } else Status = STATUS_NOT_FOUND; if (Status == STATUS_NOT_FOUND) { TRACE("generating free space cache for chunk %I64x\n", c->offset); searchkey.obj_id = c->offset; searchkey.obj_type = TYPE_EXTENT_ITEM; searchkey.offset = 0; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } lastaddr = c->offset; do { if (tp.item->key.obj_id >= c->offset + c->chunk_item->size) break; if (tp.item->key.obj_id >= c->offset && (tp.item->key.obj_type == TYPE_EXTENT_ITEM || tp.item->key.obj_type == TYPE_METADATA_ITEM)) { if (tp.item->key.obj_id > lastaddr) { s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG); if (!s) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } s->address = lastaddr; s->size = tp.item->key.obj_id - lastaddr; InsertTailList(&c->space, &s->list_entry); order_space_entry(s, &c->space_size); TRACE("(%I64x,%I64x)\n", s->address, s->size); } if (tp.item->key.obj_type == TYPE_METADATA_ITEM) lastaddr = tp.item->key.obj_id + Vcb->superblock.node_size; else lastaddr = tp.item->key.obj_id + tp.item->key.offset; } b = find_next_item(Vcb, &tp, &next_tp, false, Irp); if (b) tp = next_tp; } while (b); if (lastaddr < c->offset + c->chunk_item->size) { s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG); if (!s) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } s->address = lastaddr; s->size = c->offset + c->chunk_item->size - lastaddr; InsertTailList(&c->space, &s->list_entry); order_space_entry(s, &c->space_size); TRACE("(%I64x,%I64x)\n", s->address, s->size); } } return STATUS_SUCCESS; } NTSTATUS load_cache_chunk(device_extension* Vcb, chunk* c, PIRP Irp) { NTSTATUS Status; if (c->cache_loaded) return STATUS_SUCCESS; Status = load_free_space_cache(Vcb, c, Irp); if (!NT_SUCCESS(Status)) { ERR("load_free_space_cache returned %08lx\n", Status); return Status; } protect_superblocks(c); c->cache_loaded = true; return STATUS_SUCCESS; } static NTSTATUS insert_cache_extent(fcb* fcb, uint64_t start, uint64_t length, LIST_ENTRY* rollback) { NTSTATUS Status; LIST_ENTRY* le = fcb->Vcb->chunks.Flink; chunk* c; uint64_t flags; flags = fcb->Vcb->data_flags; while (le != &fcb->Vcb->chunks) { c = CONTAINING_RECORD(le, chunk, list_entry); if (!c->readonly && !c->reloc) { acquire_chunk_lock(c, fcb->Vcb); if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) { if (insert_extent_chunk(fcb->Vcb, fcb, c, start, length, false, NULL, NULL, rollback, BTRFS_COMPRESSION_NONE, length, false, 0)) return STATUS_SUCCESS; } release_chunk_lock(c, fcb->Vcb); } le = le->Flink; } Status = alloc_chunk(fcb->Vcb, flags, &c, false); if (!NT_SUCCESS(Status)) { ERR("alloc_chunk returned %08lx\n", Status); return Status; } acquire_chunk_lock(c, fcb->Vcb); if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) { if (insert_extent_chunk(fcb->Vcb, fcb, c, start, length, false, NULL, NULL, rollback, BTRFS_COMPRESSION_NONE, length, false, 0)) return STATUS_SUCCESS; } release_chunk_lock(c, fcb->Vcb); return STATUS_DISK_FULL; } static NTSTATUS allocate_cache_chunk(device_extension* Vcb, chunk* c, bool* changed, LIST_ENTRY* batchlist, PIRP Irp, LIST_ENTRY* rollback) { LIST_ENTRY* le; NTSTATUS Status; uint64_t num_entries, new_cache_size, i; uint32_t num_sectors; bool realloc_extents = false; // FIXME - also do bitmaps // FIXME - make sure this works when sector_size is not 4096 *changed = false; num_entries = 0; // num_entries is the number of entries in c->space and c->deleting - it might // be slightly higher then what we end up writing, but doing it this way is much // quicker and simpler. if (!IsListEmpty(&c->space)) { le = c->space.Flink; while (le != &c->space) { num_entries++; le = le->Flink; } } if (!IsListEmpty(&c->deleting)) { le = c->deleting.Flink; while (le != &c->deleting) { num_entries++; le = le->Flink; } } new_cache_size = sizeof(uint64_t) + (num_entries * sizeof(FREE_SPACE_ENTRY)); num_sectors = (uint32_t)sector_align(new_cache_size, Vcb->superblock.sector_size) >> Vcb->sector_shift; num_sectors = (uint32_t)sector_align(num_sectors, CACHE_INCREMENTS); // adjust for padding // FIXME - there must be a more efficient way of doing this new_cache_size = sizeof(uint64_t) + (sizeof(uint32_t) * num_sectors); for (i = 0; i < num_entries; i++) { if ((new_cache_size >> Vcb->sector_shift) != ((new_cache_size + sizeof(FREE_SPACE_ENTRY)) >> Vcb->sector_shift)) new_cache_size = sector_align(new_cache_size, Vcb->superblock.sector_size); new_cache_size += sizeof(FREE_SPACE_ENTRY); } new_cache_size = sector_align(new_cache_size, CACHE_INCREMENTS << Vcb->sector_shift); 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); if (c->cache) { if (new_cache_size > c->cache->inode_item.st_size) realloc_extents = true; else { le = c->cache->extents.Flink; while (le != &c->cache->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (!ext->ignore && (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC)) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)&ext->extent_data.data[0]; if (ed2->size != 0) { chunk* c2 = get_chunk_from_address(Vcb, ed2->address); if (c2 && (c2->readonly || c2->reloc)) { realloc_extents = true; break; } } } le = le->Flink; } } } if (!c->cache) { FREE_SPACE_ITEM* fsi; KEY searchkey; traverse_ptr tp; // create new inode c->cache = create_fcb(Vcb, PagedPool); if (!c->cache) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } c->cache->Vcb = Vcb; c->cache->inode_item.st_size = new_cache_size; c->cache->inode_item.st_blocks = new_cache_size; c->cache->inode_item.st_nlink = 1; c->cache->inode_item.st_mode = S_IRUSR | S_IWUSR | __S_IFREG; c->cache->inode_item.flags = BTRFS_INODE_NODATASUM | BTRFS_INODE_NODATACOW | BTRFS_INODE_NOCOMPRESS | BTRFS_INODE_PREALLOC; c->cache->Header.IsFastIoPossible = fast_io_possible(c->cache); c->cache->Header.AllocationSize.QuadPart = 0; c->cache->Header.FileSize.QuadPart = 0; c->cache->Header.ValidDataLength.QuadPart = 0; c->cache->subvol = Vcb->root_root; c->cache->inode = InterlockedIncrement64(&Vcb->root_root->lastinode); c->cache->hash = calc_crc32c(0xffffffff, (uint8_t*)&c->cache->inode, sizeof(uint64_t)); c->cache->type = BTRFS_TYPE_FILE; c->cache->created = true; // create new free space entry fsi = ExAllocatePoolWithTag(PagedPool, sizeof(FREE_SPACE_ITEM), ALLOC_TAG); if (!fsi) { ERR("out of memory\n"); reap_fcb(c->cache); c->cache = NULL; return STATUS_INSUFFICIENT_RESOURCES; } searchkey.obj_id = FREE_SPACE_CACHE_ID; searchkey.obj_type = 0; searchkey.offset = c->offset; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); ExFreePool(fsi); reap_fcb(c->cache); c->cache = NULL; return Status; } if (!keycmp(searchkey, tp.item->key)) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); ExFreePool(fsi); reap_fcb(c->cache); c->cache = NULL; return Status; } } fsi->key.obj_id = c->cache->inode; fsi->key.obj_type = TYPE_INODE_ITEM; fsi->key.offset = 0; Status = insert_tree_item(Vcb, Vcb->root_root, FREE_SPACE_CACHE_ID, 0, c->offset, fsi, sizeof(FREE_SPACE_ITEM), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(fsi); reap_fcb(c->cache); c->cache = NULL; return Status; } // allocate space Status = insert_cache_extent(c->cache, 0, new_cache_size, rollback); if (!NT_SUCCESS(Status)) { ERR("insert_cache_extent returned %08lx\n", Status); reap_fcb(c->cache); c->cache = NULL; return Status; } c->cache->extents_changed = true; InsertTailList(&Vcb->all_fcbs, &c->cache->list_entry_all); add_fcb_to_subvol(c->cache); Status = flush_fcb(c->cache, true, batchlist, Irp); if (!NT_SUCCESS(Status)) { ERR("flush_fcb returned %08lx\n", Status); free_fcb(c->cache); c->cache = NULL; return Status; } *changed = true; } else if (realloc_extents) { KEY searchkey; traverse_ptr tp; TRACE("reallocating extents\n"); // add free_space entry to tree cache searchkey.obj_id = FREE_SPACE_CACHE_ID; searchkey.obj_type = 0; searchkey.offset = c->offset; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (keycmp(searchkey, tp.item->key)) { ERR("could not find (%I64x,%x,%I64x) in root_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); return STATUS_INTERNAL_ERROR; } if (tp.item->size < sizeof(FREE_SPACE_ITEM)) { 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)); return STATUS_INTERNAL_ERROR; } tp.tree->write = true; // remove existing extents if (c->cache->inode_item.st_size > 0) { le = c->cache->extents.Flink; while (le != &c->cache->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (!ext->ignore && (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC)) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)&ext->extent_data.data[0]; if (ed2->size != 0) { chunk* c2 = get_chunk_from_address(Vcb, ed2->address); if (c2) { c2->changed = true; c2->space_changed = true; } } } le = le->Flink; } Status = excise_extents(Vcb, c->cache, 0, c->cache->inode_item.st_size, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); return Status; } } // add new extent Status = insert_cache_extent(c->cache, 0, new_cache_size, rollback); if (!NT_SUCCESS(Status)) { ERR("insert_cache_extent returned %08lx\n", Status); return Status; } // modify INODE_ITEM c->cache->inode_item.st_size = new_cache_size; c->cache->inode_item.st_blocks = new_cache_size; Status = flush_fcb(c->cache, true, batchlist, Irp); if (!NT_SUCCESS(Status)) { ERR("flush_fcb returned %08lx\n", Status); return Status; } *changed = true; } else { KEY searchkey; traverse_ptr tp; // add INODE_ITEM and free_space entry to tree cache, for writing later searchkey.obj_id = c->cache->inode; searchkey.obj_type = TYPE_INODE_ITEM; searchkey.offset = 0; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (keycmp(searchkey, tp.item->key)) { INODE_ITEM* ii; ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG); if (!ii) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(ii, &c->cache->inode_item, sizeof(INODE_ITEM)); Status = insert_tree_item(Vcb, Vcb->root_root, c->cache->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(ii); return Status; } *changed = true; } else { if (tp.item->size < sizeof(INODE_ITEM)) { 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)); return STATUS_INTERNAL_ERROR; } tp.tree->write = true; } searchkey.obj_id = FREE_SPACE_CACHE_ID; searchkey.obj_type = 0; searchkey.offset = c->offset; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); return Status; } if (keycmp(searchkey, tp.item->key)) { ERR("could not find (%I64x,%x,%I64x) in root_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); return STATUS_INTERNAL_ERROR; } if (tp.item->size < sizeof(FREE_SPACE_ITEM)) { 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)); return STATUS_INTERNAL_ERROR; } tp.tree->write = true; } // FIXME - reduce inode allocation if cache is shrinking. Make sure to avoid infinite write loops return STATUS_SUCCESS; } NTSTATUS allocate_cache(device_extension* Vcb, bool* changed, PIRP Irp, LIST_ENTRY* rollback) { LIST_ENTRY *le, batchlist; NTSTATUS Status; *changed = false; InitializeListHead(&batchlist); ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry); if (c->space_changed && c->chunk_item->size >= 0x6400000) { // 100MB bool b; acquire_chunk_lock(c, Vcb); Status = allocate_cache_chunk(Vcb, c, &b, &batchlist, Irp, rollback); release_chunk_lock(c, Vcb); if (b) *changed = true; if (!NT_SUCCESS(Status)) { ERR("allocate_cache_chunk(%I64x) returned %08lx\n", c->offset, Status); ExReleaseResourceLite(&Vcb->chunk_lock); clear_batch_list(Vcb, &batchlist); return Status; } } le = le->Flink; } ExReleaseResourceLite(&Vcb->chunk_lock); Status = commit_batch_list(Vcb, &batchlist, Irp); if (!NT_SUCCESS(Status)) { ERR("commit_batch_list returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } static void add_rollback_space(LIST_ENTRY* rollback, bool add, LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t address, uint64_t length, chunk* c) { rollback_space* rs; rs = ExAllocatePoolWithTag(PagedPool, sizeof(rollback_space), ALLOC_TAG); if (!rs) { ERR("out of memory\n"); return; } rs->list = list; rs->list_size = list_size; rs->address = address; rs->length = length; rs->chunk = c; add_rollback(rollback, add ? ROLLBACK_ADD_SPACE : ROLLBACK_SUBTRACT_SPACE, rs); } void space_list_add2(LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t address, uint64_t length, chunk* c, LIST_ENTRY* rollback) { LIST_ENTRY* le; space *s, *s2; if (IsListEmpty(list)) { s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG); if (!s) { ERR("out of memory\n"); return; } s->address = address; s->size = length; InsertTailList(list, &s->list_entry); if (list_size) InsertTailList(list_size, &s->list_entry_size); if (rollback) add_rollback_space(rollback, true, list, list_size, address, length, c); return; } le = list->Flink; do { s2 = CONTAINING_RECORD(le, space, list_entry); // old entry envelops new one completely if (s2->address <= address && s2->address + s2->size >= address + length) return; // new entry envelops old one completely if (address <= s2->address && address + length >= s2->address + s2->size) { if (address < s2->address) { if (rollback) add_rollback_space(rollback, true, list, list_size, address, s2->address - address, c); s2->size += s2->address - address; s2->address = address; while (s2->list_entry.Blink != list) { space* s3 = CONTAINING_RECORD(s2->list_entry.Blink, space, list_entry); if (s3->address + s3->size == s2->address) { s2->address = s3->address; s2->size += s3->size; RemoveEntryList(&s3->list_entry); if (list_size) RemoveEntryList(&s3->list_entry_size); ExFreePool(s3); } else break; } } if (length > s2->size) { if (rollback) add_rollback_space(rollback, true, list, list_size, s2->address + s2->size, address + length - s2->address - s2->size, c); s2->size = length; while (s2->list_entry.Flink != list) { space* s3 = CONTAINING_RECORD(s2->list_entry.Flink, space, list_entry); if (s3->address <= s2->address + s2->size) { s2->size = max(s2->size, s3->address + s3->size - s2->address); RemoveEntryList(&s3->list_entry); if (list_size) RemoveEntryList(&s3->list_entry_size); ExFreePool(s3); } else break; } } if (list_size) { RemoveEntryList(&s2->list_entry_size); order_space_entry(s2, list_size); } return; } // new entry overlaps start of old one if (address < s2->address && address + length >= s2->address) { if (rollback) add_rollback_space(rollback, true, list, list_size, address, s2->address - address, c); s2->size += s2->address - address; s2->address = address; while (s2->list_entry.Blink != list) { space* s3 = CONTAINING_RECORD(s2->list_entry.Blink, space, list_entry); if (s3->address + s3->size == s2->address) { s2->address = s3->address; s2->size += s3->size; RemoveEntryList(&s3->list_entry); if (list_size) RemoveEntryList(&s3->list_entry_size); ExFreePool(s3); } else break; } if (list_size) { RemoveEntryList(&s2->list_entry_size); order_space_entry(s2, list_size); } return; } // new entry overlaps end of old one if (address <= s2->address + s2->size && address + length > s2->address + s2->size) { if (rollback) add_rollback_space(rollback, true, list, list_size, address, s2->address + s2->size - address, c); s2->size = address + length - s2->address; while (s2->list_entry.Flink != list) { space* s3 = CONTAINING_RECORD(s2->list_entry.Flink, space, list_entry); if (s3->address <= s2->address + s2->size) { s2->size = max(s2->size, s3->address + s3->size - s2->address); RemoveEntryList(&s3->list_entry); if (list_size) RemoveEntryList(&s3->list_entry_size); ExFreePool(s3); } else break; } if (list_size) { RemoveEntryList(&s2->list_entry_size); order_space_entry(s2, list_size); } return; } // add completely separate entry if (s2->address > address + length) { s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG); if (!s) { ERR("out of memory\n"); return; } if (rollback) add_rollback_space(rollback, true, list, list_size, address, length, c); s->address = address; s->size = length; InsertHeadList(s2->list_entry.Blink, &s->list_entry); if (list_size) order_space_entry(s, list_size); return; } le = le->Flink; } while (le != list); // check if contiguous with last entry if (s2->address + s2->size == address) { s2->size += length; if (list_size) { RemoveEntryList(&s2->list_entry_size); order_space_entry(s2, list_size); } return; } // otherwise, insert at end s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG); if (!s) { ERR("out of memory\n"); return; } s->address = address; s->size = length; InsertTailList(list, &s->list_entry); if (list_size) order_space_entry(s, list_size); if (rollback) add_rollback_space(rollback, true, list, list_size, address, length, c); } void space_list_merge(LIST_ENTRY* spacelist, LIST_ENTRY* spacelist_size, LIST_ENTRY* deleting) { LIST_ENTRY* le = deleting->Flink; while (le != deleting) { space* s = CONTAINING_RECORD(le, space, list_entry); space_list_add2(spacelist, spacelist_size, s->address, s->size, NULL, NULL); le = le->Flink; } } static NTSTATUS copy_space_list(LIST_ENTRY* old_list, LIST_ENTRY* new_list) { LIST_ENTRY* le; le = old_list->Flink; while (le != old_list) { space* s = CONTAINING_RECORD(le, space, list_entry); space* s2; s2 = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG); if (!s2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } s2->address = s->address; s2->size = s->size; InsertTailList(new_list, &s2->list_entry); le = le->Flink; } return STATUS_SUCCESS; } static NTSTATUS update_chunk_cache(device_extension* Vcb, chunk* c, BTRFS_TIME* now, LIST_ENTRY* batchlist, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; FREE_SPACE_ITEM* fsi; void* data; uint64_t num_entries, *cachegen, off; uint32_t *checksums, num_sectors; LIST_ENTRY space_list, deleting; data = ExAllocatePoolWithTag(NonPagedPool, (ULONG)c->cache->inode_item.st_size, ALLOC_TAG); if (!data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(data, (ULONG)c->cache->inode_item.st_size); InitializeListHead(&space_list); InitializeListHead(&deleting); Status = copy_space_list(&c->space, &space_list); if (!NT_SUCCESS(Status)) { ERR("copy_space_list returned %08lx\n", Status); goto end; } Status = copy_space_list(&c->deleting, &deleting); if (!NT_SUCCESS(Status)) { ERR("copy_space_list returned %08lx\n", Status); while (!IsListEmpty(&space_list)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry)); } goto end; } space_list_merge(&space_list, NULL, &deleting); num_entries = 0; num_sectors = (uint32_t)(c->cache->inode_item.st_size >> Vcb->sector_shift); off = (sizeof(uint32_t) * num_sectors) + sizeof(uint64_t); while (!IsListEmpty(&space_list)) { FREE_SPACE_ENTRY* fse; space* s = CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry); if ((off + sizeof(FREE_SPACE_ENTRY)) >> Vcb->sector_shift != off >> Vcb->sector_shift) off = sector_align(off, Vcb->superblock.sector_size); fse = (FREE_SPACE_ENTRY*)((uint8_t*)data + off); fse->offset = s->address; fse->size = s->size; fse->type = FREE_SPACE_EXTENT; num_entries++; off += sizeof(FREE_SPACE_ENTRY); } // update INODE_ITEM c->cache->inode_item.generation = Vcb->superblock.generation; c->cache->inode_item.transid = Vcb->superblock.generation; c->cache->inode_item.sequence++; c->cache->inode_item.st_ctime = *now; Status = flush_fcb(c->cache, true, batchlist, Irp); if (!NT_SUCCESS(Status)) { ERR("flush_fcb returned %08lx\n", Status); goto end; } // update free_space item searchkey.obj_id = FREE_SPACE_CACHE_ID; searchkey.obj_type = 0; searchkey.offset = c->offset; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); goto end; } if (keycmp(searchkey, tp.item->key)) { ERR("could not find (%I64x,%x,%I64x) in root_root\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); Status = STATUS_INTERNAL_ERROR; goto end; } if (tp.item->size < sizeof(FREE_SPACE_ITEM)) { 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)); Status = STATUS_INTERNAL_ERROR; goto end; } fsi = (FREE_SPACE_ITEM*)tp.item->data; fsi->generation = Vcb->superblock.generation; fsi->num_entries = num_entries; fsi->num_bitmaps = 0; // set cache generation cachegen = (uint64_t*)((uint8_t*)data + (sizeof(uint32_t) * num_sectors)); *cachegen = Vcb->superblock.generation; // calculate cache checksums checksums = (uint32_t*)data; // FIXME - if we know sector is fully zeroed, use cached checksum for (uint32_t i = 0; i < num_sectors; i++) { if (i << Vcb->sector_shift > sizeof(uint32_t) * num_sectors) checksums[i] = ~calc_crc32c(0xffffffff, (uint8_t*)data + (i << Vcb->sector_shift), Vcb->superblock.sector_size); else if ((i + 1) << Vcb->sector_shift < sizeof(uint32_t) * num_sectors) checksums[i] = 0; // FIXME - test this else checksums[i] = ~calc_crc32c(0xffffffff, (uint8_t*)data + (sizeof(uint32_t) * num_sectors), ((i + 1) << Vcb->sector_shift) - (sizeof(uint32_t) * num_sectors)); } // write cache Status = do_write_file(c->cache, 0, c->cache->inode_item.st_size, data, NULL, false, 0, rollback); if (!NT_SUCCESS(Status)) { ERR("do_write_file returned %08lx\n", Status); // Writing the cache isn't critical, so we don't return an error if writing fails. This means // we can still flush on a degraded mount if metadata is RAID1 but data is RAID0. } Status = STATUS_SUCCESS; end: ExFreePool(data); return Status; } static NTSTATUS update_chunk_cache_tree(device_extension* Vcb, chunk* c, PIRP Irp) { NTSTATUS Status; LIST_ENTRY space_list; FREE_SPACE_INFO* fsi; KEY searchkey; traverse_ptr tp; uint32_t fsi_count = 0; InitializeListHead(&space_list); Status = copy_space_list(&c->space, &space_list); if (!NT_SUCCESS(Status)) { ERR("copy_space_list returned %08lx\n", Status); return Status; } space_list_merge(&space_list, NULL, &c->deleting); searchkey.obj_id = c->offset; searchkey.obj_type = TYPE_FREE_SPACE_EXTENT; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->space_root, &tp, &searchkey, false, Irp); if (Status == STATUS_NOT_FOUND) goto after_tree_walk; else if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); while (!IsListEmpty(&space_list)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry)); } return Status; } while (!IsListEmpty(&space_list) && tp.item->key.obj_id < c->offset + c->chunk_item->size) { traverse_ptr next_tp; if (tp.item->key.obj_type == TYPE_FREE_SPACE_EXTENT) { space* s = CONTAINING_RECORD(space_list.Flink, space, list_entry); if (s->address < tp.item->key.obj_id || (s->address == tp.item->key.obj_id && s->size < tp.item->key.offset)) { // add entry Status = insert_tree_item(Vcb, Vcb->space_root, s->address, TYPE_FREE_SPACE_EXTENT, s->size, NULL, 0, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); while (!IsListEmpty(&space_list)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry)); } return Status; } fsi_count++; RemoveHeadList(&space_list); ExFreePool(s); continue; } else if (s->address == tp.item->key.obj_id && s->size == tp.item->key.offset) { // unchanged entry fsi_count++; RemoveHeadList(&space_list); ExFreePool(s); } else { // remove entry Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); while (!IsListEmpty(&space_list)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry)); } return Status; } } } else if (tp.item->key.obj_type == TYPE_FREE_SPACE_BITMAP) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); while (!IsListEmpty(&space_list)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry)); } return Status; } } if (!find_next_item(Vcb, &tp, &next_tp, false, Irp)) goto after_tree_walk; tp = next_tp; } // after loop, delete remaining tree items for this chunk while (tp.item->key.obj_id < c->offset + c->chunk_item->size) { traverse_ptr next_tp; if (tp.item->key.obj_type == TYPE_FREE_SPACE_EXTENT || tp.item->key.obj_type == TYPE_FREE_SPACE_BITMAP) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); return Status; } } if (!find_next_item(Vcb, &tp, &next_tp, false, NULL)) break; tp = next_tp; } after_tree_walk: // after loop, insert remaining space_list entries while (!IsListEmpty(&space_list)) { space* s = CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry); Status = insert_tree_item(Vcb, Vcb->space_root, s->address, TYPE_FREE_SPACE_EXTENT, s->size, NULL, 0, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); while (!IsListEmpty(&space_list)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry)); } return Status; } fsi_count++; ExFreePool(s); } // change TYPE_FREE_SPACE_INFO in place if present, and insert otherwise searchkey.obj_id = c->offset; searchkey.obj_type = TYPE_FREE_SPACE_INFO; searchkey.offset = c->chunk_item->size; Status = find_item(Vcb, Vcb->space_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) { ERR("find_item returned %08lx\n", Status); return Status; } if (NT_SUCCESS(Status) && !keycmp(tp.item->key, searchkey)) { if (tp.item->size == sizeof(FREE_SPACE_INFO)) { tree* t; // change in place if possible fsi = (FREE_SPACE_INFO*)tp.item->data; fsi->count = fsi_count; fsi->flags = 0; tp.tree->write = true; t = tp.tree; while (t) { if (t->paritem && t->paritem->ignore) { t->paritem->ignore = false; t->parent->header.num_items++; t->parent->size += sizeof(internal_node); } t->header.generation = Vcb->superblock.generation; t = t->parent; } return STATUS_SUCCESS; } else delete_tree_item(Vcb, &tp); } // insert FREE_SPACE_INFO fsi = ExAllocatePoolWithTag(PagedPool, sizeof(FREE_SPACE_INFO), ALLOC_TAG); if (!fsi) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } fsi->count = fsi_count; fsi->flags = 0; Status = insert_tree_item(Vcb, Vcb->space_root, c->offset, TYPE_FREE_SPACE_INFO, c->chunk_item->size, fsi, sizeof(*fsi), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } NTSTATUS update_chunk_caches(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) { LIST_ENTRY *le, batchlist; NTSTATUS Status; chunk* c; LARGE_INTEGER time; BTRFS_TIME now; KeQuerySystemTime(&time); win_time_to_unix(time, &now); InitializeListHead(&batchlist); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { c = CONTAINING_RECORD(le, chunk, list_entry); if (c->space_changed && c->chunk_item->size >= 0x6400000) { // 100MB acquire_chunk_lock(c, Vcb); Status = update_chunk_cache(Vcb, c, &now, &batchlist, Irp, rollback); release_chunk_lock(c, Vcb); if (!NT_SUCCESS(Status)) { ERR("update_chunk_cache(%I64x) returned %08lx\n", c->offset, Status); clear_batch_list(Vcb, &batchlist); return Status; } } le = le->Flink; } Status = commit_batch_list(Vcb, &batchlist, Irp); if (!NT_SUCCESS(Status)) { ERR("commit_batch_list returned %08lx\n", Status); return Status; } le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { c = CONTAINING_RECORD(le, chunk, list_entry); if (c->changed && (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6)) { ExAcquireResourceExclusiveLite(&c->partial_stripes_lock, true); while (!IsListEmpty(&c->partial_stripes)) { partial_stripe* ps = CONTAINING_RECORD(RemoveHeadList(&c->partial_stripes), partial_stripe, list_entry); Status = flush_partial_stripe(Vcb, c, ps); if (ps->bmparr) ExFreePool(ps->bmparr); ExFreePool(ps); if (!NT_SUCCESS(Status)) { ERR("flush_partial_stripe returned %08lx\n", Status); ExReleaseResourceLite(&c->partial_stripes_lock); return Status; } } ExReleaseResourceLite(&c->partial_stripes_lock); } le = le->Flink; } return STATUS_SUCCESS; } NTSTATUS update_chunk_caches_tree(device_extension* Vcb, PIRP Irp) { LIST_ENTRY *le; NTSTATUS Status; chunk* c; Vcb->superblock.compat_ro_flags |= BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID; ExAcquireResourceSharedLite(&Vcb->chunk_lock, true); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { c = CONTAINING_RECORD(le, chunk, list_entry); if (c->space_changed) { acquire_chunk_lock(c, Vcb); Status = update_chunk_cache_tree(Vcb, c, Irp); release_chunk_lock(c, Vcb); if (!NT_SUCCESS(Status)) { ERR("update_chunk_cache_tree(%I64x) returned %08lx\n", c->offset, Status); ExReleaseResourceLite(&Vcb->chunk_lock); return Status; } } le = le->Flink; } ExReleaseResourceLite(&Vcb->chunk_lock); return STATUS_SUCCESS; } void space_list_add(chunk* c, uint64_t address, uint64_t length, LIST_ENTRY* rollback) { TRACE("(%p, %I64x, %I64x, %p)\n", c, address, length, rollback); c->changed = true; c->space_changed = true; space_list_add2(&c->deleting, NULL, address, length, c, rollback); } void space_list_subtract2(LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t address, uint64_t length, chunk* c, LIST_ENTRY* rollback) { LIST_ENTRY *le, *le2; space *s, *s2; if (IsListEmpty(list)) return; le = list->Flink; while (le != list) { s2 = CONTAINING_RECORD(le, space, list_entry); le2 = le->Flink; if (s2->address >= address + length) return; if (s2->address >= address && s2->address + s2->size <= address + length) { // remove entry entirely if (rollback) add_rollback_space(rollback, false, list, list_size, s2->address, s2->size, c); RemoveEntryList(&s2->list_entry); if (list_size) RemoveEntryList(&s2->list_entry_size); ExFreePool(s2); } else if (address + length > s2->address && address + length < s2->address + s2->size) { if (address > s2->address) { // cut out hole if (rollback) add_rollback_space(rollback, false, list, list_size, address, length, c); s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG); if (!s) { ERR("out of memory\n"); return; } s->address = s2->address; s->size = address - s2->address; InsertHeadList(s2->list_entry.Blink, &s->list_entry); s2->size = s2->address + s2->size - address - length; s2->address = address + length; if (list_size) { RemoveEntryList(&s2->list_entry_size); order_space_entry(s2, list_size); order_space_entry(s, list_size); } return; } else { // remove start of entry if (rollback) add_rollback_space(rollback, false, list, list_size, s2->address, address + length - s2->address, c); s2->size -= address + length - s2->address; s2->address = address + length; if (list_size) { RemoveEntryList(&s2->list_entry_size); order_space_entry(s2, list_size); } } } else if (address > s2->address && address < s2->address + s2->size) { // remove end of entry if (rollback) add_rollback_space(rollback, false, list, list_size, address, s2->address + s2->size - address, c); s2->size = address - s2->address; if (list_size) { RemoveEntryList(&s2->list_entry_size); order_space_entry(s2, list_size); } } le = le2; } } void space_list_subtract(chunk* c, uint64_t address, uint64_t length, LIST_ENTRY* rollback) { c->changed = true; c->space_changed = true; space_list_subtract2(&c->space, &c->space_size, address, length, c, rollback); space_list_subtract2(&c->deleting, NULL, address, length, c, rollback); } ================================================ FILE: src/fsctl.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include "btrfsioctl.h" #include "crc32c.h" #include #include #include #ifndef FSCTL_CSV_CONTROL #define FSCTL_CSV_CONTROL CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 181, METHOD_BUFFERED, FILE_ANY_ACCESS) #endif #ifndef FSCTL_QUERY_VOLUME_CONTAINER_STATE #define FSCTL_QUERY_VOLUME_CONTAINER_STATE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 228, METHOD_BUFFERED, FILE_ANY_ACCESS) #endif #define DOTDOT ".." #define SEF_AVOID_PRIVILEGE_CHECK 0x08 // on MSDN but not in any header files(?) #define SEF_SACL_AUTO_INHERIT 0x02 extern LIST_ENTRY VcbList; extern ERESOURCE global_loading_lock; extern PDRIVER_OBJECT drvobj; extern tFsRtlCheckLockForOplockRequest fFsRtlCheckLockForOplockRequest; extern tFsRtlAreThereCurrentOrInProgressFileLocks fFsRtlAreThereCurrentOrInProgressFileLocks; static void mark_subvol_dirty(device_extension* Vcb, root* r); static NTSTATUS get_file_ids(PFILE_OBJECT FileObject, void* data, ULONG length) { btrfs_get_file_ids* bgfi; fcb* fcb; if (length < sizeof(btrfs_get_file_ids)) return STATUS_BUFFER_OVERFLOW; if (!FileObject) return STATUS_INVALID_PARAMETER; fcb = FileObject->FsContext; if (!fcb) return STATUS_INVALID_PARAMETER; bgfi = data; bgfi->subvol = fcb->subvol->id; bgfi->inode = fcb->inode; bgfi->top = fcb->Vcb->root_fileref->fcb == fcb ? true : false; return STATUS_SUCCESS; } static void get_uuid(BTRFS_UUID* uuid) { LARGE_INTEGER seed; uint8_t i; seed = KeQueryPerformanceCounter(NULL); for (i = 0; i < 16; i+=2) { ULONG rand = RtlRandomEx(&seed.LowPart); uuid->uuid[i] = (rand & 0xff00) >> 8; uuid->uuid[i+1] = rand & 0xff; } } static NTSTATUS snapshot_tree_copy(device_extension* Vcb, uint64_t addr, root* subvol, uint64_t* newaddr, PIRP Irp, LIST_ENTRY* rollback) { uint8_t* buf; NTSTATUS Status; write_data_context wtc; LIST_ENTRY* le; tree t; tree_header* th; chunk* c; buf = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size, ALLOC_TAG); if (!buf) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } wtc.parity1 = wtc.parity2 = wtc.scratch = NULL; wtc.mdl = wtc.parity1_mdl = wtc.parity2_mdl = NULL; Status = read_data(Vcb, addr, Vcb->superblock.node_size, NULL, true, buf, NULL, NULL, Irp, 0, false, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned %08lx\n", Status); goto end; } th = (tree_header*)buf; RtlZeroMemory(&t, sizeof(tree)); t.root = subvol; t.header.level = th->level; t.header.tree_id = t.root->id; Status = get_tree_new_address(Vcb, &t, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("get_tree_new_address returned %08lx\n", Status); goto end; } if (!t.has_new_address) { ERR("tree new address not set\n"); Status = STATUS_INTERNAL_ERROR; goto end; } c = get_chunk_from_address(Vcb, t.new_address); if (c) c->used += Vcb->superblock.node_size; else { ERR("could not find chunk for address %I64x\n", t.new_address); Status = STATUS_INTERNAL_ERROR; goto end; } th->address = t.new_address; th->tree_id = subvol->id; th->generation = Vcb->superblock.generation; th->fs_uuid = Vcb->superblock.metadata_uuid; if (th->level == 0) { uint32_t i; leaf_node* ln = (leaf_node*)&th[1]; for (i = 0; i < th->num_items; i++) { 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)) { EXTENT_DATA* ed = (EXTENT_DATA*)(((uint8_t*)&th[1]) + ln[i].offset); if ((ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) && ln[i].size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)&ed->data[0]; if (ed2->size != 0) { // not sparse 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); if (!NT_SUCCESS(Status)) { ERR("increase_extent_refcount_data returned %08lx\n", Status); goto end; } } } } } } else { uint32_t i; internal_node* in = (internal_node*)&th[1]; for (i = 0; i < th->num_items; i++) { TREE_BLOCK_REF tbr; tbr.offset = subvol->id; Status = increase_extent_refcount(Vcb, in[i].address, Vcb->superblock.node_size, TYPE_TREE_BLOCK_REF, &tbr, NULL, th->level - 1, Irp); if (!NT_SUCCESS(Status)) { ERR("increase_extent_refcount returned %08lx\n", Status); goto end; } } } calc_tree_checksum(Vcb, th); KeInitializeEvent(&wtc.Event, NotificationEvent, false); InitializeListHead(&wtc.stripes); wtc.stripes_left = 0; Status = write_data(Vcb, t.new_address, buf, Vcb->superblock.node_size, &wtc, NULL, NULL, false, 0, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("write_data returned %08lx\n", Status); goto end; } if (wtc.stripes.Flink != &wtc.stripes) { bool need_wait = false; // launch writes and wait le = wtc.stripes.Flink; while (le != &wtc.stripes) { write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry); if (stripe->status != WriteDataStatus_Ignore) { need_wait = true; IoCallDriver(stripe->device->devobj, stripe->Irp); } le = le->Flink; } if (need_wait) KeWaitForSingleObject(&wtc.Event, Executive, KernelMode, false, NULL); le = wtc.stripes.Flink; while (le != &wtc.stripes) { write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry); if (stripe->status != WriteDataStatus_Ignore && !NT_SUCCESS(stripe->iosb.Status)) { Status = stripe->iosb.Status; log_device_error(Vcb, stripe->device, BTRFS_DEV_STAT_WRITE_ERRORS); break; } le = le->Flink; } free_write_data_stripes(&wtc); buf = NULL; } if (NT_SUCCESS(Status)) *newaddr = t.new_address; end: if (buf) ExFreePool(buf); return Status; } void flush_subvol_fcbs(root* subvol) { LIST_ENTRY* le = subvol->fcbs.Flink; if (IsListEmpty(&subvol->fcbs)) return; while (le != &subvol->fcbs) { struct _fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry); IO_STATUS_BLOCK iosb; if (fcb->type != BTRFS_TYPE_DIRECTORY && !fcb->deleted) CcFlushCache(&fcb->nonpaged->segment_object, NULL, 0, &iosb); le = le->Flink; } } static NTSTATUS do_create_snapshot(device_extension* Vcb, PFILE_OBJECT parent, fcb* subvol_fcb, PANSI_STRING utf8, PUNICODE_STRING name, bool readonly, PIRP Irp) { LIST_ENTRY rollback; uint64_t id; NTSTATUS Status; root *r, *subvol = subvol_fcb->subvol; KEY searchkey; traverse_ptr tp; uint64_t address, *root_num; LARGE_INTEGER time; BTRFS_TIME now; fcb* fcb = parent->FsContext; ccb* ccb = parent->FsContext2; LIST_ENTRY* le; file_ref *fileref, *fr; dir_child* dc = NULL; if (!ccb) { ERR("error - ccb was NULL\n"); return STATUS_INTERNAL_ERROR; } if (!(ccb->access & FILE_ADD_SUBDIRECTORY)) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } fileref = ccb->fileref; if (fileref->fcb == Vcb->dummy_fcb) return STATUS_ACCESS_DENIED; // flush open files on this subvol flush_subvol_fcbs(subvol); // flush metadata if (Vcb->need_write) Status = do_write(Vcb, Irp); else Status = STATUS_SUCCESS; free_trees(Vcb); if (!NT_SUCCESS(Status)) { ERR("do_write returned %08lx\n", Status); return Status; } InitializeListHead(&rollback); // create new root id = InterlockedIncrement64(&Vcb->root_root->lastinode); Status = create_root(Vcb, id, &r, true, Vcb->superblock.generation, Irp); if (!NT_SUCCESS(Status)) { ERR("create_root returned %08lx\n", Status); goto end; } r->lastinode = subvol->lastinode; if (!Vcb->uuid_root) { root* uuid_root; TRACE("uuid root doesn't exist, creating it\n"); Status = create_root(Vcb, BTRFS_ROOT_UUID, &uuid_root, false, 0, Irp); if (!NT_SUCCESS(Status)) { ERR("create_root returned %08lx\n", Status); goto end; } Vcb->uuid_root = uuid_root; } root_num = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t), ALLOC_TAG); if (!root_num) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } tp.tree = NULL; do { get_uuid(&r->root_item.uuid); RtlCopyMemory(&searchkey.obj_id, &r->root_item.uuid, sizeof(uint64_t)); searchkey.obj_type = TYPE_SUBVOL_UUID; RtlCopyMemory(&searchkey.offset, &r->root_item.uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t)); Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp); } while (NT_SUCCESS(Status) && !keycmp(searchkey, tp.item->key)); *root_num = r->id; Status = insert_tree_item(Vcb, Vcb->uuid_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, root_num, sizeof(uint64_t), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(root_num); goto end; } searchkey.obj_id = r->id; searchkey.obj_type = TYPE_ROOT_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); goto end; } Status = snapshot_tree_copy(Vcb, subvol->root_item.block_number, r, &address, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("snapshot_tree_copy returned %08lx\n", Status); goto end; } KeQuerySystemTime(&time); win_time_to_unix(time, &now); r->root_item.inode.generation = 1; r->root_item.inode.st_size = 3; r->root_item.inode.st_blocks = subvol->root_item.inode.st_blocks; r->root_item.inode.st_nlink = 1; r->root_item.inode.st_mode = __S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; // 40755 r->root_item.inode.flags = 0x80000000; // FIXME - find out what these mean r->root_item.inode.flags_ro = 0xffffffff; // FIXME - find out what these mean r->root_item.generation = Vcb->superblock.generation; r->root_item.objid = subvol->root_item.objid; r->root_item.block_number = address; r->root_item.bytes_used = subvol->root_item.bytes_used; r->root_item.last_snapshot_generation = Vcb->superblock.generation; r->root_item.root_level = subvol->root_item.root_level; r->root_item.generation2 = Vcb->superblock.generation; r->root_item.parent_uuid = subvol->root_item.uuid; r->root_item.ctransid = subvol->root_item.ctransid; r->root_item.otransid = Vcb->superblock.generation; r->root_item.ctime = subvol->root_item.ctime; r->root_item.otime = now; if (readonly) r->root_item.flags |= BTRFS_SUBVOL_READONLY; r->treeholder.address = address; // FIXME - do we need to copy over the send and receive fields too? if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { ERR("error - could not find ROOT_ITEM for subvol %I64x\n", r->id); Status = STATUS_INTERNAL_ERROR; goto end; } RtlCopyMemory(tp.item->data, &r->root_item, sizeof(ROOT_ITEM)); // update ROOT_ITEM of original subvol subvol->root_item.last_snapshot_generation = Vcb->superblock.generation; mark_subvol_dirty(Vcb, subvol); // create fileref for entry in other subvolume fr = create_fileref(Vcb); if (!fr) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } Status = open_fcb(Vcb, r, r->root_item.objid, BTRFS_TYPE_DIRECTORY, utf8, false, fcb, &fr->fcb, PagedPool, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fcb returned %08lx\n", Status); free_fileref(fr); goto end; } fr->parent = fileref; Status = add_dir_child(fileref->fcb, r->id, true, utf8, name, BTRFS_TYPE_DIRECTORY, &dc); if (!NT_SUCCESS(Status)) WARN("add_dir_child returned %08lx\n", Status); fr->dc = dc; dc->fileref = fr; ExAcquireResourceExclusiveLite(&fileref->fcb->nonpaged->dir_children_lock, true); InsertTailList(&fileref->children, &fr->list_entry); ExReleaseResourceLite(&fileref->fcb->nonpaged->dir_children_lock); increase_fileref_refcount(fileref); fr->created = true; mark_fileref_dirty(fr); if (fr->fcb->type == BTRFS_TYPE_DIRECTORY) fr->fcb->fileref = fr; fr->fcb->subvol->parent = fileref->fcb->subvol->id; free_fileref(fr); // change fcb's INODE_ITEM fcb->inode_item.transid = Vcb->superblock.generation; fcb->inode_item.sequence++; fcb->inode_item.st_size += utf8->Length * 2; if (!ccb->user_set_change_time) fcb->inode_item.st_ctime = now; if (!ccb->user_set_write_time) fcb->inode_item.st_mtime = now; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); fcb->subvol->root_item.ctime = now; fcb->subvol->root_item.ctransid = Vcb->superblock.generation; send_notification_fileref(fr, FILE_NOTIFY_CHANGE_DIR_NAME, FILE_ACTION_ADDED, NULL); send_notification_fileref(fr->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL); le = subvol->fcbs.Flink; while (le != &subvol->fcbs) { struct _fcb* fcb2 = CONTAINING_RECORD(le, struct _fcb, list_entry); LIST_ENTRY* le2 = fcb2->extents.Flink; while (le2 != &fcb2->extents) { extent* ext = CONTAINING_RECORD(le2, extent, list_entry); if (!ext->ignore) ext->unique = false; le2 = le2->Flink; } le = le->Flink; } Status = do_write(Vcb, Irp); free_trees(Vcb); if (!NT_SUCCESS(Status)) ERR("do_write returned %08lx\n", Status); end: if (NT_SUCCESS(Status)) clear_rollback(&rollback); else do_rollback(Vcb, &rollback); return Status; } static NTSTATUS create_snapshot(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG length, PIRP Irp) { PFILE_OBJECT subvol_obj; NTSTATUS Status; btrfs_create_snapshot* bcs = data; fcb* subvol_fcb; HANDLE subvolh; bool readonly, posix; ANSI_STRING utf8; UNICODE_STRING nameus; ULONG len; fcb* fcb; ccb* ccb; file_ref *fileref, *fr2; #if defined(_WIN64) if (IoIs32bitProcess(Irp)) { btrfs_create_snapshot32* bcs32 = data; if (length < offsetof(btrfs_create_snapshot32, name)) return STATUS_INVALID_PARAMETER; if (length < offsetof(btrfs_create_snapshot32, name) + bcs32->namelen) return STATUS_INVALID_PARAMETER; subvolh = Handle32ToHandle(bcs32->subvol); nameus.Buffer = bcs32->name; nameus.Length = nameus.MaximumLength = bcs32->namelen; readonly = bcs32->readonly; posix = bcs32->posix; } else { #endif if (length < offsetof(btrfs_create_snapshot, name)) return STATUS_INVALID_PARAMETER; if (length < offsetof(btrfs_create_snapshot, name) + bcs->namelen) return STATUS_INVALID_PARAMETER; subvolh = bcs->subvol; nameus.Buffer = bcs->name; nameus.Length = nameus.MaximumLength = bcs->namelen; readonly = bcs->readonly; posix = bcs->posix; #if defined(_WIN64) } #endif if (!subvolh) return STATUS_INVALID_PARAMETER; if (!FileObject || !FileObject->FsContext) return STATUS_INVALID_PARAMETER; fcb = FileObject->FsContext; ccb = FileObject->FsContext2; if (!fcb || !ccb || fcb->type != BTRFS_TYPE_DIRECTORY) return STATUS_INVALID_PARAMETER; fileref = ccb->fileref; if (!fileref) { ERR("fileref was NULL\n"); return STATUS_INVALID_PARAMETER; } if (!(ccb->access & FILE_ADD_SUBDIRECTORY)) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } if (Vcb->readonly) return STATUS_MEDIA_WRITE_PROTECTED; if (is_subvol_readonly(fcb->subvol, Irp)) return STATUS_ACCESS_DENIED; Status = check_file_name_valid(&nameus, posix, false); if (!NT_SUCCESS(Status)) return Status; utf8.Buffer = NULL; Status = utf16_to_utf8(NULL, 0, &len, nameus.Buffer, nameus.Length); if (!NT_SUCCESS(Status)) { ERR("utf16_to_utf8 failed with error %08lx\n", Status); return Status; } if (len == 0) { ERR("utf16_to_utf8 returned a length of 0\n"); return STATUS_INTERNAL_ERROR; } if (len > 0xffff) { ERR("len was too long\n"); return STATUS_INVALID_PARAMETER; } utf8.MaximumLength = utf8.Length = (USHORT)len; utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.Length, ALLOC_TAG); if (!utf8.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } Status = utf16_to_utf8(utf8.Buffer, len, &len, nameus.Buffer, nameus.Length); if (!NT_SUCCESS(Status)) { ERR("utf16_to_utf8 failed with error %08lx\n", Status); goto end2; } ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); // no need for fcb_lock as we have tree_lock exclusively Status = open_fileref(fcb->Vcb, &fr2, &nameus, fileref, false, NULL, NULL, PagedPool, ccb->case_sensitive || posix, Irp); if (NT_SUCCESS(Status)) { if (!fr2->deleted) { WARN("file already exists\n"); free_fileref(fr2); Status = STATUS_OBJECT_NAME_COLLISION; goto end3; } else free_fileref(fr2); } else if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_NOT_FOUND) { ERR("open_fileref returned %08lx\n", Status); goto end3; } Status = ObReferenceObjectByHandle(subvolh, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&subvol_obj, NULL); if (!NT_SUCCESS(Status)) { ERR("ObReferenceObjectByHandle returned %08lx\n", Status); goto end3; } if (subvol_obj->DeviceObject != FileObject->DeviceObject) { Status = STATUS_INVALID_PARAMETER; goto end; } subvol_fcb = subvol_obj->FsContext; if (!subvol_fcb) { Status = STATUS_INVALID_PARAMETER; goto end; } if (subvol_fcb->inode != subvol_fcb->subvol->root_item.objid) { WARN("handle inode was %I64x, expected %I64x\n", subvol_fcb->inode, subvol_fcb->subvol->root_item.objid); Status = STATUS_INVALID_PARAMETER; goto end; } ccb = subvol_obj->FsContext2; if (!ccb) { Status = STATUS_INVALID_PARAMETER; goto end; } if (!(ccb->access & FILE_TRAVERSE)) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; goto end; } if (fcb == Vcb->dummy_fcb) { Status = STATUS_ACCESS_DENIED; goto end; } // clear unique flag on extents of open files in subvol if (!IsListEmpty(&subvol_fcb->subvol->fcbs)) { LIST_ENTRY* le = subvol_fcb->subvol->fcbs.Flink; while (le != &subvol_fcb->subvol->fcbs) { struct _fcb* openfcb = CONTAINING_RECORD(le, struct _fcb, list_entry); LIST_ENTRY* le2; le2 = openfcb->extents.Flink; while (le2 != &openfcb->extents) { extent* ext = CONTAINING_RECORD(le2, extent, list_entry); ext->unique = false; le2 = le2->Flink; } le = le->Flink; } } Status = do_create_snapshot(Vcb, FileObject, subvol_fcb, &utf8, &nameus, readonly, Irp); if (NT_SUCCESS(Status)) { file_ref* fr; Status = open_fileref(Vcb, &fr, &nameus, fileref, false, NULL, NULL, PagedPool, false, Irp); if (!NT_SUCCESS(Status)) { ERR("open_fileref returned %08lx\n", Status); Status = STATUS_SUCCESS; } else { send_notification_fileref(fr, FILE_NOTIFY_CHANGE_DIR_NAME, FILE_ACTION_ADDED, NULL); free_fileref(fr); } } end: ObDereferenceObject(subvol_obj); end3: ExReleaseResourceLite(&Vcb->tree_lock); end2: ExFreePool(utf8.Buffer); return Status; } static NTSTATUS create_subvol(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, PIRP Irp) { btrfs_create_subvol* bcs; fcb *fcb, *rootfcb = NULL; ccb* ccb; file_ref* fileref; NTSTATUS Status; uint64_t id; root* r = NULL; LARGE_INTEGER time; BTRFS_TIME now; ULONG len; uint16_t irsize; UNICODE_STRING nameus; ANSI_STRING utf8; INODE_REF* ir; KEY searchkey; traverse_ptr tp; SECURITY_SUBJECT_CONTEXT subjcont; PSID owner; BOOLEAN defaulted; uint64_t* root_num; file_ref *fr = NULL, *fr2; dir_child* dc = NULL; fcb = FileObject->FsContext; if (!fcb) { ERR("error - fcb was NULL\n"); return STATUS_INTERNAL_ERROR; } ccb = FileObject->FsContext2; if (!ccb) { ERR("error - ccb was NULL\n"); return STATUS_INTERNAL_ERROR; } fileref = ccb->fileref; if (fcb->type != BTRFS_TYPE_DIRECTORY) { ERR("parent FCB was not a directory\n"); return STATUS_NOT_A_DIRECTORY; } if (!fileref) { ERR("fileref was NULL\n"); return STATUS_INVALID_PARAMETER; } if (fileref->deleted || fcb->deleted) { ERR("parent has been deleted\n"); return STATUS_FILE_DELETED; } if (!(ccb->access & FILE_ADD_SUBDIRECTORY)) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } if (Vcb->readonly) return STATUS_MEDIA_WRITE_PROTECTED; if (is_subvol_readonly(fcb->subvol, Irp)) return STATUS_ACCESS_DENIED; if (fcb == Vcb->dummy_fcb) return STATUS_ACCESS_DENIED; if (!data || datalen < sizeof(btrfs_create_subvol)) return STATUS_INVALID_PARAMETER; bcs = (btrfs_create_subvol*)data; if (offsetof(btrfs_create_subvol, name[0]) + bcs->namelen > datalen) return STATUS_INVALID_PARAMETER; nameus.Length = nameus.MaximumLength = bcs->namelen; nameus.Buffer = bcs->name; Status = check_file_name_valid(&nameus, bcs->posix, false); if (!NT_SUCCESS(Status)) return Status; utf8.Buffer = NULL; Status = utf16_to_utf8(NULL, 0, &len, nameus.Buffer, nameus.Length); if (!NT_SUCCESS(Status)) { ERR("utf16_to_utf8 failed with error %08lx\n", Status); return Status; } if (len == 0) { ERR("utf16_to_utf8 returned a length of 0\n"); return STATUS_INTERNAL_ERROR; } if (len > 0xffff) { ERR("len was too long\n"); return STATUS_INVALID_PARAMETER; } utf8.MaximumLength = utf8.Length = (USHORT)len; utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.Length, ALLOC_TAG); if (!utf8.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } Status = utf16_to_utf8(utf8.Buffer, len, &len, nameus.Buffer, nameus.Length); if (!NT_SUCCESS(Status)) { ERR("utf16_to_utf8 failed with error %08lx\n", Status); goto end2; } ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); KeQuerySystemTime(&time); win_time_to_unix(time, &now); // no need for fcb_lock as we have tree_lock exclusively Status = open_fileref(fcb->Vcb, &fr2, &nameus, fileref, false, NULL, NULL, PagedPool, ccb->case_sensitive || bcs->posix, Irp); if (NT_SUCCESS(Status)) { if (!fr2->deleted) { WARN("file already exists\n"); free_fileref(fr2); Status = STATUS_OBJECT_NAME_COLLISION; goto end; } else free_fileref(fr2); } else if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_NOT_FOUND) { ERR("open_fileref returned %08lx\n", Status); goto end; } id = InterlockedIncrement64(&Vcb->root_root->lastinode); Status = create_root(Vcb, id, &r, false, 0, Irp); if (!NT_SUCCESS(Status)) { ERR("create_root returned %08lx\n", Status); goto end; } TRACE("created root %I64x\n", id); if (!Vcb->uuid_root) { root* uuid_root; TRACE("uuid root doesn't exist, creating it\n"); Status = create_root(Vcb, BTRFS_ROOT_UUID, &uuid_root, false, 0, Irp); if (!NT_SUCCESS(Status)) { ERR("create_root returned %08lx\n", Status); goto end; } Vcb->uuid_root = uuid_root; } root_num = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t), ALLOC_TAG); if (!root_num) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } tp.tree = NULL; do { get_uuid(&r->root_item.uuid); RtlCopyMemory(&searchkey.obj_id, &r->root_item.uuid, sizeof(uint64_t)); searchkey.obj_type = TYPE_SUBVOL_UUID; RtlCopyMemory(&searchkey.offset, &r->root_item.uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t)); Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp); } while (NT_SUCCESS(Status) && !keycmp(searchkey, tp.item->key)); *root_num = r->id; Status = insert_tree_item(Vcb, Vcb->uuid_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, root_num, sizeof(uint64_t), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(root_num); goto end; } r->root_item.inode.generation = 1; r->root_item.inode.st_size = 3; r->root_item.inode.st_blocks = Vcb->superblock.node_size; r->root_item.inode.st_nlink = 1; r->root_item.inode.st_mode = __S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; // 40755 r->root_item.inode.flags = 0x80000000; // FIXME - find out what these mean r->root_item.inode.flags_ro = 0xffffffff; // FIXME - find out what these mean if (bcs->readonly) r->root_item.flags |= BTRFS_SUBVOL_READONLY; r->root_item.objid = SUBVOL_ROOT_INODE; r->root_item.bytes_used = Vcb->superblock.node_size; r->root_item.ctransid = Vcb->superblock.generation; r->root_item.otransid = Vcb->superblock.generation; r->root_item.ctime = now; r->root_item.otime = now; // add .. inode to new subvol rootfcb = create_fcb(Vcb, PagedPool); if (!rootfcb) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } rootfcb->Vcb = Vcb; rootfcb->subvol = r; rootfcb->type = BTRFS_TYPE_DIRECTORY; rootfcb->inode = SUBVOL_ROOT_INODE; rootfcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&rootfcb->inode, sizeof(uint64_t)); // FIXME - we can hardcode this rootfcb->inode_item.generation = Vcb->superblock.generation; rootfcb->inode_item.transid = Vcb->superblock.generation; rootfcb->inode_item.st_nlink = 1; rootfcb->inode_item.st_mode = __S_IFDIR | inherit_mode(fileref->fcb, true); rootfcb->inode_item.st_atime = rootfcb->inode_item.st_ctime = rootfcb->inode_item.st_mtime = rootfcb->inode_item.otime = now; rootfcb->inode_item.st_gid = GID_NOBODY; rootfcb->atts = get_file_attributes(Vcb, rootfcb->subvol, rootfcb->inode, rootfcb->type, false, true, Irp); if (r->root_item.flags & BTRFS_SUBVOL_READONLY) rootfcb->atts |= FILE_ATTRIBUTE_READONLY; SeCaptureSubjectContext(&subjcont); Status = SeAssignSecurity(fcb->sd, NULL, (void**)&rootfcb->sd, true, &subjcont, IoGetFileObjectGenericMapping(), PagedPool); if (!NT_SUCCESS(Status)) { ERR("SeAssignSecurity returned %08lx\n", Status); goto end; } if (!rootfcb->sd) { ERR("SeAssignSecurity returned NULL security descriptor\n"); Status = STATUS_INTERNAL_ERROR; goto end; } Status = RtlGetOwnerSecurityDescriptor(rootfcb->sd, &owner, &defaulted); if (!NT_SUCCESS(Status)) { ERR("RtlGetOwnerSecurityDescriptor returned %08lx\n", Status); rootfcb->inode_item.st_uid = UID_NOBODY; rootfcb->sd_dirty = true; } else { rootfcb->inode_item.st_uid = sid_to_uid(owner); rootfcb->sd_dirty = rootfcb->inode_item.st_uid == UID_NOBODY; } find_gid(rootfcb, fileref->fcb, &subjcont); rootfcb->inode_item_changed = true; acquire_fcb_lock_exclusive(Vcb); add_fcb_to_subvol(rootfcb); InsertTailList(&Vcb->all_fcbs, &rootfcb->list_entry_all); r->fcbs_version++; release_fcb_lock(Vcb); rootfcb->Header.IsFastIoPossible = fast_io_possible(rootfcb); rootfcb->Header.AllocationSize.QuadPart = 0; rootfcb->Header.FileSize.QuadPart = 0; rootfcb->Header.ValidDataLength.QuadPart = 0; rootfcb->created = true; if (fileref->fcb->inode_item.flags & BTRFS_INODE_COMPRESS) rootfcb->inode_item.flags |= BTRFS_INODE_COMPRESS; rootfcb->prop_compression = fileref->fcb->prop_compression; rootfcb->prop_compression_changed = rootfcb->prop_compression != PropCompression_None; r->lastinode = rootfcb->inode; // add INODE_REF irsize = (uint16_t)(offsetof(INODE_REF, name[0]) + sizeof(DOTDOT) - 1); ir = ExAllocatePoolWithTag(PagedPool, irsize, ALLOC_TAG); if (!ir) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } ir->index = 0; ir->n = sizeof(DOTDOT) - 1; RtlCopyMemory(ir->name, DOTDOT, ir->n); Status = insert_tree_item(Vcb, r, r->root_item.objid, TYPE_INODE_REF, r->root_item.objid, ir, irsize, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(ir); goto end; } // create fileref for entry in other subvolume fr = create_fileref(Vcb); if (!fr) { ERR("out of memory\n"); reap_fcb(rootfcb); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } fr->fcb = rootfcb; mark_fcb_dirty(rootfcb); fr->parent = fileref; Status = add_dir_child(fileref->fcb, r->id, true, &utf8, &nameus, BTRFS_TYPE_DIRECTORY, &dc); if (!NT_SUCCESS(Status)) WARN("add_dir_child returned %08lx\n", Status); fr->dc = dc; dc->fileref = fr; fr->fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG); if (!fr->fcb->hash_ptrs) { ERR("out of memory\n"); free_fileref(fr); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlZeroMemory(fr->fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256); fr->fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG); if (!fr->fcb->hash_ptrs_uc) { ERR("out of memory\n"); free_fileref(fr); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlZeroMemory(fr->fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256); ExAcquireResourceExclusiveLite(&fileref->fcb->nonpaged->dir_children_lock, true); InsertTailList(&fileref->children, &fr->list_entry); ExReleaseResourceLite(&fileref->fcb->nonpaged->dir_children_lock); increase_fileref_refcount(fileref); if (fr->fcb->type == BTRFS_TYPE_DIRECTORY) fr->fcb->fileref = fr; fr->created = true; mark_fileref_dirty(fr); // change fcb->subvol's ROOT_ITEM fcb->subvol->root_item.ctransid = Vcb->superblock.generation; fcb->subvol->root_item.ctime = now; // change fcb's INODE_ITEM fcb->inode_item.transid = Vcb->superblock.generation; fcb->inode_item.st_size += utf8.Length * 2; fcb->inode_item.sequence++; if (!ccb->user_set_change_time) fcb->inode_item.st_ctime = now; if (!ccb->user_set_write_time) fcb->inode_item.st_mtime = now; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); fr->fcb->subvol->parent = fcb->subvol->id; Status = STATUS_SUCCESS; end: if (!NT_SUCCESS(Status)) { if (fr) { fr->deleted = true; mark_fileref_dirty(fr); } else if (rootfcb) { rootfcb->deleted = true; mark_fcb_dirty(rootfcb); } if (r) { RemoveEntryList(&r->list_entry); InsertTailList(&Vcb->drop_roots, &r->list_entry); } } ExReleaseResourceLite(&Vcb->tree_lock); if (NT_SUCCESS(Status)) { send_notification_fileref(fr, FILE_NOTIFY_CHANGE_DIR_NAME, FILE_ACTION_ADDED, NULL); send_notification_fileref(fr->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL); } end2: if (fr) free_fileref(fr); return Status; } static NTSTATUS get_inode_info(PFILE_OBJECT FileObject, void* data, ULONG length) { btrfs_inode_info* bii = data; fcb* fcb; ccb* ccb; bool old_style; if (length < offsetof(btrfs_inode_info, disk_size_zstd)) return STATUS_BUFFER_OVERFLOW; if (!FileObject) return STATUS_INVALID_PARAMETER; fcb = FileObject->FsContext; if (!fcb) return STATUS_INVALID_PARAMETER; ccb = FileObject->FsContext2; if (!ccb) return STATUS_INVALID_PARAMETER; if (!(ccb->access & FILE_READ_ATTRIBUTES)) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } if (fcb->ads) fcb = ccb->fileref->parent->fcb; old_style = length < offsetof(btrfs_inode_info, sparse_size) + sizeof(((btrfs_inode_info*)NULL)->sparse_size); ExAcquireResourceSharedLite(fcb->Header.Resource, true); bii->subvol = fcb->subvol->id; bii->inode = fcb->inode; bii->top = fcb->Vcb->root_fileref->fcb == fcb ? true : false; bii->type = fcb->type; bii->st_uid = fcb->inode_item.st_uid; bii->st_gid = fcb->inode_item.st_gid; bii->st_mode = fcb->inode_item.st_mode; if (fcb->inode_item.st_rdev == 0) bii->st_rdev = 0; else bii->st_rdev = makedev((fcb->inode_item.st_rdev & 0xFFFFFFFFFFF) >> 20, fcb->inode_item.st_rdev & 0xFFFFF); bii->flags = fcb->inode_item.flags; bii->inline_length = 0; bii->disk_size_uncompressed = 0; bii->disk_size_zlib = 0; bii->disk_size_lzo = 0; if (!old_style) { bii->disk_size_zstd = 0; bii->sparse_size = 0; } if (fcb->type != BTRFS_TYPE_DIRECTORY) { uint64_t last_end = 0; LIST_ENTRY* le; bool extents_inline = false; le = fcb->extents.Flink; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (!ext->ignore) { if (!old_style && ext->offset > last_end) bii->sparse_size += ext->offset - last_end; if (ext->extent_data.type == EXTENT_TYPE_INLINE) { bii->inline_length += ext->datalen - (uint16_t)offsetof(EXTENT_DATA, data[0]); last_end = ext->offset + ext->extent_data.decoded_size; extents_inline = true; } else { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data; // FIXME - compressed extents with a hole in them are counted more than once if (ed2->size != 0) { switch (ext->extent_data.compression) { case BTRFS_COMPRESSION_NONE: bii->disk_size_uncompressed += ed2->num_bytes; break; case BTRFS_COMPRESSION_ZLIB: bii->disk_size_zlib += ed2->size; break; case BTRFS_COMPRESSION_LZO: bii->disk_size_lzo += ed2->size; break; case BTRFS_COMPRESSION_ZSTD: if (!old_style) bii->disk_size_zstd += ed2->size; break; } } last_end = ext->offset + ed2->num_bytes; } } le = le->Flink; } if (!extents_inline && !old_style && sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size) > last_end) bii->sparse_size += sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size) - last_end; if (length >= offsetof(btrfs_inode_info, num_extents) + sizeof(((btrfs_inode_info*)NULL)->num_extents)) { EXTENT_DATA2* last_ed2 = NULL; le = fcb->extents.Flink; bii->num_extents = 0; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (!ext->ignore && ext->extent_data.type != EXTENT_TYPE_INLINE) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data; if (ed2->size != 0) { if (!last_ed2 || ed2->offset != last_ed2->offset + last_ed2->num_bytes) bii->num_extents++; last_ed2 = ed2; } else last_ed2 = NULL; } le = le->Flink; } } } switch (fcb->prop_compression) { case PropCompression_Zlib: bii->compression_type = BTRFS_COMPRESSION_ZLIB; break; case PropCompression_LZO: bii->compression_type = BTRFS_COMPRESSION_LZO; break; case PropCompression_ZSTD: bii->compression_type = BTRFS_COMPRESSION_ZSTD; break; default: bii->compression_type = BTRFS_COMPRESSION_ANY; break; } ExReleaseResourceLite(fcb->Header.Resource); return STATUS_SUCCESS; } static NTSTATUS set_inode_info(PFILE_OBJECT FileObject, void* data, ULONG length, PIRP Irp) { btrfs_set_inode_info* bsii = data; NTSTATUS Status; fcb* fcb; ccb* ccb; if (length < sizeof(btrfs_set_inode_info)) return STATUS_INVALID_PARAMETER; if (!FileObject) return STATUS_INVALID_PARAMETER; fcb = FileObject->FsContext; if (!fcb) return STATUS_INVALID_PARAMETER; ccb = FileObject->FsContext2; if (!ccb) return STATUS_INVALID_PARAMETER; if (bsii->flags_changed && !(ccb->access & FILE_WRITE_ATTRIBUTES)) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } if ((bsii->mode_changed || bsii->uid_changed || bsii->gid_changed) && !(ccb->access & WRITE_DAC)) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } if (bsii->compression_type_changed && bsii->compression_type > BTRFS_COMPRESSION_ZSTD) return STATUS_INVALID_PARAMETER; if (fcb->ads) fcb = ccb->fileref->parent->fcb; if (is_subvol_readonly(fcb->subvol, Irp)) { WARN("trying to change inode on readonly subvolume\n"); return STATUS_ACCESS_DENIED; } // nocow and compression are mutually exclusive if (bsii->flags_changed && bsii->flags & BTRFS_INODE_NODATACOW && bsii->flags & BTRFS_INODE_COMPRESS) return STATUS_INVALID_PARAMETER; ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (bsii->flags_changed) { if (fcb->type != BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0 && (bsii->flags & BTRFS_INODE_NODATACOW) != (fcb->inode_item.flags & BTRFS_INODE_NODATACOW)) { WARN("trying to change nocow flag on non-empty file\n"); Status = STATUS_INVALID_PARAMETER; goto end; } fcb->inode_item.flags = bsii->flags; if (fcb->inode_item.flags & BTRFS_INODE_NODATACOW) fcb->inode_item.flags |= BTRFS_INODE_NODATASUM; else fcb->inode_item.flags &= ~(uint64_t)BTRFS_INODE_NODATASUM; } if (bsii->mode_changed) { 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; if (ccb->access & WRITE_OWNER) allowed |= S_ISUID; fcb->inode_item.st_mode &= ~allowed; fcb->inode_item.st_mode |= bsii->st_mode & allowed; } if (bsii->uid_changed && fcb->inode_item.st_uid != bsii->st_uid) { fcb->inode_item.st_uid = bsii->st_uid; fcb->sd_dirty = true; fcb->sd_deleted = false; } if (bsii->gid_changed) fcb->inode_item.st_gid = bsii->st_gid; if (bsii->compression_type_changed) { switch (bsii->compression_type) { case BTRFS_COMPRESSION_ANY: fcb->prop_compression = PropCompression_None; break; case BTRFS_COMPRESSION_ZLIB: fcb->prop_compression = PropCompression_Zlib; break; case BTRFS_COMPRESSION_LZO: fcb->prop_compression = PropCompression_LZO; break; case BTRFS_COMPRESSION_ZSTD: fcb->prop_compression = PropCompression_ZSTD; break; } fcb->prop_compression_changed = true; } if (bsii->flags_changed || bsii->mode_changed || bsii->uid_changed || bsii->gid_changed || bsii->compression_type_changed) { fcb->inode_item_changed = true; mark_fcb_dirty(fcb); } Status = STATUS_SUCCESS; end: ExReleaseResourceLite(fcb->Header.Resource); return Status; } static NTSTATUS get_devices(device_extension* Vcb, void* data, ULONG length) { btrfs_device* dev = NULL; NTSTATUS Status; LIST_ENTRY* le; ExAcquireResourceSharedLite(&Vcb->tree_lock, true); le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev2 = CONTAINING_RECORD(le, device, list_entry); ULONG structlen; if (length < sizeof(btrfs_device) - sizeof(WCHAR)) { Status = STATUS_BUFFER_OVERFLOW; goto end; } if (!dev) dev = data; else { dev->next_entry = sizeof(btrfs_device) - sizeof(WCHAR) + dev->namelen; dev = (btrfs_device*)((uint8_t*)dev + dev->next_entry); } structlen = length - offsetof(btrfs_device, namelen); if (dev2->devobj) { Status = dev_ioctl(dev2->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &dev->namelen, structlen, true, NULL); if (!NT_SUCCESS(Status)) goto end; dev->missing = false; } else { dev->namelen = 0; dev->missing = true; } dev->next_entry = 0; dev->dev_id = dev2->devitem.dev_id; dev->readonly = (Vcb->readonly || dev2->readonly) ? true : false; dev->device_number = dev2->disk_num; dev->partition_number = dev2->part_num; dev->size = dev2->devitem.num_bytes; if (dev2->devobj) { GET_LENGTH_INFORMATION gli; Status = dev_ioctl(dev2->devobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli), true, NULL); if (!NT_SUCCESS(Status)) goto end; dev->max_size = gli.Length.QuadPart; } else dev->max_size = dev->size; RtlCopyMemory(dev->stats, dev2->stats, sizeof(uint64_t) * 5); length -= sizeof(btrfs_device) - sizeof(WCHAR) + dev->namelen; le = le->Flink; } end: ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS get_usage(device_extension* Vcb, void* data, ULONG length, PIRP Irp) { btrfs_usage* usage = (btrfs_usage*)data; btrfs_usage* lastbue = NULL; NTSTATUS Status; LIST_ENTRY* le; if (length < sizeof(btrfs_usage)) return STATUS_BUFFER_OVERFLOW; if (!Vcb->chunk_usage_found) { ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); if (!Vcb->chunk_usage_found) Status = find_chunk_usage(Vcb, Irp); else Status = STATUS_SUCCESS; ExReleaseResourceLite(&Vcb->tree_lock); if (!NT_SUCCESS(Status)) { ERR("find_chunk_usage returned %08lx\n", Status); return Status; } } length -= offsetof(btrfs_usage, devices); ExAcquireResourceSharedLite(&Vcb->chunk_lock, true); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { bool addnew = false; chunk* c = CONTAINING_RECORD(le, chunk, list_entry); if (!lastbue) // first entry addnew = true; else { btrfs_usage* bue = usage; addnew = true; while (true) { if (bue->type == c->chunk_item->type) { addnew = false; break; } if (bue->next_entry == 0) break; else bue = (btrfs_usage*)((uint8_t*)bue + bue->next_entry); } } if (addnew) { btrfs_usage* bue; LIST_ENTRY* le2; uint64_t factor; if (!lastbue) { bue = usage; } else { if (length < offsetof(btrfs_usage, devices)) { Status = STATUS_BUFFER_OVERFLOW; goto end; } length -= offsetof(btrfs_usage, devices); lastbue->next_entry = offsetof(btrfs_usage, devices) + (ULONG)(lastbue->num_devices * sizeof(btrfs_usage_device)); bue = (btrfs_usage*)((uint8_t*)lastbue + lastbue->next_entry); } bue->next_entry = 0; bue->type = c->chunk_item->type; bue->size = 0; bue->used = 0; bue->num_devices = 0; if (c->chunk_item->type & BLOCK_FLAG_RAID0) factor = c->chunk_item->num_stripes; else if (c->chunk_item->type & BLOCK_FLAG_RAID10) factor = c->chunk_item->num_stripes / c->chunk_item->sub_stripes; else if (c->chunk_item->type & BLOCK_FLAG_RAID5) factor = c->chunk_item->num_stripes - 1; else if (c->chunk_item->type & BLOCK_FLAG_RAID6) factor = c->chunk_item->num_stripes - 2; else factor = 1; le2 = le; while (le2 != &Vcb->chunks) { chunk* c2 = CONTAINING_RECORD(le2, chunk, list_entry); if (c2->chunk_item->type == c->chunk_item->type) { uint16_t i; CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c2->chunk_item[1]; uint64_t stripesize; bue->size += c2->chunk_item->size; bue->used += c2->used; stripesize = c2->chunk_item->size / factor; for (i = 0; i < c2->chunk_item->num_stripes; i++) { uint64_t j; bool found = false; for (j = 0; j < bue->num_devices; j++) { if (bue->devices[j].dev_id == cis[i].dev_id) { bue->devices[j].alloc += stripesize; found = true; break; } } if (!found) { if (length < sizeof(btrfs_usage_device)) { Status = STATUS_BUFFER_OVERFLOW; goto end; } length -= sizeof(btrfs_usage_device); bue->devices[bue->num_devices].dev_id = cis[i].dev_id; bue->devices[bue->num_devices].alloc = stripesize; bue->num_devices++; } } } le2 = le2->Flink; } lastbue = bue; } le = le->Flink; } Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&Vcb->chunk_lock); return Status; } static NTSTATUS is_volume_mounted(device_extension* Vcb, PIRP Irp) { NTSTATUS Status; ULONG cc; IO_STATUS_BLOCK iosb; bool verify = false; LIST_ENTRY* le; ExAcquireResourceSharedLite(&Vcb->tree_lock, true); le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (dev->devobj && dev->removable) { Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, &cc, sizeof(ULONG), false, &iosb); if (iosb.Information != sizeof(ULONG)) cc = 0; if (Status == STATUS_VERIFY_REQUIRED || (NT_SUCCESS(Status) && cc != dev->change_count)) { dev->devobj->Flags |= DO_VERIFY_VOLUME; verify = true; } if (NT_SUCCESS(Status) && iosb.Information == sizeof(ULONG)) dev->change_count = cc; if (!NT_SUCCESS(Status) || verify) { IoSetHardErrorOrVerifyDevice(Irp, dev->devobj); ExReleaseResourceLite(&Vcb->tree_lock); return verify ? STATUS_VERIFY_REQUIRED : Status; } } le = le->Flink; } ExReleaseResourceLite(&Vcb->tree_lock); return STATUS_SUCCESS; } static NTSTATUS fs_get_statistics(void* buffer, DWORD buflen, ULONG_PTR* retlen) { FILESYSTEM_STATISTICS* fss; WARN("STUB: FSCTL_FILESYSTEM_GET_STATISTICS\n"); // This is hideously wrong, but at least it stops SMB from breaking if (buflen < sizeof(FILESYSTEM_STATISTICS)) return STATUS_BUFFER_TOO_SMALL; fss = buffer; RtlZeroMemory(fss, sizeof(FILESYSTEM_STATISTICS)); fss->Version = 1; fss->FileSystemType = FILESYSTEM_STATISTICS_TYPE_NTFS; fss->SizeOfCompleteStructure = sizeof(FILESYSTEM_STATISTICS); *retlen = sizeof(FILESYSTEM_STATISTICS); return STATUS_SUCCESS; } static NTSTATUS set_sparse(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG length, PIRP Irp) { FILE_SET_SPARSE_BUFFER* fssb = data; NTSTATUS Status; bool set; fcb* fcb; ccb* ccb = FileObject->FsContext2; file_ref* fileref = ccb ? ccb->fileref : NULL; if (data && length < sizeof(FILE_SET_SPARSE_BUFFER)) return STATUS_INVALID_PARAMETER; if (!FileObject) { ERR("FileObject was NULL\n"); return STATUS_INVALID_PARAMETER; } fcb = FileObject->FsContext; if (!fcb) { ERR("FCB was NULL\n"); return STATUS_INVALID_PARAMETER; } if (!ccb) { ERR("CCB was NULL\n"); return STATUS_INVALID_PARAMETER; } if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } if (!fileref) { ERR("no fileref\n"); return STATUS_INVALID_PARAMETER; } if (fcb->ads) { fileref = fileref->parent; fcb = fileref->fcb; } ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (fcb->type != BTRFS_TYPE_FILE) { WARN("FileObject did not point to a file\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (fssb) set = fssb->SetSparse; else set = true; if (set) { fcb->atts |= FILE_ATTRIBUTE_SPARSE_FILE; fcb->atts_changed = true; } else { ULONG defda; fcb->atts &= ~FILE_ATTRIBUTE_SPARSE_FILE; fcb->atts_changed = true; 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] == '.', true, Irp); fcb->atts_deleted = defda == fcb->atts; } mark_fcb_dirty(fcb); queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL); Status = STATUS_SUCCESS; end: ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS zero_data(device_extension* Vcb, fcb* fcb, uint64_t start, uint64_t length, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; bool make_inline, compress; uint64_t start_data, end_data; ULONG buf_head; uint8_t* data; make_inline = fcb->inode_item.st_size <= Vcb->options.max_inline || fcb_is_inline(fcb); if (!make_inline) compress = write_fcb_compressed(fcb); if (make_inline) { start_data = 0; end_data = fcb->inode_item.st_size; buf_head = (ULONG)offsetof(EXTENT_DATA, data[0]); } else if (compress) { start_data = start & ~(uint64_t)(COMPRESSED_EXTENT_SIZE - 1); end_data = min(sector_align(start + length, COMPRESSED_EXTENT_SIZE), sector_align(fcb->inode_item.st_size, Vcb->superblock.sector_size)); buf_head = 0; } else { start_data = start & ~(uint64_t)(Vcb->superblock.sector_size - 1); end_data = sector_align(start + length, Vcb->superblock.sector_size); buf_head = 0; } data = ExAllocatePoolWithTag(PagedPool, (ULONG)(buf_head + end_data - start_data), ALLOC_TAG); if (!data) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(data + buf_head, (ULONG)(end_data - start_data)); if (start > start_data || start + length < end_data) { Status = read_file(fcb, data + buf_head, start_data, end_data - start_data, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("read_file returned %08lx\n", Status); ExFreePool(data); return Status; } } RtlZeroMemory(data + buf_head + start - start_data, (ULONG)length); if (make_inline) { uint16_t edsize; EXTENT_DATA* ed = (EXTENT_DATA*)data; Status = excise_extents(Vcb, fcb, 0, sector_align(end_data, Vcb->superblock.sector_size), Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); ExFreePool(data); return Status; } edsize = (uint16_t)(offsetof(EXTENT_DATA, data[0]) + end_data); ed->generation = Vcb->superblock.generation; ed->decoded_size = end_data; ed->compression = BTRFS_COMPRESSION_NONE; ed->encryption = BTRFS_ENCRYPTION_NONE; ed->encoding = BTRFS_ENCODING_NONE; ed->type = EXTENT_TYPE_INLINE; Status = add_extent_to_fcb(fcb, 0, ed, edsize, false, NULL, rollback); if (!NT_SUCCESS(Status)) { ERR("add_extent_to_fcb returned %08lx\n", Status); ExFreePool(data); return Status; } ExFreePool(data); fcb->inode_item.st_blocks += end_data; } else if (compress) { Status = write_compressed(fcb, start_data, end_data, data, Irp, rollback); ExFreePool(data); if (!NT_SUCCESS(Status)) { ERR("write_compressed returned %08lx\n", Status); return Status; } } else { Status = do_write_file(fcb, start_data, end_data, data, Irp, false, 0, rollback); ExFreePool(data); if (!NT_SUCCESS(Status)) { ERR("do_write_file returned %08lx\n", Status); return Status; } } return STATUS_SUCCESS; } static NTSTATUS set_zero_data(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG length, PIRP Irp) { FILE_ZERO_DATA_INFORMATION* fzdi = data; NTSTATUS Status; fcb* fcb; ccb* ccb; file_ref* fileref; LIST_ENTRY rollback, *le; LARGE_INTEGER time; BTRFS_TIME now; uint64_t start, end; extent* ext; IO_STATUS_BLOCK iosb; if (!data || length < sizeof(FILE_ZERO_DATA_INFORMATION)) return STATUS_INVALID_PARAMETER; if (!FileObject) { ERR("FileObject was NULL\n"); return STATUS_INVALID_PARAMETER; } if (fzdi->BeyondFinalZero.QuadPart <= fzdi->FileOffset.QuadPart) { WARN("BeyondFinalZero was less than or equal to FileOffset (%I64x <= %I64x)\n", fzdi->BeyondFinalZero.QuadPart, fzdi->FileOffset.QuadPart); return STATUS_INVALID_PARAMETER; } fcb = FileObject->FsContext; if (!fcb) { ERR("FCB was NULL\n"); return STATUS_INVALID_PARAMETER; } ccb = FileObject->FsContext2; if (!ccb) { ERR("ccb was NULL\n"); return STATUS_INVALID_PARAMETER; } if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_DATA)) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } fileref = ccb->fileref; if (!fileref) { ERR("fileref was NULL\n"); return STATUS_INVALID_PARAMETER; } InitializeListHead(&rollback); ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &iosb); if (fcb->type != BTRFS_TYPE_FILE) { WARN("FileObject did not point to a file\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (fcb->ads) { ERR("FileObject is stream\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if ((uint64_t)fzdi->FileOffset.QuadPart >= fcb->inode_item.st_size) { Status = STATUS_SUCCESS; goto end; } ext = NULL; le = fcb->extents.Flink; while (le != &fcb->extents) { extent* ext2 = CONTAINING_RECORD(le, extent, list_entry); if (!ext2->ignore) { ext = ext2; break; } le = le->Flink; } if (!ext) { Status = STATUS_SUCCESS; goto end; } if (ext->extent_data.type == EXTENT_TYPE_INLINE) { Status = zero_data(Vcb, fcb, fzdi->FileOffset.QuadPart, fzdi->BeyondFinalZero.QuadPart - fzdi->FileOffset.QuadPart, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("zero_data returned %08lx\n", Status); goto end; } } else { start = sector_align(fzdi->FileOffset.QuadPart, Vcb->superblock.sector_size); if ((uint64_t)fzdi->BeyondFinalZero.QuadPart > fcb->inode_item.st_size) end = sector_align(fcb->inode_item.st_size, Vcb->superblock.sector_size); else end = (fzdi->BeyondFinalZero.QuadPart >> Vcb->sector_shift) << Vcb->sector_shift; if (end <= start) { Status = zero_data(Vcb, fcb, fzdi->FileOffset.QuadPart, fzdi->BeyondFinalZero.QuadPart - fzdi->FileOffset.QuadPart, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("zero_data returned %08lx\n", Status); goto end; } } else { if (start > (uint64_t)fzdi->FileOffset.QuadPart) { Status = zero_data(Vcb, fcb, fzdi->FileOffset.QuadPart, start - fzdi->FileOffset.QuadPart, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("zero_data returned %08lx\n", Status); goto end; } } if (end < (uint64_t)fzdi->BeyondFinalZero.QuadPart) { Status = zero_data(Vcb, fcb, end, fzdi->BeyondFinalZero.QuadPart - end, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("zero_data returned %08lx\n", Status); goto end; } } if (end > start) { Status = excise_extents(Vcb, fcb, start, end, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); goto end; } } } } CcPurgeCacheSection(FileObject->SectionObjectPointer, &fzdi->FileOffset, (ULONG)(fzdi->BeyondFinalZero.QuadPart - fzdi->FileOffset.QuadPart), false); KeQuerySystemTime(&time); win_time_to_unix(time, &now); fcb->inode_item.transid = Vcb->superblock.generation; fcb->inode_item.sequence++; if (!ccb->user_set_change_time) fcb->inode_item.st_ctime = now; if (!ccb->user_set_write_time) fcb->inode_item.st_mtime = now; fcb->extents_changed = true; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL); fcb->subvol->root_item.ctransid = Vcb->superblock.generation; fcb->subvol->root_item.ctime = now; Status = STATUS_SUCCESS; end: if (!NT_SUCCESS(Status)) do_rollback(Vcb, &rollback); else clear_rollback(&rollback); ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS query_ranges(PFILE_OBJECT FileObject, FILE_ALLOCATED_RANGE_BUFFER* inbuf, ULONG inbuflen, void* outbuf, ULONG outbuflen, ULONG_PTR* retlen) { NTSTATUS Status; fcb* fcb; LIST_ENTRY* le; FILE_ALLOCATED_RANGE_BUFFER* ranges = outbuf; ULONG i = 0; uint64_t last_start, last_end; TRACE("FSCTL_QUERY_ALLOCATED_RANGES\n"); if (!FileObject) { ERR("FileObject was NULL\n"); return STATUS_INVALID_PARAMETER; } if (!inbuf || inbuflen < sizeof(FILE_ALLOCATED_RANGE_BUFFER) || !outbuf) return STATUS_INVALID_PARAMETER; fcb = FileObject->FsContext; if (!fcb) { ERR("FCB was NULL\n"); return STATUS_INVALID_PARAMETER; } ExAcquireResourceSharedLite(fcb->Header.Resource, true); // If file is not marked as sparse, claim the whole thing as an allocated range if (!(fcb->atts & FILE_ATTRIBUTE_SPARSE_FILE)) { if (fcb->inode_item.st_size == 0) Status = STATUS_SUCCESS; else if (outbuflen < sizeof(FILE_ALLOCATED_RANGE_BUFFER)) Status = STATUS_BUFFER_TOO_SMALL; else { ranges[i].FileOffset.QuadPart = 0; ranges[i].Length.QuadPart = fcb->inode_item.st_size; i++; Status = STATUS_SUCCESS; } goto end; } le = fcb->extents.Flink; last_start = 0; last_end = 0; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (!ext->ignore) { EXTENT_DATA2* ed2 = (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) ? (EXTENT_DATA2*)ext->extent_data.data : NULL; uint64_t len = ed2 ? ed2->num_bytes : ext->extent_data.decoded_size; if (ext->offset > last_end) { // first extent after a hole if (last_end > last_start) { if ((i + 1) * sizeof(FILE_ALLOCATED_RANGE_BUFFER) <= outbuflen) { ranges[i].FileOffset.QuadPart = last_start; ranges[i].Length.QuadPart = min(fcb->inode_item.st_size, last_end) - last_start; i++; } else { Status = STATUS_BUFFER_TOO_SMALL; goto end; } } last_start = ext->offset; } last_end = ext->offset + len; } le = le->Flink; } if (last_end > last_start) { if ((i + 1) * sizeof(FILE_ALLOCATED_RANGE_BUFFER) <= outbuflen) { ranges[i].FileOffset.QuadPart = last_start; ranges[i].Length.QuadPart = min(fcb->inode_item.st_size, last_end) - last_start; i++; } else { Status = STATUS_BUFFER_TOO_SMALL; goto end; } } Status = STATUS_SUCCESS; end: *retlen = i * sizeof(FILE_ALLOCATED_RANGE_BUFFER); ExReleaseResourceLite(fcb->Header.Resource); return Status; } static NTSTATUS get_object_id(PFILE_OBJECT FileObject, FILE_OBJECTID_BUFFER* buf, ULONG buflen, ULONG_PTR* retlen) { fcb* fcb; TRACE("(%p, %p, %lx, %p)\n", FileObject, buf, buflen, retlen); if (!FileObject) { ERR("FileObject was NULL\n"); return STATUS_INVALID_PARAMETER; } if (!buf || buflen < sizeof(FILE_OBJECTID_BUFFER)) return STATUS_INVALID_PARAMETER; fcb = FileObject->FsContext; if (!fcb) { ERR("FCB was NULL\n"); return STATUS_INVALID_PARAMETER; } ExAcquireResourceSharedLite(fcb->Header.Resource, true); RtlCopyMemory(&buf->ObjectId[0], &fcb->inode, sizeof(uint64_t)); RtlCopyMemory(&buf->ObjectId[sizeof(uint64_t)], &fcb->subvol->id, sizeof(uint64_t)); ExReleaseResourceLite(fcb->Header.Resource); RtlZeroMemory(&buf->ExtendedInfo, sizeof(buf->ExtendedInfo)); *retlen = sizeof(FILE_OBJECTID_BUFFER); return STATUS_SUCCESS; } static void flush_fcb_caches(device_extension* Vcb) { LIST_ENTRY* le; le = Vcb->all_fcbs.Flink; while (le != &Vcb->all_fcbs) { struct _fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry_all); IO_STATUS_BLOCK iosb; if (fcb->type != BTRFS_TYPE_DIRECTORY && !fcb->deleted) CcFlushCache(&fcb->nonpaged->segment_object, NULL, 0, &iosb); le = le->Flink; } } static NTSTATUS lock_volume(device_extension* Vcb, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); NTSTATUS Status; KIRQL irql; bool lock_paused_balance = false; TRACE("FSCTL_LOCK_VOLUME\n"); if (Vcb->scrub.thread) { WARN("cannot lock while scrub running\n"); return STATUS_DEVICE_NOT_READY; } if (Vcb->balance.thread) { WARN("cannot lock while balance running\n"); return STATUS_DEVICE_NOT_READY; } TRACE("locking volume\n"); FsRtlNotifyVolumeEvent(IrpSp->FileObject, FSRTL_VOLUME_LOCK); if (Vcb->locked) return STATUS_SUCCESS; ExAcquireResourceExclusiveLite(&Vcb->fileref_lock, true); if (Vcb->root_fileref && Vcb->root_fileref->fcb && (Vcb->root_fileref->open_count > 0 || has_open_children(Vcb->root_fileref))) { Status = STATUS_ACCESS_DENIED; ExReleaseResourceLite(&Vcb->fileref_lock); goto end; } ExReleaseResourceLite(&Vcb->fileref_lock); if (Vcb->balance.thread && KeReadStateEvent(&Vcb->balance.event)) { ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); KeClearEvent(&Vcb->balance.event); ExReleaseResourceLite(&Vcb->tree_lock); lock_paused_balance = true; } ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); flush_fcb_caches(Vcb); if (Vcb->need_write && !Vcb->readonly) Status = do_write(Vcb, Irp); else Status = STATUS_SUCCESS; free_trees(Vcb); ExReleaseResourceLite(&Vcb->tree_lock); if (!NT_SUCCESS(Status)) { ERR("do_write returned %08lx\n", Status); goto end; } IoAcquireVpbSpinLock(&irql); if (!(Vcb->Vpb->Flags & VPB_LOCKED)) { Vcb->Vpb->Flags |= VPB_LOCKED; Vcb->locked = true; Vcb->locked_fileobj = IrpSp->FileObject; Vcb->lock_paused_balance = lock_paused_balance; } else { Status = STATUS_ACCESS_DENIED; IoReleaseVpbSpinLock(irql); if (lock_paused_balance) KeSetEvent(&Vcb->balance.event, 0, false); goto end; } IoReleaseVpbSpinLock(irql); Status = STATUS_SUCCESS; end: if (!NT_SUCCESS(Status)) FsRtlNotifyVolumeEvent(IrpSp->FileObject, FSRTL_VOLUME_LOCK_FAILED); return Status; } void do_unlock_volume(device_extension* Vcb) { KIRQL irql; IoAcquireVpbSpinLock(&irql); Vcb->locked = false; Vcb->Vpb->Flags &= ~VPB_LOCKED; Vcb->locked_fileobj = NULL; IoReleaseVpbSpinLock(irql); if (Vcb->lock_paused_balance) KeSetEvent(&Vcb->balance.event, 0, false); } static NTSTATUS unlock_volume(device_extension* Vcb, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); TRACE("FSCTL_UNLOCK_VOLUME\n"); if (!Vcb->locked || IrpSp->FileObject != Vcb->locked_fileobj) return STATUS_NOT_LOCKED; TRACE("unlocking volume\n"); do_unlock_volume(Vcb); FsRtlNotifyVolumeEvent(IrpSp->FileObject, FSRTL_VOLUME_UNLOCK); return STATUS_SUCCESS; } static NTSTATUS invalidate_volumes(PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); LUID TcbPrivilege = {SE_TCB_PRIVILEGE, 0}; NTSTATUS Status; HANDLE h; PFILE_OBJECT fileobj; PDEVICE_OBJECT devobj; LIST_ENTRY* le; TRACE("FSCTL_INVALIDATE_VOLUMES\n"); if (!SeSinglePrivilegeCheck(TcbPrivilege, Irp->RequestorMode)) return STATUS_PRIVILEGE_NOT_HELD; #if defined(_WIN64) if (IoIs32bitProcess(Irp)) { if (IrpSp->Parameters.FileSystemControl.InputBufferLength != sizeof(uint32_t)) return STATUS_INVALID_PARAMETER; h = (HANDLE)LongToHandle((*(uint32_t*)Irp->AssociatedIrp.SystemBuffer)); } else { #endif if (IrpSp->Parameters.FileSystemControl.InputBufferLength != sizeof(HANDLE)) return STATUS_INVALID_PARAMETER; h = *(PHANDLE)Irp->AssociatedIrp.SystemBuffer; #if defined(_WIN64) } #endif Status = ObReferenceObjectByHandle(h, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL); if (!NT_SUCCESS(Status)) { ERR("ObReferenceObjectByHandle returned %08lx\n", Status); return Status; } devobj = fileobj->DeviceObject; ExAcquireResourceSharedLite(&global_loading_lock, true); le = VcbList.Flink; while (le != &VcbList) { device_extension* Vcb = CONTAINING_RECORD(le, device_extension, list_entry); if (Vcb->Vpb && Vcb->Vpb->RealDevice == devobj) { if (Vcb->Vpb == devobj->Vpb) { KIRQL irql; PVPB newvpb; bool free_newvpb = false; newvpb = ExAllocatePoolWithTag(NonPagedPool, sizeof(VPB), ALLOC_TAG); if (!newvpb) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlZeroMemory(newvpb, sizeof(VPB)); ObReferenceObject(devobj); ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); Vcb->removing = true; ExReleaseResourceLite(&Vcb->tree_lock); CcWaitForCurrentLazyWriterActivity(); ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); flush_fcb_caches(Vcb); if (Vcb->need_write && !Vcb->readonly) Status = do_write(Vcb, Irp); else Status = STATUS_SUCCESS; free_trees(Vcb); if (!NT_SUCCESS(Status)) { ERR("do_write returned %08lx\n", Status); ExReleaseResourceLite(&Vcb->tree_lock); ExFreePool(newvpb); ObDereferenceObject(devobj); goto end; } flush_fcb_caches(Vcb); ExReleaseResourceLite(&Vcb->tree_lock); IoAcquireVpbSpinLock(&irql); if (devobj->Vpb->Flags & VPB_MOUNTED) { newvpb->Type = IO_TYPE_VPB; newvpb->Size = sizeof(VPB); newvpb->RealDevice = devobj; newvpb->Flags = devobj->Vpb->Flags & VPB_REMOVE_PENDING; devobj->Vpb = newvpb; } else free_newvpb = true; IoReleaseVpbSpinLock(irql); if (free_newvpb) ExFreePool(newvpb); if (Vcb->open_files == 0) uninit(Vcb); ObDereferenceObject(devobj); } break; } le = le->Flink; } Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&global_loading_lock); ObDereferenceObject(fileobj); return Status; } static NTSTATUS is_volume_dirty(device_extension* Vcb, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); ULONG* volstate; if (Irp->AssociatedIrp.SystemBuffer) { volstate = Irp->AssociatedIrp.SystemBuffer; } else if (Irp->MdlAddress != NULL) { volstate = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, LowPagePriority); if (!volstate) return STATUS_INSUFFICIENT_RESOURCES; } else return STATUS_INVALID_USER_BUFFER; if (IrpSp->Parameters.FileSystemControl.OutputBufferLength < sizeof(ULONG)) return STATUS_INVALID_PARAMETER; *volstate = 0; if (IrpSp->FileObject->FsContext != Vcb->volume_fcb) return STATUS_INVALID_PARAMETER; Irp->IoStatus.Information = sizeof(ULONG); return STATUS_SUCCESS; } static NTSTATUS get_compression(PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); USHORT* compression; TRACE("FSCTL_GET_COMPRESSION\n"); if (Irp->AssociatedIrp.SystemBuffer) { compression = Irp->AssociatedIrp.SystemBuffer; } else if (Irp->MdlAddress != NULL) { compression = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, LowPagePriority); if (!compression) return STATUS_INSUFFICIENT_RESOURCES; } else return STATUS_INVALID_USER_BUFFER; if (IrpSp->Parameters.FileSystemControl.OutputBufferLength < sizeof(USHORT)) return STATUS_INVALID_PARAMETER; *compression = COMPRESSION_FORMAT_NONE; Irp->IoStatus.Information = sizeof(USHORT); return STATUS_SUCCESS; } static NTSTATUS set_compression(PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); USHORT* compression; TRACE("FSCTL_SET_COMPRESSION\n"); if (IrpSp->Parameters.FileSystemControl.InputBufferLength < sizeof(USHORT)) return STATUS_INVALID_PARAMETER; compression = Irp->AssociatedIrp.SystemBuffer; if (*compression != COMPRESSION_FORMAT_NONE) return STATUS_INVALID_PARAMETER; return STATUS_SUCCESS; } static void update_volumes(device_extension* Vcb) { LIST_ENTRY* le; volume_device_extension* vde = Vcb->vde; pdo_device_extension* pdode = vde->pdode; ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(&pdode->child_lock, true); le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); vc->generation = Vcb->superblock.generation - 1; le = le->Flink; } ExReleaseResourceLite(&pdode->child_lock); ExReleaseResourceLite(&Vcb->tree_lock); } NTSTATUS dismount_volume(device_extension* Vcb, bool shutdown, PIRP Irp) { NTSTATUS Status; bool open_files; TRACE("FSCTL_DISMOUNT_VOLUME\n"); if (!(Vcb->Vpb->Flags & VPB_MOUNTED)) return STATUS_SUCCESS; if (!shutdown) { if (Vcb->disallow_dismount || Vcb->page_file_count != 0) { WARN("attempting to dismount boot volume or one containing a pagefile\n"); return STATUS_ACCESS_DENIED; } Status = FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_DISMOUNT); if (!NT_SUCCESS(Status)) { WARN("FsRtlNotifyVolumeEvent returned %08lx\n", Status); } } ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); if (!Vcb->locked) { flush_fcb_caches(Vcb); if (Vcb->need_write && !Vcb->readonly) { Status = do_write(Vcb, Irp); if (!NT_SUCCESS(Status)) ERR("do_write returned %08lx\n", Status); } } free_trees(Vcb); Vcb->removing = true; open_files = Vcb->open_files > 0; if (Vcb->vde) { update_volumes(Vcb); Vcb->vde->mounted_device = NULL; } ExReleaseResourceLite(&Vcb->tree_lock); if (!open_files) uninit(Vcb); return STATUS_SUCCESS; } static NTSTATUS is_device_part_of_mounted_btrfs_raid(PDEVICE_OBJECT devobj, PFILE_OBJECT fileobj) { NTSTATUS Status; ULONG to_read; superblock* sb; BTRFS_UUID fsuuid, devuuid; LIST_ENTRY* le; to_read = devobj->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), devobj->SectorSize); sb = ExAllocatePoolWithTag(PagedPool, to_read, ALLOC_TAG); if (!sb) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } Status = sync_read_phys(devobj, fileobj, superblock_addrs[0], to_read, (uint8_t*)sb, true); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); ExFreePool(sb); return Status; } if (sb->magic != BTRFS_MAGIC) { TRACE("device is not Btrfs\n"); ExFreePool(sb); return STATUS_SUCCESS; } if (!check_superblock_checksum(sb)) { TRACE("device has Btrfs magic, but invalid superblock checksum\n"); ExFreePool(sb); return STATUS_SUCCESS; } fsuuid = sb->uuid; devuuid = sb->dev_item.device_uuid; ExFreePool(sb); ExAcquireResourceSharedLite(&global_loading_lock, true); le = VcbList.Flink; while (le != &VcbList) { device_extension* Vcb = CONTAINING_RECORD(le, device_extension, list_entry); if (RtlCompareMemory(&Vcb->superblock.uuid, &fsuuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { LIST_ENTRY* le2; ExAcquireResourceSharedLite(&Vcb->tree_lock, true); if (Vcb->superblock.num_devices > 1) { le2 = Vcb->devices.Flink; while (le2 != &Vcb->devices) { device* dev = CONTAINING_RECORD(le2, device, list_entry); if (RtlCompareMemory(&dev->devitem.device_uuid, &devuuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { ExReleaseResourceLite(&Vcb->tree_lock); ExReleaseResourceLite(&global_loading_lock); return STATUS_DEVICE_NOT_READY; } le2 = le2->Flink; } } ExReleaseResourceLite(&Vcb->tree_lock); ExReleaseResourceLite(&global_loading_lock); return STATUS_SUCCESS; } le = le->Flink; } ExReleaseResourceLite(&global_loading_lock); return STATUS_SUCCESS; } void trim_whole_device(device* dev) { DEVICE_MANAGE_DATA_SET_ATTRIBUTES dmdsa; NTSTATUS Status; // FIXME - avoid "bootloader area"?? dmdsa.Size = sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES); dmdsa.Action = DeviceDsmAction_Trim; dmdsa.Flags = DEVICE_DSM_FLAG_ENTIRE_DATA_SET_RANGE | DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED; dmdsa.ParameterBlockOffset = 0; dmdsa.ParameterBlockLength = 0; dmdsa.DataSetRangesOffset = 0; dmdsa.DataSetRangesLength = 0; Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES, &dmdsa, sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), NULL, 0, true, NULL); if (!NT_SUCCESS(Status)) WARN("IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES returned %08lx\n", Status); } static NTSTATUS add_device(device_extension* Vcb, PIRP Irp, KPROCESSOR_MODE processor_mode) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); NTSTATUS Status; PFILE_OBJECT fileobj, mountmgrfo; PDEVICE_OBJECT DeviceObject; HANDLE h; LIST_ENTRY* le; device* dev; DEV_ITEM* di; uint64_t dev_id, size; uint8_t* mb; uint64_t* stats; UNICODE_STRING mmdevpath, pnp_name, pnp_name2; volume_child* vc; PDEVICE_OBJECT mountmgr; KEY searchkey; traverse_ptr tp; STORAGE_DEVICE_NUMBER sdn; volume_device_extension* vde; pdo_device_extension* pdode; const GUID* pnp_guid; GET_LENGTH_INFORMATION gli; pnp_name.Buffer = NULL; if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; if (!Vcb->vde) { WARN("not allowing second device to be added to non-PNP device\n"); return STATUS_NOT_SUPPORTED; } if (Vcb->readonly) // FIXME - handle adding R/W device to seeding device return STATUS_MEDIA_WRITE_PROTECTED; #if defined(_WIN64) if (IoIs32bitProcess(Irp)) { if (IrpSp->Parameters.FileSystemControl.InputBufferLength != sizeof(uint32_t)) return STATUS_INVALID_PARAMETER; h = (HANDLE)LongToHandle((*(uint32_t*)Irp->AssociatedIrp.SystemBuffer)); } else { #endif if (IrpSp->Parameters.FileSystemControl.InputBufferLength != sizeof(HANDLE)) return STATUS_INVALID_PARAMETER; h = *(PHANDLE)Irp->AssociatedIrp.SystemBuffer; #if defined(_WIN64) } #endif Status = ObReferenceObjectByHandle(h, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL); if (!NT_SUCCESS(Status)) { ERR("ObReferenceObjectByHandle returned %08lx\n", Status); return Status; } DeviceObject = fileobj->DeviceObject; Status = get_device_pnp_name(DeviceObject, &pnp_name, &pnp_guid); if (!NT_SUCCESS(Status)) { ERR("get_device_pnp_name returned %08lx\n", Status); ObDereferenceObject(fileobj); return Status; } // If this is a disk, we have been handed the PDO, so need to go up to find something we can use if (RtlCompareMemory(pnp_guid, &GUID_DEVINTERFACE_DISK, sizeof(GUID)) == sizeof(GUID) && DeviceObject->AttachedDevice) DeviceObject = DeviceObject->AttachedDevice; Status = dev_ioctl(DeviceObject, IOCTL_DISK_IS_WRITABLE, NULL, 0, NULL, 0, true, NULL); if (!NT_SUCCESS(Status)) { ERR("IOCTL_DISK_IS_WRITABLE returned %08lx\n", Status); ObDereferenceObject(fileobj); return Status; } Status = is_device_part_of_mounted_btrfs_raid(DeviceObject, fileobj); if (!NT_SUCCESS(Status)) { ERR("is_device_part_of_mounted_btrfs_raid returned %08lx\n", Status); ObDereferenceObject(fileobj); return Status; } // if disk, check it has no partitions if (RtlCompareMemory(pnp_guid, &GUID_DEVINTERFACE_DISK, sizeof(GUID)) == sizeof(GUID)) { ULONG dlisize; DRIVE_LAYOUT_INFORMATION_EX* dli = NULL; dlisize = 0; do { dlisize += 1024; if (dli) ExFreePool(dli); dli = ExAllocatePoolWithTag(PagedPool, dlisize, ALLOC_TAG); if (!dli) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end2; } Status = dev_ioctl(DeviceObject, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0, dli, dlisize, true, NULL); } while (Status == STATUS_BUFFER_TOO_SMALL); if (NT_SUCCESS(Status) && dli->PartitionCount > 0) { ExFreePool(dli); ERR("not adding disk which has partitions\n"); Status = STATUS_DEVICE_NOT_READY; goto end2; } ExFreePool(dli); } Status = dev_ioctl(DeviceObject, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(STORAGE_DEVICE_NUMBER), true, NULL); if (NT_SUCCESS(Status)) { if (sdn.DeviceType != FILE_DEVICE_DISK) { // FIXME - accept floppies and CDs? WARN("device was not disk\n"); ObDereferenceObject(fileobj); return STATUS_INVALID_PARAMETER; } } else { sdn.DeviceNumber = 0xffffffff; sdn.PartitionNumber = 0xffffffff; } Status = dev_ioctl(DeviceObject, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli), true, NULL); if (!NT_SUCCESS(Status)) { ERR("error reading length information: %08lx\n", Status); ObDereferenceObject(fileobj); return Status; } size = gli.Length.QuadPart; if (size < 0x100000) { ERR("device was not large enough to hold FS (%I64x bytes, need at least 1 MB)\n", size); ObDereferenceObject(fileobj); return STATUS_INTERNAL_ERROR; } volume_removal(&pnp_name); ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); if (Vcb->need_write) Status = do_write(Vcb, Irp); else Status = STATUS_SUCCESS; free_trees(Vcb); if (!NT_SUCCESS(Status)) { ERR("do_write returned %08lx\n", Status); goto end; } dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG); if (!dev) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlZeroMemory(dev, sizeof(device)); dev->devobj = DeviceObject; dev->fileobj = fileobj; dev->seeding = false; init_device(Vcb, dev, true); InitializeListHead(&dev->space); if (size > 0x100000) { // add disk hole - the first MB is marked as used Status = add_space_entry(&dev->space, NULL, 0x100000, size - 0x100000); if (!NT_SUCCESS(Status)) { ERR("add_space_entry returned %08lx\n", Status); goto end; } } dev_id = 0; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev2 = CONTAINING_RECORD(le, device, list_entry); if (dev2->devitem.dev_id > dev_id) dev_id = dev2->devitem.dev_id; le = le->Flink; } dev_id++; dev->devitem.dev_id = dev_id; dev->devitem.num_bytes = size; dev->devitem.bytes_used = 0; dev->devitem.optimal_io_align = Vcb->superblock.sector_size; dev->devitem.optimal_io_width = Vcb->superblock.sector_size; dev->devitem.minimal_io_size = Vcb->superblock.sector_size; dev->devitem.type = 0; dev->devitem.generation = 0; dev->devitem.start_offset = 0; dev->devitem.dev_group = 0; dev->devitem.seek_speed = 0; dev->devitem.bandwidth = 0; get_uuid(&dev->devitem.device_uuid); dev->devitem.fs_uuid = Vcb->superblock.uuid; di = ExAllocatePoolWithTag(PagedPool, sizeof(DEV_ITEM), ALLOC_TAG); if (!di) { ERR("out of memory\n"); goto end; } RtlCopyMemory(di, &dev->devitem, sizeof(DEV_ITEM)); Status = insert_tree_item(Vcb, Vcb->chunk_root, 1, TYPE_DEV_ITEM, di->dev_id, di, sizeof(DEV_ITEM), NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(di); goto end; } // add stats entry to dev tree stats = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t) * 5, ALLOC_TAG); if (!stats) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlZeroMemory(stats, sizeof(uint64_t) * 5); searchkey.obj_id = 0; searchkey.obj_type = TYPE_DEV_STATS; searchkey.offset = di->dev_id; Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); ExFreePool(stats); goto end; } if (!keycmp(tp.item->key, searchkey)) { Status = delete_tree_item(Vcb, &tp); if (!NT_SUCCESS(Status)) { ERR("delete_tree_item returned %08lx\n", Status); ExFreePool(stats); goto end; } } Status = insert_tree_item(Vcb, Vcb->dev_root, 0, TYPE_DEV_STATS, di->dev_id, stats, sizeof(uint64_t) * 5, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("insert_tree_item returned %08lx\n", Status); ExFreePool(stats); goto end; } if (dev->trim && !dev->readonly && !Vcb->options.no_trim) trim_whole_device(dev); // We clear the first megabyte of the device, so Windows doesn't identify it as another FS mb = ExAllocatePoolWithTag(PagedPool, 0x100000, ALLOC_TAG); if (!mb) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlZeroMemory(mb, 0x100000); Status = write_data_phys(DeviceObject, fileobj, 0, mb, 0x100000); if (!NT_SUCCESS(Status)) { ERR("write_data_phys returned %08lx\n", Status); ExFreePool(mb); goto end; } ExFreePool(mb); vde = Vcb->vde; pdode = vde->pdode; vc = ExAllocatePoolWithTag(NonPagedPool, sizeof(volume_child), ALLOC_TAG); if (!vc) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } vc->uuid = dev->devitem.device_uuid; vc->devid = dev_id; vc->generation = Vcb->superblock.generation; vc->devobj = DeviceObject; vc->fileobj = fileobj; vc->notification_entry = NULL; vc->boot_volume = false; Status = IoRegisterPlugPlayNotification(EventCategoryTargetDeviceChange, 0, fileobj, drvobj, pnp_removal, vde->pdode, &vc->notification_entry); if (!NT_SUCCESS(Status)) WARN("IoRegisterPlugPlayNotification returned %08lx\n", Status); pnp_name2 = pnp_name; if (pnp_name.Length > 4 * sizeof(WCHAR) && pnp_name.Buffer[0] == '\\' && (pnp_name.Buffer[1] == '\\' || pnp_name.Buffer[1] == '?') && pnp_name.Buffer[2] == '?' && pnp_name.Buffer[3] == '\\') { pnp_name2.Buffer = &pnp_name2.Buffer[3]; pnp_name2.Length -= 3 * sizeof(WCHAR); pnp_name2.MaximumLength -= 3 * sizeof(WCHAR); } vc->pnp_name.Length = vc->pnp_name.MaximumLength = pnp_name2.Length; if (pnp_name2.Length == 0) vc->pnp_name.Buffer = NULL; else { vc->pnp_name.Buffer = ExAllocatePoolWithTag(PagedPool, pnp_name2.Length, ALLOC_TAG); if (!vc->pnp_name.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(vc->pnp_name.Buffer, pnp_name2.Buffer, pnp_name2.Length); } vc->size = size; vc->seeding = false; vc->disk_num = sdn.DeviceNumber; vc->part_num = sdn.PartitionNumber; vc->had_drive_letter = false; ExAcquireResourceExclusiveLite(&pdode->child_lock, true); InsertTailList(&pdode->children, &vc->list_entry); pdode->num_children++; pdode->children_loaded++; ExReleaseResourceLite(&pdode->child_lock); RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME); Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &mountmgrfo, &mountmgr); if (!NT_SUCCESS(Status)) ERR("IoGetDeviceObjectPointer returned %08lx\n", Status); else { Status = remove_drive_letter(mountmgr, &pnp_name); if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) WARN("remove_drive_letter returned %08lx\n", Status); vc->had_drive_letter = NT_SUCCESS(Status); ObDereferenceObject(mountmgrfo); } Vcb->superblock.num_devices++; Vcb->superblock.total_bytes += size; Vcb->devices_loaded++; InsertTailList(&Vcb->devices, &dev->list_entry); // FIXME - send notification that volume size has increased ObReferenceObject(DeviceObject); // for Vcb Status = do_write(Vcb, Irp); if (!NT_SUCCESS(Status)) ERR("do_write returned %08lx\n", Status); ObReferenceObject(fileobj); end: free_trees(Vcb); ExReleaseResourceLite(&Vcb->tree_lock); end2: ObDereferenceObject(fileobj); if (pnp_name.Buffer) ExFreePool(pnp_name.Buffer); if (NT_SUCCESS(Status)) FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_CHANGE_SIZE); return Status; } static NTSTATUS allow_extended_dasd_io(device_extension* Vcb, PFILE_OBJECT FileObject) { fcb* fcb; ccb* ccb; TRACE("FSCTL_ALLOW_EXTENDED_DASD_IO\n"); if (!FileObject) return STATUS_INVALID_PARAMETER; fcb = FileObject->FsContext; ccb = FileObject->FsContext2; if (!fcb) return STATUS_INVALID_PARAMETER; if (fcb != Vcb->volume_fcb) return STATUS_INVALID_PARAMETER; if (!ccb) return STATUS_INVALID_PARAMETER; ccb->allow_extended_dasd_io = true; return STATUS_SUCCESS; } static NTSTATUS query_uuid(device_extension* Vcb, void* data, ULONG length) { if (length < sizeof(BTRFS_UUID)) return STATUS_BUFFER_OVERFLOW; RtlCopyMemory(data, &Vcb->superblock.uuid, sizeof(BTRFS_UUID)); return STATUS_SUCCESS; } static NTSTATUS reset_stats(device_extension* Vcb, void* data, ULONG length, KPROCESSOR_MODE processor_mode) { uint64_t devid; NTSTATUS Status; LIST_ENTRY* le; if (length < sizeof(uint64_t)) return STATUS_INVALID_PARAMETER; if (Vcb->readonly) return STATUS_MEDIA_WRITE_PROTECTED; if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; devid = *((uint64_t*)data); ExAcquireResourceSharedLite(&Vcb->tree_lock, true); le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (dev->devitem.dev_id == devid) { RtlZeroMemory(dev->stats, sizeof(uint64_t) * 5); dev->stats_changed = true; Vcb->stats_changed = true; Vcb->need_write = true; Status = STATUS_SUCCESS; goto end; } le = le->Flink; } WARN("device %I64x not found\n", devid); Status = STATUS_INVALID_PARAMETER; end: ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS get_integrity_information(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen) { FSCTL_GET_INTEGRITY_INFORMATION_BUFFER* fgiib = (FSCTL_GET_INTEGRITY_INFORMATION_BUFFER*)data; TRACE("FSCTL_GET_INTEGRITY_INFORMATION\n"); // STUB if (!FileObject) return STATUS_INVALID_PARAMETER; if (!data || datalen < sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER)) return STATUS_INVALID_PARAMETER; fgiib->ChecksumAlgorithm = 0; fgiib->Reserved = 0; fgiib->Flags = 0; fgiib->ChecksumChunkSizeInBytes = Vcb->superblock.sector_size; fgiib->ClusterSizeInBytes = Vcb->superblock.sector_size; return STATUS_SUCCESS; } static NTSTATUS set_integrity_information(PFILE_OBJECT FileObject, void* data, ULONG datalen) { TRACE("FSCTL_SET_INTEGRITY_INFORMATION\n"); // STUB if (!FileObject) return STATUS_INVALID_PARAMETER; if (!data || datalen < sizeof(FSCTL_SET_INTEGRITY_INFORMATION_BUFFER)) return STATUS_INVALID_PARAMETER; return STATUS_SUCCESS; } bool fcb_is_inline(fcb* fcb) { LIST_ENTRY* le; le = fcb->extents.Flink; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (!ext->ignore) return ext->extent_data.type == EXTENT_TYPE_INLINE; le = le->Flink; } return false; } static NTSTATUS duplicate_extents(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, PIRP Irp) { DUPLICATE_EXTENTS_DATA* ded = (DUPLICATE_EXTENTS_DATA*)data; fcb *fcb = FileObject ? FileObject->FsContext : NULL, *sourcefcb; ccb *ccb = FileObject ? FileObject->FsContext2 : NULL, *sourceccb; NTSTATUS Status; PFILE_OBJECT sourcefo; uint64_t sourcelen, nbytes = 0; LIST_ENTRY rollback, *le, newexts; LARGE_INTEGER time; BTRFS_TIME now; bool make_inline; if (!ded || datalen < sizeof(DUPLICATE_EXTENTS_DATA)) return STATUS_BUFFER_TOO_SMALL; if (Vcb->readonly) return STATUS_MEDIA_WRITE_PROTECTED; if (ded->ByteCount.QuadPart == 0) return STATUS_SUCCESS; if (!fcb || !ccb || fcb == Vcb->volume_fcb) return STATUS_INVALID_PARAMETER; if (is_subvol_readonly(fcb->subvol, Irp)) return STATUS_ACCESS_DENIED; if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_DATA)) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } if (!fcb->ads && fcb->type != BTRFS_TYPE_FILE && fcb->type != BTRFS_TYPE_SYMLINK) return STATUS_INVALID_PARAMETER; Status = ObReferenceObjectByHandle(ded->FileHandle, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&sourcefo, NULL); if (!NT_SUCCESS(Status)) { ERR("ObReferenceObjectByHandle returned %08lx\n", Status); return Status; } if (sourcefo->DeviceObject != FileObject->DeviceObject) { WARN("source and destination are on different volumes\n"); ObDereferenceObject(sourcefo); return STATUS_INVALID_PARAMETER; } sourcefcb = sourcefo->FsContext; sourceccb = sourcefo->FsContext2; if (!sourcefcb || !sourceccb || sourcefcb == Vcb->volume_fcb) { ObDereferenceObject(sourcefo); return STATUS_INVALID_PARAMETER; } if (!sourcefcb->ads && !fcb->ads) { if ((ded->SourceFileOffset.QuadPart & (Vcb->superblock.sector_size - 1)) || (ded->TargetFileOffset.QuadPart & (Vcb->superblock.sector_size - 1))) { ObDereferenceObject(sourcefo); return STATUS_INVALID_PARAMETER; } if (ded->ByteCount.QuadPart & (Vcb->superblock.sector_size - 1)) { ObDereferenceObject(sourcefo); return STATUS_INVALID_PARAMETER; } } if (Irp->RequestorMode == UserMode && (!(sourceccb->access & FILE_READ_DATA) || !(sourceccb->access & FILE_READ_ATTRIBUTES))) { WARN("insufficient privileges\n"); ObDereferenceObject(sourcefo); return STATUS_ACCESS_DENIED; } if (!sourcefcb->ads && sourcefcb->type != BTRFS_TYPE_FILE && sourcefcb->type != BTRFS_TYPE_SYMLINK) { ObDereferenceObject(sourcefo); return STATUS_INVALID_PARAMETER; } sourcelen = sourcefcb->ads ? sourcefcb->adsdata.Length : sourcefcb->inode_item.st_size; if (sector_align(sourcelen, Vcb->superblock.sector_size) < (uint64_t)ded->SourceFileOffset.QuadPart + (uint64_t)ded->ByteCount.QuadPart) { ObDereferenceObject(sourcefo); return STATUS_NOT_SUPPORTED; } if (fcb == sourcefcb && ((ded->SourceFileOffset.QuadPart >= ded->TargetFileOffset.QuadPart && ded->SourceFileOffset.QuadPart < ded->TargetFileOffset.QuadPart + ded->ByteCount.QuadPart) || (ded->TargetFileOffset.QuadPart >= ded->SourceFileOffset.QuadPart && ded->TargetFileOffset.QuadPart < ded->SourceFileOffset.QuadPart + ded->ByteCount.QuadPart))) { WARN("source and destination are the same, and the ranges overlap\n"); ObDereferenceObject(sourcefo); return STATUS_INVALID_PARAMETER; } // fail if nocsum flag set on one file but not the other if (!fcb->ads && !sourcefcb->ads && (fcb->inode_item.flags & BTRFS_INODE_NODATASUM) != (sourcefcb->inode_item.flags & BTRFS_INODE_NODATASUM)) { ObDereferenceObject(sourcefo); return STATUS_INVALID_PARAMETER; } InitializeListHead(&rollback); InitializeListHead(&newexts); ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (fcb != sourcefcb) ExAcquireResourceSharedLite(sourcefcb->Header.Resource, true); if (!FsRtlFastCheckLockForWrite(&fcb->lock, &ded->TargetFileOffset, &ded->ByteCount, 0, FileObject, PsGetCurrentProcess())) { Status = STATUS_FILE_LOCK_CONFLICT; goto end; } if (!FsRtlFastCheckLockForRead(&sourcefcb->lock, &ded->SourceFileOffset, &ded->ByteCount, 0, FileObject, PsGetCurrentProcess())) { Status = STATUS_FILE_LOCK_CONFLICT; goto end; } make_inline = fcb->ads ? false : (fcb->inode_item.st_size <= Vcb->options.max_inline || fcb_is_inline(fcb)); if (fcb->ads || sourcefcb->ads || make_inline || fcb_is_inline(sourcefcb)) { uint8_t* data2; ULONG bytes_read, dataoff, datalen2; if (make_inline) { dataoff = (ULONG)ded->TargetFileOffset.QuadPart; datalen2 = (ULONG)fcb->inode_item.st_size; } else if (fcb->ads) { dataoff = 0; datalen2 = (ULONG)ded->ByteCount.QuadPart; } else { dataoff = ded->TargetFileOffset.QuadPart & (Vcb->superblock.sector_size - 1); datalen2 = (ULONG)sector_align(ded->ByteCount.QuadPart + dataoff, Vcb->superblock.sector_size); } data2 = ExAllocatePoolWithTag(PagedPool, datalen2, ALLOC_TAG); if (!data2) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } if (dataoff > 0) { if (make_inline) Status = read_file(fcb, data2, 0, datalen2, NULL, Irp); else Status = read_file(fcb, data2, ded->TargetFileOffset.QuadPart - dataoff, dataoff, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("read_file returned %08lx\n", Status); ExFreePool(data2); goto end; } } if (sourcefcb->ads) { Status = read_stream(sourcefcb, data2 + dataoff, ded->SourceFileOffset.QuadPart, (ULONG)ded->ByteCount.QuadPart, &bytes_read); if (!NT_SUCCESS(Status)) { ERR("read_stream returned %08lx\n", Status); ExFreePool(data2); goto end; } } else { Status = read_file(sourcefcb, data2 + dataoff, ded->SourceFileOffset.QuadPart, ded->ByteCount.QuadPart, &bytes_read, Irp); if (!NT_SUCCESS(Status)) { ERR("read_file returned %08lx\n", Status); ExFreePool(data2); goto end; } } if (dataoff + bytes_read < datalen2) RtlZeroMemory(data2 + dataoff + bytes_read, datalen2 - bytes_read); if (fcb->ads) RtlCopyMemory(&fcb->adsdata.Buffer[ded->TargetFileOffset.QuadPart], data2, (USHORT)min(ded->ByteCount.QuadPart, fcb->adsdata.Length - ded->TargetFileOffset.QuadPart)); else if (make_inline) { uint16_t edsize; EXTENT_DATA* ed; Status = excise_extents(Vcb, fcb, 0, sector_align(fcb->inode_item.st_size, Vcb->superblock.sector_size), Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); ExFreePool(data2); goto end; } edsize = (uint16_t)(offsetof(EXTENT_DATA, data[0]) + datalen2); ed = ExAllocatePoolWithTag(PagedPool, edsize, ALLOC_TAG); if (!ed) { ERR("out of memory\n"); ExFreePool(data2); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } ed->generation = Vcb->superblock.generation; ed->decoded_size = fcb->inode_item.st_size; ed->compression = BTRFS_COMPRESSION_NONE; ed->encryption = BTRFS_ENCRYPTION_NONE; ed->encoding = BTRFS_ENCODING_NONE; ed->type = EXTENT_TYPE_INLINE; RtlCopyMemory(ed->data, data2, datalen2); Status = add_extent_to_fcb(fcb, 0, ed, edsize, false, NULL, &rollback); if (!NT_SUCCESS(Status)) { ERR("add_extent_to_fcb returned %08lx\n", Status); ExFreePool(data2); goto end; } fcb->inode_item.st_blocks += datalen2; } else { uint64_t start = ded->TargetFileOffset.QuadPart - (ded->TargetFileOffset.QuadPart & (Vcb->superblock.sector_size - 1)); Status = do_write_file(fcb, start, start + datalen2, data2, Irp, false, 0, &rollback); if (!NT_SUCCESS(Status)) { ERR("do_write_file returned %08lx\n", Status); ExFreePool(data2); goto end; } } ExFreePool(data2); } else { LIST_ENTRY* lastextle; le = sourcefcb->extents.Flink; while (le != &sourcefcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (!ext->ignore) { if (ext->offset >= (uint64_t)ded->SourceFileOffset.QuadPart + (uint64_t)ded->ByteCount.QuadPart) break; if (ext->extent_data.type != EXTENT_TYPE_INLINE) { ULONG extlen = offsetof(extent, extent_data) + sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2); extent* ext2; EXTENT_DATA2 *ed2s, *ed2d; chunk* c; ed2s = (EXTENT_DATA2*)ext->extent_data.data; if (ext->offset + ed2s->num_bytes <= (uint64_t)ded->SourceFileOffset.QuadPart) { le = le->Flink; continue; } ext2 = ExAllocatePoolWithTag(PagedPool, extlen, ALLOC_TAG); if (!ext2) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } if (ext->offset < (uint64_t)ded->SourceFileOffset.QuadPart) ext2->offset = ded->TargetFileOffset.QuadPart; else ext2->offset = ext->offset - ded->SourceFileOffset.QuadPart + ded->TargetFileOffset.QuadPart; ext2->datalen = sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2); ext2->unique = false; ext2->ignore = false; ext2->inserted = true; ext2->extent_data.generation = Vcb->superblock.generation; ext2->extent_data.decoded_size = ext->extent_data.decoded_size; ext2->extent_data.compression = ext->extent_data.compression; ext2->extent_data.encryption = ext->extent_data.encryption; ext2->extent_data.encoding = ext->extent_data.encoding; ext2->extent_data.type = ext->extent_data.type; ed2d = (EXTENT_DATA2*)ext2->extent_data.data; ed2d->address = ed2s->address; ed2d->size = ed2s->size; if (ext->offset < (uint64_t)ded->SourceFileOffset.QuadPart) { ed2d->offset = ed2s->offset + ded->SourceFileOffset.QuadPart - ext->offset; ed2d->num_bytes = min((uint64_t)ded->ByteCount.QuadPart, ed2s->num_bytes + ext->offset - ded->SourceFileOffset.QuadPart); } else { ed2d->offset = ed2s->offset; ed2d->num_bytes = min(ded->SourceFileOffset.QuadPart + ded->ByteCount.QuadPart - ext->offset, ed2s->num_bytes); } if (ext->csum) { if (ext->extent_data.compression == BTRFS_COMPRESSION_NONE) { ext2->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ed2d->num_bytes * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG); if (!ext2->csum) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(ext2); goto end; } RtlCopyMemory(ext2->csum, (uint8_t*)ext->csum + (((ed2d->offset - ed2s->offset) * Vcb->csum_size) >> Vcb->sector_shift), (ULONG)((ed2d->num_bytes * Vcb->csum_size) >> Vcb->sector_shift)); } else { ext2->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ed2d->size * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG); if (!ext2->csum) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(ext2); goto end; } RtlCopyMemory(ext2->csum, ext->csum, (ULONG)((ed2s->size * Vcb->csum_size) >> Vcb->sector_shift)); } } else ext2->csum = NULL; InsertTailList(&newexts, &ext2->list_entry); c = get_chunk_from_address(Vcb, ed2s->address); if (!c) { ERR("get_chunk_from_address(%I64x) failed\n", ed2s->address); Status = STATUS_INTERNAL_ERROR; goto end; } Status = update_changed_extent_ref(Vcb, c, ed2s->address, ed2s->size, fcb->subvol->id, fcb->inode, ext2->offset - ed2d->offset, 1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp); if (!NT_SUCCESS(Status)) { ERR("update_changed_extent_ref returned %08lx\n", Status); goto end; } nbytes += ed2d->num_bytes; } } le = le->Flink; } Status = excise_extents(Vcb, fcb, ded->TargetFileOffset.QuadPart, ded->TargetFileOffset.QuadPart + ded->ByteCount.QuadPart, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); while (!IsListEmpty(&newexts)) { extent* ext = CONTAINING_RECORD(RemoveHeadList(&newexts), extent, list_entry); ExFreePool(ext); } goto end; } // clear unique flags in source fcb le = sourcefcb->extents.Flink; while (le != &sourcefcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (!ext->ignore && ext->unique && (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC)) { EXTENT_DATA2* ed2s = (EXTENT_DATA2*)ext->extent_data.data; LIST_ENTRY* le2; le2 = newexts.Flink; while (le2 != &newexts) { extent* ext2 = CONTAINING_RECORD(le2, extent, list_entry); if (ext2->extent_data.type == EXTENT_TYPE_REGULAR || ext2->extent_data.type == EXTENT_TYPE_PREALLOC) { EXTENT_DATA2* ed2d = (EXTENT_DATA2*)ext2->extent_data.data; if (ed2d->address == ed2s->address && ed2d->size == ed2s->size) { ext->unique = false; break; } } le2 = le2->Flink; } } le = le->Flink; } lastextle = &fcb->extents; while (!IsListEmpty(&newexts)) { extent* ext = CONTAINING_RECORD(RemoveHeadList(&newexts), extent, list_entry); add_extent(fcb, lastextle, ext); lastextle = &ext->list_entry; } } KeQuerySystemTime(&time); win_time_to_unix(time, &now); if (fcb->ads) { ccb->fileref->parent->fcb->inode_item.sequence++; if (!ccb->user_set_change_time) ccb->fileref->parent->fcb->inode_item.st_ctime = now; ccb->fileref->parent->fcb->inode_item_changed = true; mark_fcb_dirty(ccb->fileref->parent->fcb); } else { fcb->inode_item.st_blocks += nbytes; fcb->inode_item.sequence++; if (!ccb->user_set_change_time) fcb->inode_item.st_ctime = now; if (!ccb->user_set_write_time) { fcb->inode_item.st_mtime = now; queue_notification_fcb(ccb->fileref, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL); } fcb->inode_item_changed = true; fcb->extents_changed = true; } mark_fcb_dirty(fcb); if (FileObject->SectionObjectPointer->DataSectionObject) CcPurgeCacheSection(FileObject->SectionObjectPointer, &ded->TargetFileOffset, (ULONG)ded->ByteCount.QuadPart, false); Status = STATUS_SUCCESS; end: ObDereferenceObject(sourcefo); if (NT_SUCCESS(Status)) clear_rollback(&rollback); else do_rollback(Vcb, &rollback); if (fcb != sourcefcb) ExReleaseResourceLite(sourcefcb->Header.Resource); ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS check_inode_used(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* subvol, _In_ uint64_t inode, _In_ uint32_t hash, _In_opt_ PIRP Irp) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; uint8_t c = hash >> 24; if (subvol->fcbs_ptrs[c]) { LIST_ENTRY* le = subvol->fcbs_ptrs[c]; while (le != &subvol->fcbs) { struct _fcb* fcb2 = CONTAINING_RECORD(le, struct _fcb, list_entry); if (fcb2->inode == inode) return STATUS_SUCCESS; else if (fcb2->hash > hash) break; le = le->Flink; } } searchkey.obj_id = inode; searchkey.obj_type = TYPE_INODE_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, subvol, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) return STATUS_SUCCESS; return STATUS_NOT_FOUND; } static NTSTATUS mknod(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, PIRP Irp) { NTSTATUS Status; btrfs_mknod* bmn; fcb *parfcb, *fcb; ccb* parccb; file_ref *parfileref, *fileref; UNICODE_STRING name; root* subvol; uint64_t inode; dir_child* dc; LARGE_INTEGER time; BTRFS_TIME now; ANSI_STRING utf8; ULONG len, i; SECURITY_SUBJECT_CONTEXT subjcont; PSID owner; BOOLEAN defaulted; TRACE("(%p, %p, %p, %lu)\n", Vcb, FileObject, data, datalen); if (!FileObject || !FileObject->FsContext || !FileObject->FsContext2 || FileObject->FsContext == Vcb->volume_fcb) return STATUS_INVALID_PARAMETER; if (Vcb->readonly) return STATUS_MEDIA_WRITE_PROTECTED; parfcb = FileObject->FsContext; if (parfcb->type != BTRFS_TYPE_DIRECTORY) { WARN("trying to create file in something other than a directory\n"); return STATUS_INVALID_PARAMETER; } if (is_subvol_readonly(parfcb->subvol, Irp)) return STATUS_ACCESS_DENIED; parccb = FileObject->FsContext2; parfileref = parccb->fileref; if (!parfileref) return STATUS_INVALID_PARAMETER; if (datalen < sizeof(btrfs_mknod)) return STATUS_INVALID_PARAMETER; bmn = (btrfs_mknod*)data; if (datalen < offsetof(btrfs_mknod, name[0]) + bmn->namelen || bmn->namelen < sizeof(WCHAR)) return STATUS_INVALID_PARAMETER; if (bmn->type == BTRFS_TYPE_UNKNOWN || bmn->type > BTRFS_TYPE_SYMLINK) return STATUS_INVALID_PARAMETER; if ((bmn->type == BTRFS_TYPE_DIRECTORY && !(parccb->access & FILE_ADD_SUBDIRECTORY)) || (bmn->type != BTRFS_TYPE_DIRECTORY && !(parccb->access & FILE_ADD_FILE))) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } if (bmn->inode != 0) { if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode)) return STATUS_PRIVILEGE_NOT_HELD; } for (i = 0; i < bmn->namelen / sizeof(WCHAR); i++) { if (bmn->name[i] == 0 || bmn->name[i] == '/') return STATUS_OBJECT_NAME_INVALID; } // don't allow files called . or .. if (bmn->name[0] == '.' && (bmn->namelen == sizeof(WCHAR) || (bmn->namelen == 2 * sizeof(WCHAR) && bmn->name[1] == '.'))) return STATUS_OBJECT_NAME_INVALID; Status = utf16_to_utf8(NULL, 0, &len, bmn->name, bmn->namelen); if (!NT_SUCCESS(Status)) { ERR("utf16_to_utf8 returned %08lx\n", Status); return Status; } if (len == 0) { ERR("utf16_to_utf8 returned a length of 0\n"); return STATUS_INTERNAL_ERROR; } if (len > 0xffff) { ERR("len was too long (%lx)\n", len); return STATUS_INVALID_PARAMETER; } utf8.MaximumLength = utf8.Length = (USHORT)len; utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG); if (!utf8.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } Status = utf16_to_utf8(utf8.Buffer, len, &len, bmn->name, bmn->namelen); if (!NT_SUCCESS(Status)) { ERR("utf16_to_utf8 failed with error %08lx\n", Status); ExFreePool(utf8.Buffer); return Status; } name.Length = name.MaximumLength = bmn->namelen; name.Buffer = bmn->name; Status = find_file_in_dir(&name, parfcb, &subvol, &inode, &dc, true); if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_NOT_FOUND) { ERR("find_file_in_dir returned %08lx\n", Status); goto end; } if (NT_SUCCESS(Status)) { WARN("filename already exists\n"); Status = STATUS_OBJECT_NAME_COLLISION; goto end; } KeQuerySystemTime(&time); win_time_to_unix(time, &now); fcb = create_fcb(Vcb, PagedPool); if (!fcb) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } fcb->Vcb = Vcb; fcb->inode_item.generation = Vcb->superblock.generation; fcb->inode_item.transid = Vcb->superblock.generation; fcb->inode_item.st_size = 0; fcb->inode_item.st_blocks = 0; fcb->inode_item.block_group = 0; fcb->inode_item.st_nlink = 1; fcb->inode_item.st_uid = UID_NOBODY; fcb->inode_item.st_gid = GID_NOBODY; fcb->inode_item.st_mode = inherit_mode(parfcb, bmn->type == BTRFS_TYPE_DIRECTORY); if (bmn->type == BTRFS_TYPE_BLOCKDEV || bmn->type == BTRFS_TYPE_CHARDEV) fcb->inode_item.st_rdev = (minor(bmn->st_rdev) & 0xFFFFF) | ((major(bmn->st_rdev) & 0xFFFFFFFFFFF) << 20); else fcb->inode_item.st_rdev = 0; fcb->inode_item.flags = 0; fcb->inode_item.sequence = 1; fcb->inode_item.st_atime = now; fcb->inode_item.st_ctime = now; fcb->inode_item.st_mtime = now; fcb->inode_item.otime = now; if (bmn->type == BTRFS_TYPE_DIRECTORY) fcb->inode_item.st_mode |= __S_IFDIR; else if (bmn->type == BTRFS_TYPE_CHARDEV) fcb->inode_item.st_mode |= __S_IFCHR; else if (bmn->type == BTRFS_TYPE_BLOCKDEV) fcb->inode_item.st_mode |= __S_IFBLK; else if (bmn->type == BTRFS_TYPE_FIFO) fcb->inode_item.st_mode |= __S_IFIFO; else if (bmn->type == BTRFS_TYPE_SOCKET) fcb->inode_item.st_mode |= __S_IFSOCK; else if (bmn->type == BTRFS_TYPE_SYMLINK) fcb->inode_item.st_mode |= __S_IFLNK; else fcb->inode_item.st_mode |= __S_IFREG; if (bmn->type != BTRFS_TYPE_DIRECTORY) fcb->inode_item.st_mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH); // remove executable bit if not directory // inherit nodatacow flag from parent directory if (parfcb->inode_item.flags & BTRFS_INODE_NODATACOW) { fcb->inode_item.flags |= BTRFS_INODE_NODATACOW; if (bmn->type != BTRFS_TYPE_DIRECTORY) fcb->inode_item.flags |= BTRFS_INODE_NODATASUM; } if (parfcb->inode_item.flags & BTRFS_INODE_COMPRESS) fcb->inode_item.flags |= BTRFS_INODE_COMPRESS; fcb->prop_compression = parfcb->prop_compression; fcb->prop_compression_changed = fcb->prop_compression != PropCompression_None; fcb->inode_item_changed = true; fcb->Header.IsFastIoPossible = fast_io_possible(fcb); fcb->Header.AllocationSize.QuadPart = 0; fcb->Header.FileSize.QuadPart = 0; fcb->Header.ValidDataLength.QuadPart = 0; fcb->atts = 0; if (bmn->name[0] == '.') fcb->atts |= FILE_ATTRIBUTE_HIDDEN; if (bmn->type == BTRFS_TYPE_DIRECTORY) fcb->atts |= FILE_ATTRIBUTE_DIRECTORY; fcb->atts_changed = false; InterlockedIncrement(&parfcb->refcount); fcb->subvol = parfcb->subvol; SeCaptureSubjectContext(&subjcont); Status = SeAssignSecurityEx(parfileref ? parfileref->fcb->sd : NULL, NULL, (void**)&fcb->sd, NULL, fcb->type == BTRFS_TYPE_DIRECTORY, SEF_SACL_AUTO_INHERIT, &subjcont, IoGetFileObjectGenericMapping(), PagedPool); if (!NT_SUCCESS(Status)) { ERR("SeAssignSecurityEx returned %08lx\n", Status); reap_fcb(fcb); goto end; } Status = RtlGetOwnerSecurityDescriptor(fcb->sd, &owner, &defaulted); if (!NT_SUCCESS(Status)) { WARN("RtlGetOwnerSecurityDescriptor returned %08lx\n", Status); fcb->sd_dirty = true; } else { fcb->inode_item.st_uid = sid_to_uid(owner); fcb->sd_dirty = fcb->inode_item.st_uid == UID_NOBODY; } find_gid(fcb, parfcb, &subjcont); ExAcquireResourceExclusiveLite(&Vcb->fileref_lock, true); acquire_fcb_lock_exclusive(Vcb); if (bmn->inode == 0) { fcb->inode = InterlockedIncrement64(&parfcb->subvol->lastinode); fcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&fcb->inode, sizeof(uint64_t)); } else { if (bmn->inode > (uint64_t)parfcb->subvol->lastinode) { fcb->inode = parfcb->subvol->lastinode = bmn->inode; fcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&fcb->inode, sizeof(uint64_t)); } else { uint32_t hash = calc_crc32c(0xffffffff, (uint8_t*)&bmn->inode, sizeof(uint64_t)); Status = check_inode_used(Vcb, subvol, bmn->inode, hash, Irp); if (NT_SUCCESS(Status)) { // STATUS_SUCCESS means inode found release_fcb_lock(Vcb); ExReleaseResourceLite(&Vcb->fileref_lock); WARN("inode collision\n"); Status = STATUS_INVALID_PARAMETER; goto end; } else if (Status != STATUS_NOT_FOUND) { ERR("check_inode_used returned %08lx\n", Status); release_fcb_lock(Vcb); ExReleaseResourceLite(&Vcb->fileref_lock); goto end; } fcb->inode = bmn->inode; fcb->hash = hash; } } fcb->inode = inode; fcb->type = bmn->type; fileref = create_fileref(Vcb); if (!fileref) { release_fcb_lock(Vcb); ExReleaseResourceLite(&Vcb->fileref_lock); ERR("out of memory\n"); reap_fcb(fcb); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } fileref->fcb = fcb; fcb->created = true; fileref->created = true; fcb->subvol->root_item.ctransid = Vcb->superblock.generation; fcb->subvol->root_item.ctime = now; fileref->parent = parfileref; mark_fcb_dirty(fcb); mark_fileref_dirty(fileref); Status = add_dir_child(fileref->parent->fcb, fcb->inode, false, &utf8, &name, fcb->type, &dc); if (!NT_SUCCESS(Status)) WARN("add_dir_child returned %08lx\n", Status); fileref->dc = dc; dc->fileref = fileref; ExAcquireResourceExclusiveLite(&parfileref->fcb->nonpaged->dir_children_lock, true); InsertTailList(&parfileref->children, &fileref->list_entry); ExReleaseResourceLite(&parfileref->fcb->nonpaged->dir_children_lock); increase_fileref_refcount(parfileref); if (fcb->type == BTRFS_TYPE_DIRECTORY) { fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG); if (!fcb->hash_ptrs) { release_fcb_lock(Vcb); ExReleaseResourceLite(&Vcb->fileref_lock); ERR("out of memory\n"); free_fileref(fileref); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlZeroMemory(fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256); fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG); if (!fcb->hash_ptrs_uc) { release_fcb_lock(Vcb); ExReleaseResourceLite(&Vcb->fileref_lock); ERR("out of memory\n"); free_fileref(fileref); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlZeroMemory(fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256); } add_fcb_to_subvol(fcb); InsertTailList(&Vcb->all_fcbs, &fcb->list_entry_all); if (bmn->type == BTRFS_TYPE_DIRECTORY) fileref->fcb->fileref = fileref; ExAcquireResourceExclusiveLite(parfcb->Header.Resource, true); parfcb->inode_item.st_size += utf8.Length * 2; parfcb->inode_item.transid = Vcb->superblock.generation; parfcb->inode_item.sequence++; if (!parccb->user_set_change_time) parfcb->inode_item.st_ctime = now; if (!parccb->user_set_write_time) parfcb->inode_item.st_mtime = now; parfcb->subvol->fcbs_version++; ExReleaseResourceLite(parfcb->Header.Resource); release_fcb_lock(Vcb); ExReleaseResourceLite(&Vcb->fileref_lock); parfcb->inode_item_changed = true; mark_fcb_dirty(parfcb); send_notification_fileref(fileref, bmn->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL); if (!parccb->user_set_write_time) queue_notification_fcb(parfileref, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL); Status = STATUS_SUCCESS; end: ExFreePool(utf8.Buffer); return Status; } static void mark_subvol_dirty(device_extension* Vcb, root* r) { if (!r->dirty) { r->dirty = true; ExAcquireResourceExclusiveLite(&Vcb->dirty_subvols_lock, true); InsertTailList(&Vcb->dirty_subvols, &r->list_entry_dirty); ExReleaseResourceLite(&Vcb->dirty_subvols_lock); } Vcb->need_write = true; } static NTSTATUS recvd_subvol(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, KPROCESSOR_MODE processor_mode) { btrfs_received_subvol* brs = (btrfs_received_subvol*)data; fcb* fcb; NTSTATUS Status; LARGE_INTEGER time; BTRFS_TIME now; TRACE("(%p, %p, %p, %lu)\n", Vcb, FileObject, data, datalen); if (!data || datalen < sizeof(btrfs_received_subvol)) return STATUS_INVALID_PARAMETER; if (!FileObject || !FileObject->FsContext || FileObject->FsContext == Vcb->volume_fcb) return STATUS_INVALID_PARAMETER; fcb = FileObject->FsContext; if (!fcb->subvol) return STATUS_INVALID_PARAMETER; if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; ExAcquireResourceSharedLite(&Vcb->tree_lock, true); if (fcb->subvol->root_item.rtransid != 0) { WARN("subvol already has received information set\n"); Status = STATUS_INVALID_PARAMETER; goto end; } KeQuerySystemTime(&time); win_time_to_unix(time, &now); RtlCopyMemory(&fcb->subvol->root_item.received_uuid, &brs->uuid, sizeof(BTRFS_UUID)); fcb->subvol->root_item.stransid = brs->generation; fcb->subvol->root_item.rtransid = Vcb->superblock.generation; fcb->subvol->root_item.rtime = now; fcb->subvol->received = true; mark_subvol_dirty(Vcb, fcb->subvol); Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS fsctl_get_xattrs(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, KPROCESSOR_MODE processor_mode) { LIST_ENTRY* le; btrfs_set_xattr* bsxa; ULONG reqlen = (ULONG)offsetof(btrfs_set_xattr, data[0]); fcb* fcb; ccb* ccb; if (!data || datalen < reqlen) return STATUS_INVALID_PARAMETER; if (!FileObject || !FileObject->FsContext || !FileObject->FsContext2 || FileObject->FsContext == Vcb->volume_fcb) return STATUS_INVALID_PARAMETER; fcb = FileObject->FsContext; ccb = FileObject->FsContext2; if (!(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES)) && processor_mode == UserMode) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } ExAcquireResourceSharedLite(fcb->Header.Resource, true); le = fcb->xattrs.Flink; while (le != &fcb->xattrs) { xattr* xa = CONTAINING_RECORD(le, xattr, list_entry); if (xa->valuelen > 0) reqlen += (ULONG)offsetof(btrfs_set_xattr, data[0]) + xa->namelen + xa->valuelen; le = le->Flink; } if (datalen < reqlen) { ExReleaseResourceLite(fcb->Header.Resource); return STATUS_BUFFER_OVERFLOW; } bsxa = (btrfs_set_xattr*)data; if (reqlen > 0) { le = fcb->xattrs.Flink; while (le != &fcb->xattrs) { xattr* xa = CONTAINING_RECORD(le, xattr, list_entry); if (xa->valuelen > 0) { bsxa->namelen = xa->namelen; bsxa->valuelen = xa->valuelen; memcpy(bsxa->data, xa->data, xa->namelen + xa->valuelen); bsxa = (btrfs_set_xattr*)&bsxa->data[xa->namelen + xa->valuelen]; } le = le->Flink; } } bsxa->namelen = 0; bsxa->valuelen = 0; ExReleaseResourceLite(fcb->Header.Resource); return STATUS_SUCCESS; } static NTSTATUS fsctl_set_xattr(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, PIRP Irp) { NTSTATUS Status; btrfs_set_xattr* bsxa; xattr* xa; fcb* fcb; ccb* ccb; LIST_ENTRY* le; static const char stream_pref[] = "user."; TRACE("(%p, %p, %p, %lu)\n", Vcb, FileObject, data, datalen); if (!data || datalen < sizeof(btrfs_set_xattr)) return STATUS_INVALID_PARAMETER; bsxa = (btrfs_set_xattr*)data; if (datalen < offsetof(btrfs_set_xattr, data[0]) + bsxa->namelen + bsxa->valuelen) return STATUS_INVALID_PARAMETER; if (bsxa->namelen + bsxa->valuelen + sizeof(tree_header) + sizeof(leaf_node) + offsetof(DIR_ITEM, name[0]) > Vcb->superblock.node_size) return STATUS_INVALID_PARAMETER; if (!FileObject || !FileObject->FsContext || !FileObject->FsContext2 || FileObject->FsContext == Vcb->volume_fcb) return STATUS_INVALID_PARAMETER; if (Vcb->readonly) return STATUS_MEDIA_WRITE_PROTECTED; fcb = FileObject->FsContext; ccb = FileObject->FsContext2; if (is_subvol_readonly(fcb->subvol, Irp)) return STATUS_ACCESS_DENIED; if (!(ccb->access & FILE_WRITE_ATTRIBUTES) && Irp->RequestorMode == UserMode) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (bsxa->namelen == sizeof(EA_NTACL) - 1 && RtlCompareMemory(bsxa->data, EA_NTACL, sizeof(EA_NTACL) - 1) == sizeof(EA_NTACL) - 1) { if ((!(ccb->access & WRITE_DAC) || !(ccb->access & WRITE_OWNER)) && Irp->RequestorMode == UserMode) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; goto end; } if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode)) { Status = STATUS_PRIVILEGE_NOT_HELD; goto end; } if (fcb->sd) ExFreePool(fcb->sd); if (bsxa->valuelen > 0 && RtlValidRelativeSecurityDescriptor(bsxa->data + bsxa->namelen, bsxa->valuelen, 0)) { fcb->sd = ExAllocatePoolWithTag(PagedPool, bsxa->valuelen, ALLOC_TAG); if (!fcb->sd) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(fcb->sd, bsxa->data + bsxa->namelen, bsxa->valuelen); } else if (fcb->sd) fcb->sd = NULL; fcb->sd_dirty = true; if (!fcb->sd) { fcb_get_sd(fcb, ccb->fileref->parent->fcb, false, Irp); fcb->sd_deleted = true; } mark_fcb_dirty(fcb); Status = STATUS_SUCCESS; goto end; } else if (bsxa->namelen == sizeof(EA_DOSATTRIB) - 1 && RtlCompareMemory(bsxa->data, EA_DOSATTRIB, sizeof(EA_DOSATTRIB) - 1) == sizeof(EA_DOSATTRIB) - 1) { ULONG atts; if (bsxa->valuelen > 0 && get_file_attributes_from_xattr(bsxa->data + bsxa->namelen, bsxa->valuelen, &atts)) { fcb->atts = atts; if (fcb->type == BTRFS_TYPE_DIRECTORY) fcb->atts |= FILE_ATTRIBUTE_DIRECTORY; else if (fcb->type == BTRFS_TYPE_SYMLINK) fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT; if (fcb->inode == SUBVOL_ROOT_INODE) { if (fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY) fcb->atts |= FILE_ATTRIBUTE_READONLY; else fcb->atts &= ~FILE_ATTRIBUTE_READONLY; } fcb->atts_deleted = false; } else { bool hidden = ccb->fileref && ccb->fileref->dc && ccb->fileref->dc->utf8.Buffer && ccb->fileref->dc->utf8.Buffer[0] == '.'; fcb->atts = get_file_attributes(Vcb, fcb->subvol, fcb->inode, fcb->type, hidden, true, Irp); fcb->atts_deleted = true; } fcb->atts_changed = true; mark_fcb_dirty(fcb); Status = STATUS_SUCCESS; goto end; } else if (bsxa->namelen == sizeof(EA_REPARSE) - 1 && RtlCompareMemory(bsxa->data, EA_REPARSE, sizeof(EA_REPARSE) - 1) == sizeof(EA_REPARSE) - 1) { if (fcb->reparse_xattr.Buffer) { ExFreePool(fcb->reparse_xattr.Buffer); fcb->reparse_xattr.Buffer = NULL; fcb->reparse_xattr.Length = fcb->reparse_xattr.MaximumLength = 0; } if (bsxa->valuelen > 0) { fcb->reparse_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, bsxa->valuelen, ALLOC_TAG); if (!fcb->reparse_xattr.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(fcb->reparse_xattr.Buffer, bsxa->data + bsxa->namelen, bsxa->valuelen); fcb->reparse_xattr.Length = fcb->reparse_xattr.MaximumLength = bsxa->valuelen; } fcb->reparse_xattr_changed = true; mark_fcb_dirty(fcb); Status = STATUS_SUCCESS; goto end; } else if (bsxa->namelen == sizeof(EA_EA) - 1 && RtlCompareMemory(bsxa->data, EA_EA, sizeof(EA_EA) - 1) == sizeof(EA_EA) - 1) { if (!(ccb->access & FILE_WRITE_EA) && Irp->RequestorMode == UserMode) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; goto end; } if (fcb->ea_xattr.Buffer) { ExFreePool(fcb->ea_xattr.Buffer); fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = 0; fcb->ea_xattr.Buffer = NULL; } fcb->ealen = 0; if (bsxa->valuelen > 0) { ULONG offset; Status = IoCheckEaBufferValidity((FILE_FULL_EA_INFORMATION*)(bsxa->data + bsxa->namelen), bsxa->valuelen, &offset); if (!NT_SUCCESS(Status)) WARN("IoCheckEaBufferValidity returned %08lx (error at offset %lu)\n", Status, offset); else { FILE_FULL_EA_INFORMATION* eainfo; fcb->ea_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, bsxa->valuelen, ALLOC_TAG); if (!fcb->ea_xattr.Buffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlCopyMemory(fcb->ea_xattr.Buffer, bsxa->data + bsxa->namelen, bsxa->valuelen); fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = bsxa->valuelen; fcb->ealen = 4; // calculate ealen eainfo = (FILE_FULL_EA_INFORMATION*)(bsxa->data + bsxa->namelen); do { fcb->ealen += 5 + eainfo->EaNameLength + eainfo->EaValueLength; if (eainfo->NextEntryOffset == 0) break; eainfo = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)eainfo) + eainfo->NextEntryOffset); } while (true); } } fcb->ea_changed = true; mark_fcb_dirty(fcb); Status = STATUS_SUCCESS; goto end; } 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) { if (bsxa->valuelen > 0 && bsxa->data[bsxa->namelen] == '1') { fcb->case_sensitive = true; mark_fcb_dirty(fcb); } Status = STATUS_SUCCESS; goto end; } 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) { static const char lzo[] = "lzo"; static const char zlib[] = "zlib"; static const char zstd[] = "zstd"; if (bsxa->valuelen == sizeof(zstd) - 1 && RtlCompareMemory(bsxa->data + bsxa->namelen, zstd, bsxa->valuelen) == bsxa->valuelen) fcb->prop_compression = PropCompression_ZSTD; else if (bsxa->valuelen == sizeof(lzo) - 1 && RtlCompareMemory(bsxa->data + bsxa->namelen, lzo, bsxa->valuelen) == bsxa->valuelen) fcb->prop_compression = PropCompression_LZO; else if (bsxa->valuelen == sizeof(zlib) - 1 && RtlCompareMemory(bsxa->data + bsxa->namelen, zlib, bsxa->valuelen) == bsxa->valuelen) fcb->prop_compression = PropCompression_Zlib; else fcb->prop_compression = PropCompression_None; if (fcb->prop_compression != PropCompression_None) { fcb->inode_item.flags |= BTRFS_INODE_COMPRESS; fcb->inode_item_changed = true; } fcb->prop_compression_changed = true; mark_fcb_dirty(fcb); Status = STATUS_SUCCESS; goto end; } else if (bsxa->namelen >= (sizeof(stream_pref) - 1) && RtlCompareMemory(bsxa->data, stream_pref, sizeof(stream_pref) - 1) == sizeof(stream_pref) - 1) { // don't allow xattrs beginning with user., as these appear as streams instead Status = STATUS_OBJECT_NAME_INVALID; goto end; } xa = ExAllocatePoolWithTag(PagedPool, offsetof(xattr, data[0]) + bsxa->namelen + bsxa->valuelen, ALLOC_TAG); if (!xa) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } le = fcb->xattrs.Flink; while (le != &fcb->xattrs) { xattr* xa2 = CONTAINING_RECORD(le, xattr, list_entry); if (xa2->namelen == bsxa->namelen && RtlCompareMemory(xa2->data, bsxa->data, xa2->namelen) == xa2->namelen) { RemoveEntryList(&xa2->list_entry); ExFreePool(xa2); break; } le = le->Flink; } xa->namelen = bsxa->namelen; xa->valuelen = bsxa->valuelen; xa->dirty = true; RtlCopyMemory(xa->data, bsxa->data, bsxa->namelen + bsxa->valuelen); InsertTailList(&fcb->xattrs, &xa->list_entry); fcb->xattrs_changed = true; mark_fcb_dirty(fcb); Status = STATUS_SUCCESS; end: ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS reserve_subvol(device_extension* Vcb, PFILE_OBJECT FileObject, PIRP Irp) { fcb* fcb; ccb* ccb; TRACE("(%p, %p)\n", Vcb, FileObject); // "Reserving" a readonly subvol allows the calling process to write into it until the handle is closed. if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode)) return STATUS_PRIVILEGE_NOT_HELD; if (!FileObject || !FileObject->FsContext || !FileObject->FsContext2 || FileObject->FsContext == Vcb->volume_fcb) return STATUS_INVALID_PARAMETER; fcb = FileObject->FsContext; ccb = FileObject->FsContext2; if (!(fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY)) return STATUS_INVALID_PARAMETER; if (fcb->subvol->reserved) return STATUS_INVALID_PARAMETER; fcb->subvol->reserved = PsGetCurrentProcess(); ccb->reserving = true; return STATUS_SUCCESS; } static NTSTATUS get_subvol_path(device_extension* Vcb, uint64_t id, WCHAR* out, ULONG outlen, PIRP Irp) { LIST_ENTRY* le; root* r = NULL; NTSTATUS Status; file_ref* fr; UNICODE_STRING us; le = Vcb->roots.Flink; while (le != &Vcb->roots) { root* r2 = CONTAINING_RECORD(le, root, list_entry); if (r2->id == id) { r = r2; break; } le = le->Flink; } if (!r) { ERR("couldn't find subvol %I64x\n", id); return STATUS_INTERNAL_ERROR; } ExAcquireResourceExclusiveLite(&Vcb->fileref_lock, true); Status = open_fileref_by_inode(Vcb, r, r->root_item.objid, &fr, Irp); if (!NT_SUCCESS(Status)) { ExReleaseResourceLite(&Vcb->fileref_lock); ERR("open_fileref_by_inode returned %08lx\n", Status); return Status; } us.Buffer = out; us.Length = 0; us.MaximumLength = (USHORT)min(0xffff, outlen) - sizeof(WCHAR); Status = fileref_get_filename(fr, &us, NULL, NULL); if (NT_SUCCESS(Status) || Status == STATUS_BUFFER_OVERFLOW) out[us.Length / sizeof(WCHAR)] = 0; else ERR("fileref_get_filename returned %08lx\n", Status); free_fileref(fr); ExReleaseResourceLite(&Vcb->fileref_lock); return Status; } static NTSTATUS find_subvol(device_extension* Vcb, void* in, ULONG inlen, void* out, ULONG outlen, PIRP Irp) { btrfs_find_subvol* bfs; NTSTATUS Status; traverse_ptr tp; KEY searchkey; if (!in || inlen < sizeof(btrfs_find_subvol)) return STATUS_INVALID_PARAMETER; if (!out || outlen < sizeof(WCHAR)) return STATUS_INVALID_PARAMETER; if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode)) return STATUS_PRIVILEGE_NOT_HELD; bfs = (btrfs_find_subvol*)in; ExAcquireResourceSharedLite(&Vcb->tree_lock, true); if (!Vcb->uuid_root) { ERR("couldn't find uuid root\n"); Status = STATUS_NOT_FOUND; goto end; } RtlCopyMemory(&searchkey.obj_id, &bfs->uuid, sizeof(uint64_t)); searchkey.obj_type = TYPE_SUBVOL_UUID; RtlCopyMemory(&searchkey.offset, &bfs->uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t)); Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); goto end; } if (!keycmp(searchkey, tp.item->key) && tp.item->size >= sizeof(uint64_t)) { uint64_t* id = (uint64_t*)tp.item->data; if (bfs->ctransid != 0) { KEY searchkey2; traverse_ptr tp2; searchkey2.obj_id = *id; searchkey2.obj_type = TYPE_ROOT_ITEM; searchkey2.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->root_root, &tp2, &searchkey2, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); goto end; } if (tp2.item->key.obj_id == searchkey2.obj_id && tp2.item->key.obj_type == searchkey2.obj_type && tp2.item->size >= offsetof(ROOT_ITEM, otransid)) { ROOT_ITEM* ri = (ROOT_ITEM*)tp2.item->data; if (ri->ctransid == bfs->ctransid) { TRACE("found subvol %I64x\n", *id); Status = get_subvol_path(Vcb, *id, out, outlen, Irp); goto end; } } } else { TRACE("found subvol %I64x\n", *id); Status = get_subvol_path(Vcb, *id, out, outlen, Irp); goto end; } } searchkey.obj_type = TYPE_SUBVOL_REC_UUID; Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); goto end; } if (!keycmp(searchkey, tp.item->key) && tp.item->size >= sizeof(uint64_t)) { uint64_t* ids = (uint64_t*)tp.item->data; ULONG i; for (i = 0; i < tp.item->size / sizeof(uint64_t); i++) { if (bfs->ctransid != 0) { KEY searchkey2; traverse_ptr tp2; searchkey2.obj_id = ids[i]; searchkey2.obj_type = TYPE_ROOT_ITEM; searchkey2.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->root_root, &tp2, &searchkey2, false, Irp); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); goto end; } if (tp2.item->key.obj_id == searchkey2.obj_id && tp2.item->key.obj_type == searchkey2.obj_type && tp2.item->size >= offsetof(ROOT_ITEM, otransid)) { ROOT_ITEM* ri = (ROOT_ITEM*)tp2.item->data; if (ri->ctransid == bfs->ctransid) { TRACE("found subvol %I64x\n", ids[i]); Status = get_subvol_path(Vcb, ids[i], out, outlen, Irp); goto end; } } } else { TRACE("found subvol %I64x\n", ids[i]); Status = get_subvol_path(Vcb, ids[i], out, outlen, Irp); goto end; } } } Status = STATUS_NOT_FOUND; end: ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS resize_device(device_extension* Vcb, void* data, ULONG len, PIRP Irp) { btrfs_resize* br = (btrfs_resize*)data; NTSTATUS Status; LIST_ENTRY* le; device* dev = NULL; TRACE("(%p, %p, %lu)\n", Vcb, data, len); if (!data || len < sizeof(btrfs_resize) || (br->size & (Vcb->superblock.sector_size - 1)) != 0) return STATUS_INVALID_PARAMETER; if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode)) return STATUS_PRIVILEGE_NOT_HELD; if (Vcb->readonly) return STATUS_MEDIA_WRITE_PROTECTED; ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev2 = CONTAINING_RECORD(le, device, list_entry); if (dev2->devitem.dev_id == br->device) { dev = dev2; break; } le = le->Flink; } if (!dev) { ERR("could not find device %I64x\n", br->device); Status = STATUS_INVALID_PARAMETER; goto end; } if (!dev->devobj) { ERR("trying to resize missing device\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (dev->readonly) { ERR("trying to resize readonly device\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (br->size > 0 && dev->devitem.num_bytes == br->size) { TRACE("size unchanged, returning STATUS_SUCCESS\n"); Status = STATUS_SUCCESS; goto end; } if (br->size > 0 && dev->devitem.num_bytes > br->size) { // shrink device bool need_balance = true; uint64_t old_size, delta; le = dev->space.Flink; while (le != &dev->space) { space* s = CONTAINING_RECORD(le, space, list_entry); if (s->address <= br->size && s->address + s->size >= dev->devitem.num_bytes) { need_balance = false; break; } le = le->Flink; } delta = dev->devitem.num_bytes - br->size; if (need_balance) { OBJECT_ATTRIBUTES oa; int i; if (Vcb->balance.thread) { WARN("balance already running\n"); Status = STATUS_DEVICE_NOT_READY; goto end; } RtlZeroMemory(Vcb->balance.opts, sizeof(btrfs_balance_opts) * 3); for (i = 0; i < 3; i++) { Vcb->balance.opts[i].flags = BTRFS_BALANCE_OPTS_ENABLED | BTRFS_BALANCE_OPTS_DEVID | BTRFS_BALANCE_OPTS_DRANGE; Vcb->balance.opts[i].devid = dev->devitem.dev_id; Vcb->balance.opts[i].drange_start = br->size; Vcb->balance.opts[i].drange_end = dev->devitem.num_bytes; } Vcb->balance.paused = false; Vcb->balance.shrinking = true; Vcb->balance.status = STATUS_SUCCESS; KeInitializeEvent(&Vcb->balance.event, NotificationEvent, !Vcb->balance.paused); space_list_subtract2(&dev->space, NULL, br->size, delta, NULL, NULL); InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); Status = PsCreateSystemThread(&Vcb->balance.thread, 0, &oa, NULL, NULL, balance_thread, Vcb); if (!NT_SUCCESS(Status)) { ERR("PsCreateSystemThread returned %08lx\n", Status); goto end; } Status = STATUS_MORE_PROCESSING_REQUIRED; goto end; } old_size = dev->devitem.num_bytes; dev->devitem.num_bytes = br->size; Status = update_dev_item(Vcb, dev, Irp); if (!NT_SUCCESS(Status)) { ERR("update_dev_item returned %08lx\n", Status); dev->devitem.num_bytes = old_size; goto end; } space_list_subtract2(&dev->space, NULL, br->size, delta, NULL, NULL); Vcb->superblock.total_bytes -= delta; } else { // extend device GET_LENGTH_INFORMATION gli; uint64_t old_size, delta; Status = dev_ioctl(dev->devobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli), true, NULL); if (!NT_SUCCESS(Status)) { ERR("IOCTL_DISK_GET_LENGTH_INFO returned %08lx\n", Status); goto end; } if (br->size == 0) { br->size = gli.Length.QuadPart; if (dev->devitem.num_bytes == br->size) { TRACE("size unchanged, returning STATUS_SUCCESS\n"); Status = STATUS_SUCCESS; goto end; } if (br->size == 0) { ERR("IOCTL_DISK_GET_LENGTH_INFO returned 0 length\n"); Status = STATUS_INTERNAL_ERROR; goto end; } } else if ((uint64_t)gli.Length.QuadPart < br->size) { ERR("device was %I64x bytes, trying to extend to %I64x\n", gli.Length.QuadPart, br->size); Status = STATUS_INVALID_PARAMETER; goto end; } delta = br->size - dev->devitem.num_bytes; old_size = dev->devitem.num_bytes; dev->devitem.num_bytes = br->size; Status = update_dev_item(Vcb, dev, Irp); if (!NT_SUCCESS(Status)) { ERR("update_dev_item returned %08lx\n", Status); dev->devitem.num_bytes = old_size; goto end; } space_list_add2(&dev->space, NULL, dev->devitem.num_bytes, delta, NULL, NULL); Vcb->superblock.total_bytes += delta; } Status = STATUS_SUCCESS; Vcb->need_write = true; end: ExReleaseResourceLite(&Vcb->tree_lock); if (NT_SUCCESS(Status)) FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_CHANGE_SIZE); return Status; } static NTSTATUS fsctl_oplock(device_extension* Vcb, PIRP* Pirp) { NTSTATUS Status; PIRP Irp = *Pirp; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); uint32_t fsctl = IrpSp->Parameters.FileSystemControl.FsControlCode; PFILE_OBJECT FileObject = IrpSp->FileObject; fcb* fcb = FileObject ? FileObject->FsContext : NULL; ccb* ccb = FileObject ? FileObject->FsContext2 : NULL; file_ref* fileref = ccb ? ccb->fileref : NULL; PREQUEST_OPLOCK_INPUT_BUFFER buf = NULL; bool oplock_request = false, oplock_ack = false; ULONG oplock_count = 0; if (!fcb) { ERR("fcb was NULL\n"); return STATUS_INVALID_PARAMETER; } if (!fileref) { ERR("fileref was NULL\n"); return STATUS_INVALID_PARAMETER; } if (fcb->type != BTRFS_TYPE_FILE && fcb->type != BTRFS_TYPE_DIRECTORY) return STATUS_INVALID_PARAMETER; if (fsctl == FSCTL_REQUEST_OPLOCK) { if (IrpSp->Parameters.FileSystemControl.InputBufferLength < sizeof(REQUEST_OPLOCK_INPUT_BUFFER)) return STATUS_BUFFER_TOO_SMALL; if (IrpSp->Parameters.FileSystemControl.OutputBufferLength < sizeof(REQUEST_OPLOCK_OUTPUT_BUFFER)) return STATUS_BUFFER_TOO_SMALL; buf = Irp->AssociatedIrp.SystemBuffer; // flags are mutually exclusive if (buf->Flags & REQUEST_OPLOCK_INPUT_FLAG_REQUEST && buf->Flags & REQUEST_OPLOCK_INPUT_FLAG_ACK) return STATUS_INVALID_PARAMETER; oplock_request = buf->Flags & REQUEST_OPLOCK_INPUT_FLAG_REQUEST; oplock_ack = buf->Flags & REQUEST_OPLOCK_INPUT_FLAG_ACK; if (!oplock_request && !oplock_ack) return STATUS_INVALID_PARAMETER; } bool shared_request = (fsctl == FSCTL_REQUEST_OPLOCK_LEVEL_2) || (fsctl == FSCTL_REQUEST_OPLOCK && !(buf->RequestedOplockLevel & OPLOCK_LEVEL_CACHE_WRITE)); if (fcb->type == BTRFS_TYPE_DIRECTORY && (fsctl != FSCTL_REQUEST_OPLOCK || !shared_request)) { WARN("oplock requests on directories can only be for read or read-handle oplocks\n"); return STATUS_INVALID_PARAMETER; } ExAcquireResourceSharedLite(&Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (fsctl == FSCTL_REQUEST_OPLOCK_LEVEL_1 || fsctl == FSCTL_REQUEST_BATCH_OPLOCK || fsctl == FSCTL_REQUEST_FILTER_OPLOCK || fsctl == FSCTL_REQUEST_OPLOCK_LEVEL_2 || oplock_request) { if (shared_request) { if (fcb->type == BTRFS_TYPE_FILE) { if (fFsRtlCheckLockForOplockRequest) oplock_count = !fFsRtlCheckLockForOplockRequest(&fcb->lock, &fcb->Header.AllocationSize); else if (fFsRtlAreThereCurrentOrInProgressFileLocks) oplock_count = fFsRtlAreThereCurrentOrInProgressFileLocks(&fcb->lock); else oplock_count = FsRtlAreThereCurrentFileLocks(&fcb->lock); } } else oplock_count = fileref->open_count; } if ((fsctl == FSCTL_REQUEST_FILTER_OPLOCK || fsctl == FSCTL_REQUEST_BATCH_OPLOCK || (fsctl == FSCTL_REQUEST_OPLOCK && buf->RequestedOplockLevel & OPLOCK_LEVEL_CACHE_HANDLE)) && fileref->delete_on_close) { ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&Vcb->tree_lock); return STATUS_DELETE_PENDING; } Status = FsRtlOplockFsctrl(fcb_oplock(fcb), Irp, oplock_count); *Pirp = NULL; fcb->Header.IsFastIoPossible = fast_io_possible(fcb); ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&Vcb->tree_lock); return Status; } static NTSTATUS get_retrieval_pointers(device_extension* Vcb, PFILE_OBJECT FileObject, STARTING_VCN_INPUT_BUFFER* in, ULONG inlen, RETRIEVAL_POINTERS_BUFFER* out, ULONG outlen, ULONG_PTR* retlen) { NTSTATUS Status; fcb* fcb; TRACE("get_retrieval_pointers(%p, %p, %p, %lx, %p, %lx, %p)\n", Vcb, FileObject, in, inlen, out, outlen, retlen); if (!FileObject) return STATUS_INVALID_PARAMETER; fcb = FileObject->FsContext; if (!fcb) return STATUS_INVALID_PARAMETER; if (inlen < sizeof(STARTING_VCN_INPUT_BUFFER) || in->StartingVcn.QuadPart < 0) return STATUS_INVALID_PARAMETER; if (!out) return STATUS_INVALID_PARAMETER; if (outlen < offsetof(RETRIEVAL_POINTERS_BUFFER, Extents[0])) return STATUS_BUFFER_TOO_SMALL; ExAcquireResourceSharedLite(fcb->Header.Resource, true); try { LIST_ENTRY* le = fcb->extents.Flink; extent* first_ext = NULL; unsigned int num_extents = 0, first_extent_num = 0, i; uint64_t num_sectors, last_off = 0; num_sectors = (fcb->inode_item.st_size + Vcb->superblock.sector_size - 1) >> Vcb->sector_shift; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (ext->ignore || ext->extent_data.type == EXTENT_TYPE_INLINE) { le = le->Flink; continue; } if (ext->offset > last_off) num_extents++; if ((ext->offset >> Vcb->sector_shift) <= (uint64_t)in->StartingVcn.QuadPart && (ext->offset + ext->extent_data.decoded_size) >> Vcb->sector_shift > (uint64_t)in->StartingVcn.QuadPart) { first_ext = ext; first_extent_num = num_extents; } num_extents++; last_off = ext->offset + ext->extent_data.decoded_size; le = le->Flink; } if (num_sectors > last_off >> Vcb->sector_shift) num_extents++; if (!first_ext) { Status = STATUS_END_OF_FILE; leave; } out->ExtentCount = num_extents - first_extent_num; out->StartingVcn.QuadPart = first_ext->offset >> Vcb->sector_shift; outlen -= offsetof(RETRIEVAL_POINTERS_BUFFER, Extents[0]); *retlen = offsetof(RETRIEVAL_POINTERS_BUFFER, Extents[0]); le = &first_ext->list_entry; i = 0; last_off = 0; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (ext->ignore || ext->extent_data.type == EXTENT_TYPE_INLINE) { le = le->Flink; continue; } if (ext->offset > last_off) { if (outlen < sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER)) { Status = STATUS_BUFFER_OVERFLOW; leave; } out->Extents[i].NextVcn.QuadPart = ext->offset >> Vcb->sector_shift; out->Extents[i].Lcn.QuadPart = -1; outlen -= sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER); *retlen += sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER); i++; } if (outlen < sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER)) { Status = STATUS_BUFFER_OVERFLOW; leave; } out->Extents[i].NextVcn.QuadPart = (ext->offset + ext->extent_data.decoded_size) >> Vcb->sector_shift; if (ext->extent_data.compression == BTRFS_COMPRESSION_NONE) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data; out->Extents[i].Lcn.QuadPart = (ed2->address + ed2->offset) >> Vcb->sector_shift; } else out->Extents[i].Lcn.QuadPart = -1; outlen -= sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER); *retlen += sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER); i++; le = le->Flink; } if (num_sectors << Vcb->sector_shift > last_off) { if (outlen < sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER)) { Status = STATUS_BUFFER_OVERFLOW; leave; } out->Extents[i].NextVcn.QuadPart = num_sectors; out->Extents[i].Lcn.QuadPart = -1; outlen -= sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER); *retlen += sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER); } Status = STATUS_SUCCESS; } finally { ExReleaseResourceLite(fcb->Header.Resource); } return Status; } static NTSTATUS add_csum_sparse_extents(device_extension* Vcb, uint64_t sparse_extents, uint8_t** ptr, bool found, void* hash_ptr) { if (!found) { uint8_t* sector = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.sector_size, ALLOC_TAG); if (!sector) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } memset(sector, 0, Vcb->superblock.sector_size); get_sector_csum(Vcb, sector, hash_ptr); ExFreePool(sector); } switch (Vcb->superblock.csum_type) { case CSUM_TYPE_CRC32C: { uint32_t* csum = (uint32_t*)*ptr; uint32_t sparse_hash = *(uint32_t*)hash_ptr; for (uint64_t i = 0; i < sparse_extents; i++) { csum[i] = sparse_hash; } break; } case CSUM_TYPE_XXHASH: { uint64_t* csum = (uint64_t*)*ptr; uint64_t sparse_hash = *(uint64_t*)hash_ptr; for (uint64_t i = 0; i < sparse_extents; i++) { csum[i] = sparse_hash; } break; } case CSUM_TYPE_SHA256: case CSUM_TYPE_BLAKE2: { uint8_t* csum = (uint8_t*)*ptr; for (uint64_t i = 0; i < sparse_extents; i++) { memcpy(csum, hash_ptr, 32); csum += 32; } break; } default: ERR("unrecognized hash type %x\n", Vcb->superblock.csum_type); return STATUS_INTERNAL_ERROR; } *ptr += sparse_extents * Vcb->csum_size; return STATUS_SUCCESS; } static NTSTATUS get_csum_info(device_extension* Vcb, PFILE_OBJECT FileObject, btrfs_csum_info* buf, ULONG buflen, ULONG_PTR* retlen, KPROCESSOR_MODE processor_mode) { NTSTATUS Status; fcb* fcb; ccb* ccb; TRACE("get_csum_info(%p, %p, %p, %lx, %p, %x)\n", Vcb, FileObject, buf, buflen, retlen, processor_mode); if (!FileObject) return STATUS_INVALID_PARAMETER; fcb = FileObject->FsContext; ccb = FileObject->FsContext2; if (!fcb || !ccb) return STATUS_INVALID_PARAMETER; if (!buf) return STATUS_INVALID_PARAMETER; if (buflen < offsetof(btrfs_csum_info, data[0])) return STATUS_BUFFER_TOO_SMALL; if (processor_mode == UserMode && !(ccb->access & (FILE_READ_DATA | FILE_WRITE_DATA))) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } ExAcquireResourceSharedLite(fcb->Header.Resource, true); try { LIST_ENTRY* le; uint8_t* ptr; uint64_t last_off; uint8_t sparse_hash[MAX_HASH_SIZE]; bool sparse_hash_found = false; if (fcb->ads) { Status = STATUS_INVALID_DEVICE_REQUEST; leave; } if (fcb->type == BTRFS_TYPE_DIRECTORY) { Status = STATUS_FILE_IS_A_DIRECTORY; leave; } if (fcb->inode_item.flags & BTRFS_INODE_NODATASUM) { Status = STATUS_INVALID_DEVICE_REQUEST; leave; } buf->csum_type = Vcb->superblock.csum_type; buf->csum_length = Vcb->csum_size; le = fcb->extents.Flink; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (ext->ignore) { le = le->Flink; continue; } if (ext->extent_data.type == EXTENT_TYPE_INLINE) { buf->num_sectors = 0; *retlen = offsetof(btrfs_csum_info, data[0]); Status = STATUS_SUCCESS; leave; } le = le->Flink; } buf->num_sectors = (fcb->inode_item.st_size + Vcb->superblock.sector_size - 1) >> Vcb->sector_shift; if (buflen < offsetof(btrfs_csum_info, data[0]) + (buf->csum_length * buf->num_sectors)) { Status = STATUS_BUFFER_OVERFLOW; *retlen = offsetof(btrfs_csum_info, data[0]); leave; } ptr = buf->data; last_off = 0; le = fcb->extents.Flink; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); EXTENT_DATA2* ed2; if (ext->ignore || ext->extent_data.type == EXTENT_TYPE_INLINE) { le = le->Flink; continue; } if (ext->offset > last_off) { uint64_t sparse_extents = (ext->offset - last_off) >> Vcb->sector_shift; add_csum_sparse_extents(Vcb, sparse_extents, &ptr, sparse_hash_found, sparse_hash); sparse_hash_found = true; } ed2 = (EXTENT_DATA2*)ext->extent_data.data; if (ext->extent_data.compression != BTRFS_COMPRESSION_NONE) memset(ptr, 0, (ed2->num_bytes >> Vcb->sector_shift) * Vcb->csum_size); // dummy value for compressed extents else { if (ext->csum) memcpy(ptr, ext->csum, (ed2->num_bytes >> Vcb->sector_shift) * Vcb->csum_size); else memset(ptr, 0, (ed2->num_bytes >> Vcb->sector_shift) * Vcb->csum_size); ptr += (ed2->num_bytes >> Vcb->sector_shift) * Vcb->csum_size; } last_off = ext->offset + ed2->num_bytes; le = le->Flink; } if (buf->num_sectors > last_off >> Vcb->sector_shift) { uint64_t sparse_extents = buf->num_sectors - (last_off >> Vcb->sector_shift); add_csum_sparse_extents(Vcb, sparse_extents, &ptr, sparse_hash_found, sparse_hash); } *retlen = offsetof(btrfs_csum_info, data[0]) + (buf->csum_length * buf->num_sectors); Status = STATUS_SUCCESS; } finally { ExReleaseResourceLite(fcb->Header.Resource); } return Status; } NTSTATUS fsctl_request(PDEVICE_OBJECT DeviceObject, PIRP* Pirp, uint32_t type) { PIRP Irp = *Pirp; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); NTSTATUS Status; if (IrpSp->FileObject && IrpSp->FileObject->FsContext) { device_extension* Vcb = DeviceObject->DeviceExtension; if (Vcb->type == VCB_TYPE_FS) FsRtlCheckOplock(fcb_oplock(IrpSp->FileObject->FsContext), Irp, NULL, NULL, NULL); } switch (type) { case FSCTL_REQUEST_OPLOCK_LEVEL_1: case FSCTL_REQUEST_OPLOCK_LEVEL_2: case FSCTL_REQUEST_BATCH_OPLOCK: case FSCTL_OPLOCK_BREAK_ACKNOWLEDGE: case FSCTL_OPBATCH_ACK_CLOSE_PENDING: case FSCTL_OPLOCK_BREAK_NOTIFY: case FSCTL_OPLOCK_BREAK_ACK_NO_2: case FSCTL_REQUEST_FILTER_OPLOCK: case FSCTL_REQUEST_OPLOCK: Status = fsctl_oplock(DeviceObject->DeviceExtension, Pirp); break; case FSCTL_LOCK_VOLUME: Status = lock_volume(DeviceObject->DeviceExtension, Irp); break; case FSCTL_UNLOCK_VOLUME: Status = unlock_volume(DeviceObject->DeviceExtension, Irp); break; case FSCTL_DISMOUNT_VOLUME: Status = dismount_volume(DeviceObject->DeviceExtension, false, Irp); break; case FSCTL_IS_VOLUME_MOUNTED: Status = is_volume_mounted(DeviceObject->DeviceExtension, Irp); break; case FSCTL_IS_PATHNAME_VALID: WARN("STUB: FSCTL_IS_PATHNAME_VALID\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_MARK_VOLUME_DIRTY: WARN("STUB: FSCTL_MARK_VOLUME_DIRTY\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_QUERY_RETRIEVAL_POINTERS: WARN("STUB: FSCTL_QUERY_RETRIEVAL_POINTERS\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_GET_COMPRESSION: Status = get_compression(Irp); break; case FSCTL_SET_COMPRESSION: Status = set_compression(Irp); break; case FSCTL_SET_BOOTLOADER_ACCESSED: WARN("STUB: FSCTL_SET_BOOTLOADER_ACCESSED\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_INVALIDATE_VOLUMES: Status = invalidate_volumes(Irp); break; case FSCTL_QUERY_FAT_BPB: WARN("STUB: FSCTL_QUERY_FAT_BPB\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_FILESYSTEM_GET_STATISTICS: Status = fs_get_statistics(Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength, &Irp->IoStatus.Information); break; case FSCTL_GET_NTFS_VOLUME_DATA: WARN("STUB: FSCTL_GET_NTFS_VOLUME_DATA\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_GET_NTFS_FILE_RECORD: WARN("STUB: FSCTL_GET_NTFS_FILE_RECORD\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_GET_VOLUME_BITMAP: WARN("STUB: FSCTL_GET_VOLUME_BITMAP\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_GET_RETRIEVAL_POINTERS: Status = get_retrieval_pointers(DeviceObject->DeviceExtension, IrpSp->FileObject, IrpSp->Parameters.FileSystemControl.Type3InputBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->UserBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength, &Irp->IoStatus.Information); break; case FSCTL_MOVE_FILE: WARN("STUB: FSCTL_MOVE_FILE\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_IS_VOLUME_DIRTY: Status = is_volume_dirty(DeviceObject->DeviceExtension, Irp); break; case FSCTL_ALLOW_EXTENDED_DASD_IO: Status = allow_extended_dasd_io(DeviceObject->DeviceExtension, IrpSp->FileObject); break; case FSCTL_FIND_FILES_BY_SID: WARN("STUB: FSCTL_FIND_FILES_BY_SID\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_SET_OBJECT_ID: WARN("STUB: FSCTL_SET_OBJECT_ID\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_GET_OBJECT_ID: Status = get_object_id(IrpSp->FileObject, Irp->UserBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength, &Irp->IoStatus.Information); break; case FSCTL_DELETE_OBJECT_ID: WARN("STUB: FSCTL_DELETE_OBJECT_ID\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_SET_REPARSE_POINT: Status = set_reparse_point(Irp); break; case FSCTL_GET_REPARSE_POINT: Status = get_reparse_point(IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength, &Irp->IoStatus.Information); break; case FSCTL_DELETE_REPARSE_POINT: Status = delete_reparse_point(Irp); break; case FSCTL_ENUM_USN_DATA: WARN("STUB: FSCTL_ENUM_USN_DATA\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_SECURITY_ID_CHECK: WARN("STUB: FSCTL_SECURITY_ID_CHECK\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_READ_USN_JOURNAL: WARN("STUB: FSCTL_READ_USN_JOURNAL\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_SET_OBJECT_ID_EXTENDED: WARN("STUB: FSCTL_SET_OBJECT_ID_EXTENDED\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_CREATE_OR_GET_OBJECT_ID: Status = get_object_id(IrpSp->FileObject, Irp->UserBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength, &Irp->IoStatus.Information); break; case FSCTL_SET_SPARSE: Status = set_sparse(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp); break; case FSCTL_SET_ZERO_DATA: Status = set_zero_data(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp); break; case FSCTL_QUERY_ALLOCATED_RANGES: Status = query_ranges(IrpSp->FileObject, IrpSp->Parameters.FileSystemControl.Type3InputBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->UserBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength, &Irp->IoStatus.Information); break; case FSCTL_ENABLE_UPGRADE: WARN("STUB: FSCTL_ENABLE_UPGRADE\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_SET_ENCRYPTION: WARN("STUB: FSCTL_SET_ENCRYPTION\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_ENCRYPTION_FSCTL_IO: WARN("STUB: FSCTL_ENCRYPTION_FSCTL_IO\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_WRITE_RAW_ENCRYPTED: WARN("STUB: FSCTL_WRITE_RAW_ENCRYPTED\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_READ_RAW_ENCRYPTED: WARN("STUB: FSCTL_READ_RAW_ENCRYPTED\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_CREATE_USN_JOURNAL: WARN("STUB: FSCTL_CREATE_USN_JOURNAL\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_READ_FILE_USN_DATA: WARN("STUB: FSCTL_READ_FILE_USN_DATA\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_WRITE_USN_CLOSE_RECORD: WARN("STUB: FSCTL_WRITE_USN_CLOSE_RECORD\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_EXTEND_VOLUME: WARN("STUB: FSCTL_EXTEND_VOLUME\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_QUERY_USN_JOURNAL: WARN("STUB: FSCTL_QUERY_USN_JOURNAL\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_DELETE_USN_JOURNAL: WARN("STUB: FSCTL_DELETE_USN_JOURNAL\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_MARK_HANDLE: WARN("STUB: FSCTL_MARK_HANDLE\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_SIS_COPYFILE: WARN("STUB: FSCTL_SIS_COPYFILE\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_SIS_LINK_FILES: WARN("STUB: FSCTL_SIS_LINK_FILES\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_RECALL_FILE: WARN("STUB: FSCTL_RECALL_FILE\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_READ_FROM_PLEX: WARN("STUB: FSCTL_READ_FROM_PLEX\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_FILE_PREFETCH: WARN("STUB: FSCTL_FILE_PREFETCH\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; #if _WIN32_WINNT >= 0x0600 case FSCTL_MAKE_MEDIA_COMPATIBLE: WARN("STUB: FSCTL_MAKE_MEDIA_COMPATIBLE\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_SET_DEFECT_MANAGEMENT: WARN("STUB: FSCTL_SET_DEFECT_MANAGEMENT\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_QUERY_SPARING_INFO: WARN("STUB: FSCTL_QUERY_SPARING_INFO\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_QUERY_ON_DISK_VOLUME_INFO: WARN("STUB: FSCTL_QUERY_ON_DISK_VOLUME_INFO\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_SET_VOLUME_COMPRESSION_STATE: WARN("STUB: FSCTL_SET_VOLUME_COMPRESSION_STATE\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_MODIFY_RM: WARN("STUB: FSCTL_TXFS_MODIFY_RM\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_QUERY_RM_INFORMATION: WARN("STUB: FSCTL_TXFS_QUERY_RM_INFORMATION\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_ROLLFORWARD_REDO: WARN("STUB: FSCTL_TXFS_ROLLFORWARD_REDO\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_ROLLFORWARD_UNDO: WARN("STUB: FSCTL_TXFS_ROLLFORWARD_UNDO\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_START_RM: WARN("STUB: FSCTL_TXFS_START_RM\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_SHUTDOWN_RM: WARN("STUB: FSCTL_TXFS_SHUTDOWN_RM\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_READ_BACKUP_INFORMATION: WARN("STUB: FSCTL_TXFS_READ_BACKUP_INFORMATION\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_WRITE_BACKUP_INFORMATION: WARN("STUB: FSCTL_TXFS_WRITE_BACKUP_INFORMATION\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_CREATE_SECONDARY_RM: WARN("STUB: FSCTL_TXFS_CREATE_SECONDARY_RM\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_GET_METADATA_INFO: WARN("STUB: FSCTL_TXFS_GET_METADATA_INFO\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_GET_TRANSACTED_VERSION: WARN("STUB: FSCTL_TXFS_GET_TRANSACTED_VERSION\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_SAVEPOINT_INFORMATION: WARN("STUB: FSCTL_TXFS_SAVEPOINT_INFORMATION\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_CREATE_MINIVERSION: WARN("STUB: FSCTL_TXFS_CREATE_MINIVERSION\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_TRANSACTION_ACTIVE: WARN("STUB: FSCTL_TXFS_TRANSACTION_ACTIVE\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_SET_ZERO_ON_DEALLOCATION: WARN("STUB: FSCTL_SET_ZERO_ON_DEALLOCATION\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_SET_REPAIR: WARN("STUB: FSCTL_SET_REPAIR\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_GET_REPAIR: WARN("STUB: FSCTL_GET_REPAIR\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_WAIT_FOR_REPAIR: WARN("STUB: FSCTL_WAIT_FOR_REPAIR\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_INITIATE_REPAIR: WARN("STUB: FSCTL_INITIATE_REPAIR\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_CSC_INTERNAL: WARN("STUB: FSCTL_CSC_INTERNAL\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_SHRINK_VOLUME: WARN("STUB: FSCTL_SHRINK_VOLUME\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_SET_SHORT_NAME_BEHAVIOR: WARN("STUB: FSCTL_SET_SHORT_NAME_BEHAVIOR\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_DFSR_SET_GHOST_HANDLE_STATE: WARN("STUB: FSCTL_DFSR_SET_GHOST_HANDLE_STATE\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_LIST_TRANSACTION_LOCKED_FILES: WARN("STUB: FSCTL_TXFS_LIST_TRANSACTION_LOCKED_FILES\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_LIST_TRANSACTIONS: WARN("STUB: FSCTL_TXFS_LIST_TRANSACTIONS\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_QUERY_PAGEFILE_ENCRYPTION: WARN("STUB: FSCTL_QUERY_PAGEFILE_ENCRYPTION\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_RESET_VOLUME_ALLOCATION_HINTS: WARN("STUB: FSCTL_RESET_VOLUME_ALLOCATION_HINTS\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_TXFS_READ_BACKUP_INFORMATION2: WARN("STUB: FSCTL_TXFS_READ_BACKUP_INFORMATION2\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_CSV_CONTROL: WARN("STUB: FSCTL_CSV_CONTROL\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; #endif // TRACE rather than WARN because Windows 10 spams this undocumented fsctl case FSCTL_QUERY_VOLUME_CONTAINER_STATE: TRACE("STUB: FSCTL_QUERY_VOLUME_CONTAINER_STATE\n"); Status = STATUS_INVALID_DEVICE_REQUEST; break; case FSCTL_GET_INTEGRITY_INFORMATION: Status = get_integrity_information(DeviceObject->DeviceExtension, IrpSp->FileObject, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength); break; case FSCTL_SET_INTEGRITY_INFORMATION: Status = set_integrity_information(IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength); break; case FSCTL_DUPLICATE_EXTENTS_TO_FILE: Status = duplicate_extents(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp); break; case FSCTL_BTRFS_GET_FILE_IDS: Status = get_file_ids(IrpSp->FileObject, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength); break; case FSCTL_BTRFS_CREATE_SUBVOL: Status = create_subvol(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp); break; case FSCTL_BTRFS_CREATE_SNAPSHOT: Status = create_snapshot(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp); break; case FSCTL_BTRFS_GET_INODE_INFO: Status = get_inode_info(IrpSp->FileObject, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength); break; case FSCTL_BTRFS_SET_INODE_INFO: Status = set_inode_info(IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp); break; case FSCTL_BTRFS_GET_DEVICES: Status = get_devices(DeviceObject->DeviceExtension, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength); break; case FSCTL_BTRFS_GET_USAGE: Status = get_usage(DeviceObject->DeviceExtension, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength, Irp); break; case FSCTL_BTRFS_START_BALANCE: Status = start_balance(DeviceObject->DeviceExtension, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->RequestorMode); break; case FSCTL_BTRFS_QUERY_BALANCE: Status = query_balance(DeviceObject->DeviceExtension, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength); break; case FSCTL_BTRFS_PAUSE_BALANCE: Status = pause_balance(DeviceObject->DeviceExtension, Irp->RequestorMode); break; case FSCTL_BTRFS_RESUME_BALANCE: Status = resume_balance(DeviceObject->DeviceExtension, Irp->RequestorMode); break; case FSCTL_BTRFS_STOP_BALANCE: Status = stop_balance(DeviceObject->DeviceExtension, Irp->RequestorMode); break; case FSCTL_BTRFS_ADD_DEVICE: Status = add_device(DeviceObject->DeviceExtension, Irp, Irp->RequestorMode); break; case FSCTL_BTRFS_REMOVE_DEVICE: Status = remove_device(DeviceObject->DeviceExtension, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->RequestorMode); break; case FSCTL_BTRFS_GET_UUID: Status = query_uuid(DeviceObject->DeviceExtension, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength); break; case FSCTL_BTRFS_START_SCRUB: Status = start_scrub(DeviceObject->DeviceExtension, Irp->RequestorMode); break; case FSCTL_BTRFS_QUERY_SCRUB: Status = query_scrub(DeviceObject->DeviceExtension, Irp->RequestorMode, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength); break; case FSCTL_BTRFS_PAUSE_SCRUB: Status = pause_scrub(DeviceObject->DeviceExtension, Irp->RequestorMode); break; case FSCTL_BTRFS_RESUME_SCRUB: Status = resume_scrub(DeviceObject->DeviceExtension, Irp->RequestorMode); break; case FSCTL_BTRFS_STOP_SCRUB: Status = stop_scrub(DeviceObject->DeviceExtension, Irp->RequestorMode); break; case FSCTL_BTRFS_RESET_STATS: Status = reset_stats(DeviceObject->DeviceExtension, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->RequestorMode); break; case FSCTL_BTRFS_MKNOD: Status = mknod(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp); break; case FSCTL_BTRFS_RECEIVED_SUBVOL: Status = recvd_subvol(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->RequestorMode); break; case FSCTL_BTRFS_GET_XATTRS: Status = fsctl_get_xattrs(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->UserBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength, Irp->RequestorMode); break; case FSCTL_BTRFS_SET_XATTR: Status = fsctl_set_xattr(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp); break; case FSCTL_BTRFS_RESERVE_SUBVOL: Status = reserve_subvol(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp); break; case FSCTL_BTRFS_FIND_SUBVOL: Status = find_subvol(DeviceObject->DeviceExtension, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->UserBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength, Irp); break; case FSCTL_BTRFS_SEND_SUBVOL: Status = send_subvol(DeviceObject->DeviceExtension, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, IrpSp->FileObject, Irp); break; case FSCTL_BTRFS_READ_SEND_BUFFER: Status = read_send_buffer(DeviceObject->DeviceExtension, IrpSp->FileObject, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength, &Irp->IoStatus.Information, Irp->RequestorMode); break; case FSCTL_BTRFS_RESIZE: Status = resize_device(DeviceObject->DeviceExtension, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp); break; case FSCTL_BTRFS_GET_CSUM_INFO: Status = get_csum_info(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength, &Irp->IoStatus.Information, Irp->RequestorMode); break; default: WARN("unknown control code %lx (DeviceType = %lx, Access = %lx, Function = %lx, Method = %lx)\n", IrpSp->Parameters.FileSystemControl.FsControlCode, (IrpSp->Parameters.FileSystemControl.FsControlCode & 0xff0000) >> 16, (IrpSp->Parameters.FileSystemControl.FsControlCode & 0xc000) >> 14, (IrpSp->Parameters.FileSystemControl.FsControlCode & 0x3ffc) >> 2, IrpSp->Parameters.FileSystemControl.FsControlCode & 0x3); Status = STATUS_INVALID_DEVICE_REQUEST; break; } return Status; } ================================================ FILE: src/fsrtl.c ================================================ /* * PROJECT: ReactOS Kernel - Vista+ APIs * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) * FILE: lib/drivers/ntoskrnl_vista/fsrtl.c * PURPOSE: FsRtl functions of Vista+ * PROGRAMMERS: Pierre Schweitzer */ #include #include FORCEINLINE BOOLEAN IsNullGuid(IN PGUID Guid) { if (Guid->Data1 == 0 && Guid->Data2 == 0 && Guid->Data3 == 0 && ((ULONG *)Guid->Data4)[0] == 0 && ((ULONG *)Guid->Data4)[1] == 0) { return TRUE; } return FALSE; } FORCEINLINE BOOLEAN IsEven(IN USHORT Digit) { return ((Digit & 1) != 1); } NTSTATUS __stdcall compat_FsRtlValidateReparsePointBuffer(IN ULONG BufferLength, IN PREPARSE_DATA_BUFFER ReparseBuffer) { USHORT DataLength; ULONG ReparseTag; PREPARSE_GUID_DATA_BUFFER GuidBuffer; /* Validate data size range */ if (BufferLength < REPARSE_DATA_BUFFER_HEADER_SIZE || BufferLength > MAXIMUM_REPARSE_DATA_BUFFER_SIZE) { return STATUS_IO_REPARSE_DATA_INVALID; } GuidBuffer = (PREPARSE_GUID_DATA_BUFFER)ReparseBuffer; DataLength = ReparseBuffer->ReparseDataLength; ReparseTag = ReparseBuffer->ReparseTag; /* Validate size consistency */ if (DataLength + REPARSE_DATA_BUFFER_HEADER_SIZE != BufferLength && DataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE != BufferLength) { return STATUS_IO_REPARSE_DATA_INVALID; } /* REPARSE_DATA_BUFFER is reserved for MS tags */ if (DataLength + REPARSE_DATA_BUFFER_HEADER_SIZE == BufferLength && !IsReparseTagMicrosoft(ReparseTag)) { return STATUS_IO_REPARSE_DATA_INVALID; } /* If that a GUID data buffer, its GUID cannot be null, and it cannot contain a MS tag */ if (DataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE == BufferLength && ((!IsReparseTagMicrosoft(ReparseTag) && IsNullGuid(&GuidBuffer->ReparseGuid)) || (ReparseTag == IO_REPARSE_TAG_MOUNT_POINT || ReparseTag == IO_REPARSE_TAG_SYMLINK))) { return STATUS_IO_REPARSE_DATA_INVALID; } /* Check the data for MS non reserved tags */ if (!(ReparseTag & 0xFFF0000) && ReparseTag != IO_REPARSE_TAG_RESERVED_ZERO && ReparseTag != IO_REPARSE_TAG_RESERVED_ONE) { /* If that's a mount point, validate the MountPointReparseBuffer branch */ if (ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { /* We need information */ if (DataLength >= REPARSE_DATA_BUFFER_HEADER_SIZE) { /* Substitue must be the first in row */ if (!ReparseBuffer->MountPointReparseBuffer.SubstituteNameOffset) { /* Substitude must be null-terminated */ if (ReparseBuffer->MountPointReparseBuffer.PrintNameOffset == ReparseBuffer->MountPointReparseBuffer.SubstituteNameLength + sizeof(UNICODE_NULL)) { /* There must just be the Offset/Length fields + buffer + 2 null chars */ 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)) { return STATUS_SUCCESS; } } } } } else { #define FIELDS_SIZE (FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) - FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.SubstituteNameOffset)) /* If that's not a symlink, accept the MS tag as it */ if (ReparseTag != IO_REPARSE_TAG_SYMLINK) { return STATUS_SUCCESS; } /* We need information */ if (DataLength >= FIELDS_SIZE) { /* Validate lengths */ if (ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength && ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameLength) { /* Validate unicode strings */ if (IsEven(ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength) && IsEven(ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameLength) && IsEven(ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameOffset) && IsEven(ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameOffset)) { if ((DataLength + REPARSE_DATA_BUFFER_HEADER_SIZE >= ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameOffset + ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength + FIELDS_SIZE + REPARSE_DATA_BUFFER_HEADER_SIZE) && (DataLength + REPARSE_DATA_BUFFER_HEADER_SIZE >= ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameLength + ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameOffset + FIELDS_SIZE + REPARSE_DATA_BUFFER_HEADER_SIZE)) { return STATUS_SUCCESS; } } } } #undef FIELDS_SIZE } return STATUS_IO_REPARSE_DATA_INVALID; } return STATUS_IO_REPARSE_TAG_INVALID; } ================================================ FILE: src/galois.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" static const uint8_t glog[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, 0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x01}; static const uint8_t gilog[] = {0x00, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, 0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71, 0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, 0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6, 0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, 0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, 0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, 0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, 0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, 0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, 0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, 0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, 0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6, 0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, 0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, 0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf}; // divides the bytes in data by 2^div void galois_divpower(uint8_t* data, uint8_t div, uint32_t len) { while (len > 0) { if (data[0] != 0) { if (gilog[data[0]] <= div) data[0] = glog[(gilog[data[0]] + (255 - div)) % 255]; else data[0] = glog[(gilog[data[0]] - div) % 255]; } data++; len--; } } uint8_t gpow2(uint8_t e) { return glog[e%255]; } uint8_t gmul(uint8_t a, uint8_t b) { if (a == 0 || b == 0) return 0; else return glog[(gilog[a] + gilog[b]) % 255]; } uint8_t gdiv(uint8_t a, uint8_t b) { if (b == 0) { return 0xff; // shouldn't happen } else if (a == 0) { return 0; } else { if (gilog[a] >= gilog[b]) return glog[(gilog[a] - gilog[b]) % 255]; else return glog[255-((gilog[b] - gilog[a]) % 255)]; } } // The code from the following functions is derived from the paper // "The mathematics of RAID-6", by H. Peter Anvin. // https://www.kernel.org/pub/linux/kernel/people/hpa/raid6.pdf #if defined(_AMD64_) || defined(_ARM64_) __inline static uint64_t galois_double_mask64(uint64_t v) { v &= 0x8080808080808080; return (v << 1) - (v >> 7); } #else __inline static uint32_t galois_double_mask32(uint32_t v) { v &= 0x80808080; return (v << 1) - (v >> 7); } #endif void galois_double(uint8_t* data, uint32_t len) { // FIXME - SIMD? #if defined(_AMD64_) || defined(_ARM64_) while (len > sizeof(uint64_t)) { uint64_t v = *((uint64_t*)data), vv; vv = (v << 1) & 0xfefefefefefefefe; vv ^= galois_double_mask64(v) & 0x1d1d1d1d1d1d1d1d; *((uint64_t*)data) = vv; data += sizeof(uint64_t); len -= sizeof(uint64_t); } #else while (len > sizeof(uint32_t)) { uint32_t v = *((uint32_t*)data), vv; vv = (v << 1) & 0xfefefefe; vv ^= galois_double_mask32(v) & 0x1d1d1d1d; *((uint32_t*)data) = vv; data += sizeof(uint32_t); len -= sizeof(uint32_t); } #endif while (len > 0) { data[0] = (data[0] << 1) ^ ((data[0] & 0x80) ? 0x1d : 0); data++; len--; } } ================================================ FILE: src/mkbtrfs/mkbtrfs.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "resource.h" #include "../btrfs.h" #define UBTRFS_DLL L"ubtrfs.dll" // These are undocumented, and what comes from format.exe typedef struct { void* table; void* unk1; WCHAR* string; } DSTRING; typedef struct { void* table; } STREAM_MESSAGE; #define FORMAT_FLAG_QUICK_FORMAT 0x00000001 #define FORMAT_FLAG_UNKNOWN1 0x00000002 #define FORMAT_FLAG_DISMOUNT_FIRST 0x00000004 #define FORMAT_FLAG_UNKNOWN2 0x00000040 #define FORMAT_FLAG_LARGE_RECORDS 0x00000100 #define FORMAT_FLAG_INTEGRITY_DISABLE 0x00000100 typedef struct { uint16_t unk1; uint16_t unk2; uint32_t flags; DSTRING* label; } options; typedef BOOL (__stdcall* pFormatEx)(DSTRING* root, STREAM_MESSAGE* message, options* opts, uint32_t unk1); typedef void (__stdcall* pSetSizes)(ULONG sector, ULONG node); typedef void (__stdcall* pSetIncompatFlags)(uint64_t incompat_flags); typedef void (__stdcall* pSetCompatROFlags)(uint64_t compat_ro_flags); typedef void (__stdcall* pSetCsumType)(uint16_t csum_type); static void print_string(FILE* f, int resid, ...) { WCHAR s[1024], t[1024]; va_list ap; if (!LoadStringW(GetModuleHandle(NULL), resid, s, sizeof(s) / sizeof(WCHAR))) { fprintf(stderr, "LoadString failed (error %lu)\n", GetLastError()); return; } va_start(ap, resid); vswprintf(t, sizeof(t) / sizeof(WCHAR), s, ap); fwprintf(f, L"%s\n", t); va_end(ap); } int main(int argc, char** argv) { HMODULE ubtrfs; bool baddrive = false, success; char *ds = NULL, *labels = NULL; WCHAR dsw[10], labelw[255], dsw2[255]; UNICODE_STRING drive, label; pFormatEx FormatEx; options opts; DSTRING labelds, rootds; ULONG sector_size = 0, node_size = 0; int i; bool invalid_args = false; uint64_t incompat_flags = BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF | BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA | BTRFS_INCOMPAT_FLAGS_NO_HOLES; uint64_t compat_ro_flags = BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE; uint16_t csum_type = CSUM_TYPE_CRC32C; pSetIncompatFlags SetIncompatFlags; pSetCsumType SetCsumType; if (argc >= 2) { for (i = 1; i < argc; i++) { if (argv[i][0] == '/' && argv[i][1] != 0) { char cmd[255], *colon; colon = strstr(argv[i], ":"); if (colon) { memcpy(cmd, argv[i] + 1, colon - argv[i] - 1); cmd[colon - argv[i] - 1] = 0; } else strcpy(cmd, argv[i] + 1); if (!_stricmp(cmd, "sectorsize")) { if (!colon || colon[1] == 0) { print_string(stdout, IDS_NO_SECTOR_SIZE); invalid_args = true; break; } else sector_size = atoi(&colon[1]); } else if (!_stricmp(cmd, "nodesize")) { if (!colon || colon[1] == 0) { print_string(stdout, IDS_NO_NODE_SIZE); invalid_args = true; break; } else node_size = atoi(&colon[1]); } else if (!_stricmp(cmd, "csum")) { char* v; if (!colon || colon[1] == 0) { print_string(stdout, IDS_NO_CSUM); invalid_args = true; break; } v = &colon[1]; if (!_stricmp(v, "crc32c")) csum_type = CSUM_TYPE_CRC32C; else if (!_stricmp(v, "xxhash")) csum_type = CSUM_TYPE_XXHASH; else if (!_stricmp(v, "sha256")) csum_type = CSUM_TYPE_SHA256; else if (!_stricmp(v, "blake2")) csum_type = CSUM_TYPE_BLAKE2; else { print_string(stdout, IDS_INVALID_CSUM_TYPE); invalid_args = true; break; } } else if (!_stricmp(cmd, "mixed")) incompat_flags |= BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS; else if (!_stricmp(cmd, "notmixed")) incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS; else if (!_stricmp(cmd, "extiref")) incompat_flags |= BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF; else if (!_stricmp(cmd, "notextiref")) incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF; else if (!_stricmp(cmd, "skinnymetadata")) incompat_flags |= BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA; else if (!_stricmp(cmd, "notskinnymetadata")) incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA; else if (!_stricmp(cmd, "noholes")) incompat_flags |= BTRFS_INCOMPAT_FLAGS_NO_HOLES; else if (!_stricmp(cmd, "notnoholes")) incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_NO_HOLES; else if (!_stricmp(cmd, "freespacetree")) compat_ro_flags |= BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE; else if (!_stricmp(cmd, "notfreespacetree")) compat_ro_flags &= ~BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE; else if (!_stricmp(cmd, "blockgrouptree")) compat_ro_flags |= BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE; else if (!_stricmp(cmd, "notblockgrouptree")) compat_ro_flags &= ~BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE; else { print_string(stdout, IDS_UNKNOWN_ARG); invalid_args = true; break; } } else { if (!ds) ds = argv[i]; else if (!labels) labels = argv[i]; else { print_string(stdout, IDS_TOO_MANY_ARGS); invalid_args = true; break; } } } } else invalid_args = true; if (!ds) invalid_args = true; if (invalid_args) { char* c = argv[0] + strlen(argv[0]) - 1; char* fn = NULL; WCHAR fnw[MAX_PATH], *s; int ret; while (c > argv[0]) { if (*c == '/' || *c == '\\') { fn = c + 1; break; } c--; } if (!fn) fn = argv[0]; if (!MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, fn, -1, fnw, sizeof(fnw) / sizeof(WCHAR))) { print_string(stderr, IDS_MULTIBYTE_FAILED, GetLastError()); return 1; } print_string(stdout, IDS_USAGE, fnw); ret = LoadStringW(GetModuleHandle(NULL), IDS_USAGE2, (WCHAR*)&s, 0); if (!ret) { fprintf(stderr, "LoadString failed (error %lu)\n", GetLastError()); return 0; } fwprintf(stdout, L"%.*s\n", ret, s); return 0; } if (ds[0] != '\\') { if ((ds[0] >= 'A' && ds[0] <= 'Z') || (ds[0] >= 'a' && ds[0] <= 'z')) { if (ds[1] == 0 || (ds[1] == ':' && ds[2] == 0) || (ds[1] == ':' && ds[2] == '\\' && ds[3] == 0)) { dsw[0] = '\\'; dsw[1] = '?'; dsw[2] = '?'; dsw[3] = '\\'; dsw[4] = ds[0]; dsw[5] = ':'; dsw[6] = 0; drive.Buffer = dsw; drive.Length = drive.MaximumLength = (USHORT)(wcslen(drive.Buffer) * sizeof(WCHAR)); } else baddrive = true; } else baddrive = true; } else { if (!MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, ds, -1, dsw2, sizeof(dsw2) / sizeof(WCHAR))) { print_string(stderr, IDS_MULTIBYTE_FAILED, GetLastError()); return 1; } drive.Buffer = dsw2; drive.Length = drive.MaximumLength = (USHORT)(wcslen(drive.Buffer) * sizeof(WCHAR)); } if (baddrive) { if (!MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, ds, -1, dsw2, sizeof(dsw2) / sizeof(WCHAR))) { print_string(stderr, IDS_MULTIBYTE_FAILED, GetLastError()); return 1; } print_string(stderr, IDS_CANT_RECOGNIZE_DRIVE, dsw2); return 1; } if (labels) { if (!MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, labels, -1, labelw, sizeof(labelw) / sizeof(WCHAR))) { print_string(stderr, IDS_MULTIBYTE_FAILED, GetLastError()); return 1; } label.Buffer = labelw; label.Length = label.MaximumLength = (USHORT)(wcslen(labelw) * sizeof(WCHAR)); } else { label.Buffer = NULL; label.Length = label.MaximumLength = 0; } ubtrfs = LoadLibraryW(UBTRFS_DLL); if (!ubtrfs) { #if defined(__i386) || defined(_M_IX86) ubtrfs = LoadLibraryW(L"Debug\\x86\\ubtrfs.dll"); #elif defined(__x86_64__) || defined(_M_X64) ubtrfs = LoadLibraryW(L"Debug\\x64\\ubtrfs.dll"); #endif } if (!ubtrfs) { print_string(stderr, IDS_CANT_LOAD_DLL, UBTRFS_DLL); return 1; } if (node_size != 0 || sector_size != 0) { pSetSizes SetSizes; SetSizes = (pSetSizes)GetProcAddress(ubtrfs, "SetSizes"); if (!SetSizes) { print_string(stderr, IDS_CANT_FIND_SETSIZES, UBTRFS_DLL); return 1; } SetSizes(node_size, sector_size); } // From Linux btrfs/disk-io.c: "Artificial requirement for block-group-tree to force // newer features (free-space-tree, no-holes) so the test matrix is smaller." if (compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE) { compat_ro_flags |= BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE; incompat_flags |= BTRFS_INCOMPAT_FLAGS_NO_HOLES; } if (compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE) compat_ro_flags |= BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID; SetIncompatFlags = (pSetIncompatFlags)GetProcAddress(ubtrfs, "SetIncompatFlags"); if (!SetIncompatFlags) { print_string(stderr, IDS_CANT_FIND_FUNCTION, "SetIncompatFlags", UBTRFS_DLL); return 1; } SetIncompatFlags(incompat_flags); if (compat_ro_flags != 0) { pSetCompatROFlags SetCompatROFlags = (pSetIncompatFlags)GetProcAddress(ubtrfs, "SetCompatROFlags"); if (!SetCompatROFlags) { print_string(stderr, IDS_CANT_FIND_FUNCTION, "SetCompatROFlags", UBTRFS_DLL); return 1; } SetCompatROFlags(compat_ro_flags); } SetCsumType = (pSetCsumType)GetProcAddress(ubtrfs, "SetCsumType"); if (!SetCsumType) { print_string(stderr, IDS_CANT_FIND_FUNCTION, "SetCsumType", UBTRFS_DLL); return 1; } SetCsumType(csum_type); FormatEx = (pFormatEx)GetProcAddress(ubtrfs, "FormatEx"); if (!FormatEx) { print_string(stderr, IDS_CANT_FIND_FORMATEX, UBTRFS_DLL); return 1; } memset(&opts, 0, sizeof(options)); if (label.Length > 0) { labelds.string = label.Buffer; opts.label = &labelds; } rootds.string = drive.Buffer; success = FormatEx(&rootds, NULL, &opts, 0); if (!success) { print_string(stderr, IDS_FORMATEX_ERROR); return 1; } print_string(stdout, IDS_SUCCESS); return 0; } ================================================ FILE: src/mkbtrfs/mkbtrfs.rc.in ================================================ // Microsoft Visual C++ generated resource script. // #include "@CMAKE_CURRENT_SOURCE_DIR@/src/mkbtrfs/resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United Kingdom) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK #pragma code_page(1252) #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x0L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "080904b0" BEGIN VALUE "FileDescription", "Btrfs formatting utility" VALUE "FileVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" VALUE "InternalName", "mkbtrfs" VALUE "LegalCopyright", "Copyright (c) Mark Harmstone 2016-24" VALUE "OriginalFilename", "mkbtrfs.exe" VALUE "ProductName", "WinBtrfs" VALUE "ProductVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x809, 1200 END END ///////////////////////////////////////////////////////////////////////////// // // String Table // STRINGTABLE BEGIN IDS_USAGE "Usage: %s device [label]\n" IDS_MULTIBYTE_FAILED "MultiByteToWideChar failed (error %lu)" IDS_CANT_RECOGNIZE_DRIVE "Could not recognize drive %s" IDS_CANT_LOAD_DLL "Unable to load %s" IDS_CANT_FIND_FORMATEX "Could not load function FormatEx in %s" IDS_FORMATEX_ERROR "FormatEx failed" IDS_SUCCESS "Completed successfully." IDS_CANT_FIND_SETSIZES "Could not load function SetSizes in %s" IDS_TOO_MANY_ARGS "Too many arguments." IDS_UNKNOWN_ARG "Unknown argument." IDS_NO_SECTOR_SIZE "No sector size specified." END STRINGTABLE BEGIN IDS_NO_NODE_SIZE "No node size specified." IDS_CANT_FIND_FUNCTION "Could not load function %s in %s" 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." IDS_NO_CSUM "No csum value given. Valid values are crc32c, xxhash, sha256, and blake2." IDS_INVALID_CSUM_TYPE "Invalid csum value. Valid values are crc32c, xxhash, sha256, and blake2." END #endif // English (United Kingdom) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: src/mkbtrfs/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by mkbtrfs.rc // #define IDS_USAGE 101 #define IDS_MULTIBYTE_FAILED 102 #define IDS_CANT_RECOGNIZE_DRIVE 103 #define IDS_CANT_LOAD_DLL 104 #define IDS_CANT_FIND_FORMATEX 105 #define IDS_FORMATEX_ERROR 106 #define IDS_SUCCESS 107 #define IDS_CANT_FIND_SETSIZES 108 #define IDS_TOO_MANY_ARGS 109 #define IDS_UNKNOWN_ARG 110 #define IDS_NO_SECTOR_SIZE 111 #define IDS_NO_NODE_SIZE 112 #define IDS_CANT_FIND_FUNCTION 113 #define IDS_USAGE2 114 #define IDS_NO_CSUM 115 #define IDS_INVALID_CSUM_TYPE 116 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: src/pnp.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" extern ERESOURCE pdo_list_lock; extern LIST_ENTRY pdo_list; NTSTATUS pnp_query_remove_device(PDEVICE_OBJECT DeviceObject, PIRP Irp) { device_extension* Vcb = DeviceObject->DeviceExtension; NTSTATUS Status; // We might be going away imminently - do a flush so we're not caught out ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); if (Vcb->root_fileref && Vcb->root_fileref->fcb && (Vcb->root_fileref->open_count > 0 || has_open_children(Vcb->root_fileref))) { ExReleaseResourceLite(&Vcb->tree_lock); return STATUS_ACCESS_DENIED; } if (Vcb->need_write && !Vcb->readonly) { Status = do_write(Vcb, Irp); free_trees(Vcb); if (!NT_SUCCESS(Status)) { ERR("do_write returned %08lx\n", Status); ExReleaseResourceLite(&Vcb->tree_lock); return Status; } } ExReleaseResourceLite(&Vcb->tree_lock); return STATUS_UNSUCCESSFUL; } static NTSTATUS pnp_remove_device(PDEVICE_OBJECT DeviceObject) { device_extension* Vcb = DeviceObject->DeviceExtension; NTSTATUS Status; if (DeviceObject->Vpb->Flags & VPB_MOUNTED) { Status = FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_DISMOUNT); if (!NT_SUCCESS(Status)) { WARN("FsRtlNotifyVolumeEvent returned %08lx\n", Status); } if (Vcb->vde) Vcb->vde->mounted_device = NULL; ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); Vcb->removing = true; ExReleaseResourceLite(&Vcb->tree_lock); if (Vcb->open_files == 0) uninit(Vcb); } return STATUS_SUCCESS; } NTSTATUS pnp_surprise_removal(PDEVICE_OBJECT DeviceObject, PIRP Irp) { device_extension* Vcb = DeviceObject->DeviceExtension; TRACE("(%p, %p)\n", DeviceObject, Irp); UNUSED(Irp); if (DeviceObject->Vpb->Flags & VPB_MOUNTED) { ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); if (Vcb->vde) Vcb->vde->mounted_device = NULL; Vcb->removing = true; ExReleaseResourceLite(&Vcb->tree_lock); if (Vcb->open_files == 0) uninit(Vcb); } return STATUS_SUCCESS; } static NTSTATUS bus_query_capabilities(PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PDEVICE_CAPABILITIES dc = IrpSp->Parameters.DeviceCapabilities.Capabilities; dc->UniqueID = true; dc->SilentInstall = true; return STATUS_SUCCESS; } static NTSTATUS bus_query_device_relations(PIRP Irp) { NTSTATUS Status; ULONG num_children; LIST_ENTRY* le; ULONG drsize, i; DEVICE_RELATIONS* dr; ExAcquireResourceSharedLite(&pdo_list_lock, true); num_children = 0; le = pdo_list.Flink; while (le != &pdo_list) { pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry); if (!pdode->dont_report) num_children++; le = le->Flink; } drsize = offsetof(DEVICE_RELATIONS, Objects[0]) + (num_children * sizeof(PDEVICE_OBJECT)); dr = ExAllocatePoolWithTag(PagedPool, drsize, ALLOC_TAG); if (!dr) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } dr->Count = num_children; i = 0; le = pdo_list.Flink; while (le != &pdo_list) { pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry); if (!pdode->dont_report) { ObReferenceObject(pdode->pdo); dr->Objects[i] = pdode->pdo; i++; } le = le->Flink; } Irp->IoStatus.Information = (ULONG_PTR)dr; Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&pdo_list_lock); return Status; } static NTSTATUS bus_query_hardware_ids(PIRP Irp) { WCHAR* out; static const WCHAR ids[] = L"ROOT\\btrfs\0"; out = ExAllocatePoolWithTag(PagedPool, sizeof(ids), ALLOC_TAG); if (!out) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(out, ids, sizeof(ids)); Irp->IoStatus.Information = (ULONG_PTR)out; return STATUS_SUCCESS; } static NTSTATUS bus_pnp(bus_device_extension* bde, PIRP Irp) { NTSTATUS Status = Irp->IoStatus.Status; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); bool handled = false; switch (IrpSp->MinorFunction) { case IRP_MN_START_DEVICE: case IRP_MN_CANCEL_REMOVE_DEVICE: case IRP_MN_SURPRISE_REMOVAL: case IRP_MN_REMOVE_DEVICE: Status = STATUS_SUCCESS; handled = true; break; case IRP_MN_QUERY_REMOVE_DEVICE: Status = STATUS_UNSUCCESSFUL; handled = true; break; case IRP_MN_QUERY_CAPABILITIES: Status = bus_query_capabilities(Irp); handled = true; break; case IRP_MN_QUERY_DEVICE_RELATIONS: if (IrpSp->Parameters.QueryDeviceRelations.Type != BusRelations || no_pnp) break; Status = bus_query_device_relations(Irp); handled = true; break; case IRP_MN_QUERY_ID: if (IrpSp->Parameters.QueryId.IdType != BusQueryHardwareIDs) break; Status = bus_query_hardware_ids(Irp); handled = true; break; } if (!NT_SUCCESS(Status) && handled) { Irp->IoStatus.Status = Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); return Status; } Irp->IoStatus.Status = Status; IoSkipCurrentIrpStackLocation(Irp); return IoCallDriver(bde->attached_device, Irp); } static NTSTATUS pdo_query_device_id(pdo_device_extension* pdode, PIRP Irp) { WCHAR name[100], *noff, *out; int i; static const WCHAR pref[] = L"Btrfs\\"; RtlCopyMemory(name, pref, sizeof(pref) - sizeof(WCHAR)); noff = &name[(sizeof(pref) / sizeof(WCHAR)) - 1]; for (i = 0; i < 16; i++) { *noff = hex_digit(pdode->uuid.uuid[i] >> 4); noff++; *noff = hex_digit(pdode->uuid.uuid[i] & 0xf); noff++; if (i == 3 || i == 5 || i == 7 || i == 9) { *noff = '-'; noff++; } } *noff = 0; out = ExAllocatePoolWithTag(PagedPool, (wcslen(name) + 1) * sizeof(WCHAR), ALLOC_TAG); if (!out) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(out, name, (wcslen(name) + 1) * sizeof(WCHAR)); Irp->IoStatus.Information = (ULONG_PTR)out; return STATUS_SUCCESS; } static NTSTATUS pdo_query_hardware_ids(PIRP Irp) { WCHAR* out; static const WCHAR ids[] = L"BtrfsVolume\0"; out = ExAllocatePoolWithTag(PagedPool, sizeof(ids), ALLOC_TAG); if (!out) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(out, ids, sizeof(ids)); Irp->IoStatus.Information = (ULONG_PTR)out; return STATUS_SUCCESS; } static NTSTATUS pdo_query_id(pdo_device_extension* pdode, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); switch (IrpSp->Parameters.QueryId.IdType) { case BusQueryDeviceID: TRACE("BusQueryDeviceID\n"); return pdo_query_device_id(pdode, Irp); case BusQueryHardwareIDs: TRACE("BusQueryHardwareIDs\n"); return pdo_query_hardware_ids(Irp); default: break; } return Irp->IoStatus.Status; } typedef struct { IO_STATUS_BLOCK iosb; KEVENT Event; NTSTATUS Status; } device_usage_context; _Function_class_(IO_COMPLETION_ROUTINE) static NTSTATUS __stdcall device_usage_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) { device_usage_context* context = conptr; UNUSED(DeviceObject); context->Status = Irp->IoStatus.Status; KeSetEvent(&context->Event, 0, false); return STATUS_MORE_PROCESSING_REQUIRED; } static NTSTATUS pdo_device_usage_notification(pdo_device_extension* pdode, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); LIST_ENTRY* le; TRACE("(%p, %p)\n", pdode, Irp); ExAcquireResourceSharedLite(&pdode->child_lock, true); le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); if (vc->devobj) { PIRP Irp2; PIO_STACK_LOCATION IrpSp2; device_usage_context context; Irp2 = IoAllocateIrp(vc->devobj->StackSize, false); if (!Irp2) { ERR("out of memory\n"); ExReleaseResourceLite(&pdode->child_lock); return STATUS_INSUFFICIENT_RESOURCES; } IrpSp2 = IoGetNextIrpStackLocation(Irp2); IrpSp2->MajorFunction = IRP_MJ_PNP; IrpSp2->MinorFunction = IRP_MN_DEVICE_USAGE_NOTIFICATION; IrpSp2->Parameters.UsageNotification = IrpSp->Parameters.UsageNotification; IrpSp2->FileObject = vc->fileobj; context.iosb.Status = STATUS_SUCCESS; Irp2->UserIosb = &context.iosb; KeInitializeEvent(&context.Event, NotificationEvent, false); Irp2->UserEvent = &context.Event; IoSetCompletionRoutine(Irp2, device_usage_completion, &context, true, true, true); context.Status = IoCallDriver(vc->devobj, Irp2); if (context.Status == STATUS_PENDING) KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL); if (!NT_SUCCESS(context.Status)) { ERR("IoCallDriver returned %08lx\n", context.Status); ExReleaseResourceLite(&pdode->child_lock); return context.Status; } } le = le->Flink; } ExReleaseResourceLite(&pdode->child_lock); return STATUS_SUCCESS; } static NTSTATUS pdo_query_device_relations(PDEVICE_OBJECT pdo, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PDEVICE_RELATIONS device_relations; if (IrpSp->Parameters.QueryDeviceRelations.Type != TargetDeviceRelation) return Irp->IoStatus.Status; device_relations = ExAllocatePoolWithTag(PagedPool, sizeof(DEVICE_RELATIONS), ALLOC_TAG); if (!device_relations) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } device_relations->Count = 1; device_relations->Objects[0] = pdo; ObReferenceObject(pdo); Irp->IoStatus.Information = (ULONG_PTR)device_relations; return STATUS_SUCCESS; } static NTSTATUS pdo_pnp(PDEVICE_OBJECT pdo, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); pdo_device_extension* pdode = pdo->DeviceExtension; switch (IrpSp->MinorFunction) { case IRP_MN_QUERY_ID: return pdo_query_id(pdode, Irp); case IRP_MN_START_DEVICE: case IRP_MN_CANCEL_REMOVE_DEVICE: case IRP_MN_SURPRISE_REMOVAL: case IRP_MN_REMOVE_DEVICE: return STATUS_SUCCESS; case IRP_MN_QUERY_REMOVE_DEVICE: return STATUS_UNSUCCESSFUL; case IRP_MN_DEVICE_USAGE_NOTIFICATION: return pdo_device_usage_notification(pdode, Irp); case IRP_MN_QUERY_DEVICE_RELATIONS: return pdo_query_device_relations(pdo, Irp); } return Irp->IoStatus.Status; } static NTSTATUS pnp_device_usage_notification(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); device_extension* Vcb = DeviceObject->DeviceExtension; if (IrpSp->Parameters.UsageNotification.InPath) { switch (IrpSp->Parameters.UsageNotification.Type) { case DeviceUsageTypePaging: case DeviceUsageTypeHibernation: case DeviceUsageTypeDumpFile: IoAdjustPagingPathCount(&Vcb->page_file_count, IrpSp->Parameters.UsageNotification.InPath); break; default: break; } } IoSkipCurrentIrpStackLocation(Irp); return IoCallDriver(Vcb->Vpb->RealDevice, Irp); } _Dispatch_type_(IRP_MJ_PNP) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_pnp(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); device_extension* Vcb = DeviceObject->DeviceExtension; NTSTATUS Status; bool top_level; FsRtlEnterFileSystem(); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_BUS) { Status = bus_pnp(DeviceObject->DeviceExtension, Irp); goto exit; } else if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { volume_device_extension* vde = DeviceObject->DeviceExtension; IoSkipCurrentIrpStackLocation(Irp); Status = IoCallDriver(vde->attached_device, Irp); goto exit; } else if (Vcb && Vcb->type == VCB_TYPE_PDO) { Status = pdo_pnp(DeviceObject, Irp); goto end; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } Status = STATUS_NOT_IMPLEMENTED; switch (IrpSp->MinorFunction) { case IRP_MN_CANCEL_REMOVE_DEVICE: Status = STATUS_SUCCESS; break; case IRP_MN_QUERY_REMOVE_DEVICE: Status = pnp_query_remove_device(DeviceObject, Irp); break; case IRP_MN_REMOVE_DEVICE: Status = pnp_remove_device(DeviceObject); break; case IRP_MN_SURPRISE_REMOVAL: Status = pnp_surprise_removal(DeviceObject, Irp); break; case IRP_MN_DEVICE_USAGE_NOTIFICATION: Status = pnp_device_usage_notification(DeviceObject, Irp); goto exit; default: TRACE("passing minor function 0x%x on\n", IrpSp->MinorFunction); IoSkipCurrentIrpStackLocation(Irp); Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp); goto exit; } end: Irp->IoStatus.Status = Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); exit: TRACE("returning %08lx\n", Status); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } ================================================ FILE: src/read.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include "zstd/lib/common/xxhash.h" #include "crc32c.h" enum read_data_status { ReadDataStatus_Pending, ReadDataStatus_Success, ReadDataStatus_Error, ReadDataStatus_MissingDevice, ReadDataStatus_Skip }; struct read_data_context; typedef struct { struct read_data_context* context; uint16_t stripenum; bool rewrite; PIRP Irp; IO_STATUS_BLOCK iosb; enum read_data_status status; PMDL mdl; uint64_t stripestart; uint64_t stripeend; } read_data_stripe; typedef struct { KEVENT Event; NTSTATUS Status; chunk* c; uint64_t address; uint32_t buflen; LONG num_stripes, stripes_left; uint64_t type; uint32_t sector_size; uint16_t firstoff, startoffstripe, sectors_per_stripe; void* csum; bool tree; read_data_stripe* stripes; uint8_t* va; } read_data_context; extern bool diskacc; extern tPsUpdateDiskCounters fPsUpdateDiskCounters; extern tCcCopyReadEx fCcCopyReadEx; extern tFsRtlUpdateDiskCounters fFsRtlUpdateDiskCounters; #define LZO_PAGE_SIZE 4096 _Function_class_(IO_COMPLETION_ROUTINE) static NTSTATUS __stdcall read_data_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) { read_data_stripe* stripe = conptr; read_data_context* context = (read_data_context*)stripe->context; UNUSED(DeviceObject); stripe->iosb = Irp->IoStatus; if (NT_SUCCESS(Irp->IoStatus.Status)) stripe->status = ReadDataStatus_Success; else stripe->status = ReadDataStatus_Error; if (InterlockedDecrement(&context->stripes_left) == 0) KeSetEvent(&context->Event, 0, false); return STATUS_MORE_PROCESSING_REQUIRED; } NTSTATUS check_csum(device_extension* Vcb, uint8_t* data, uint32_t sectors, void* csum) { void* csum2; csum2 = ExAllocatePoolWithTag(PagedPool, Vcb->csum_size * sectors, ALLOC_TAG); if (!csum2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } do_calc_job(Vcb, data, sectors, csum2); if (RtlCompareMemory(csum2, csum, sectors * Vcb->csum_size) != sectors * Vcb->csum_size) { ExFreePool(csum2); return STATUS_CRC_ERROR; } ExFreePool(csum2); return STATUS_SUCCESS; } void get_tree_checksum(device_extension* Vcb, tree_header* th, void* csum) { switch (Vcb->superblock.csum_type) { case CSUM_TYPE_CRC32C: *(uint32_t*)csum = ~calc_crc32c(0xffffffff, (uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum)); break; case CSUM_TYPE_XXHASH: *(uint64_t*)csum = XXH64((uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum), 0); break; case CSUM_TYPE_SHA256: calc_sha256(csum, &th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum)); break; case CSUM_TYPE_BLAKE2: blake2b(csum, BLAKE2_HASH_SIZE, (uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum)); break; } } bool check_tree_checksum(device_extension* Vcb, tree_header* th) { switch (Vcb->superblock.csum_type) { case CSUM_TYPE_CRC32C: { uint32_t crc32 = ~calc_crc32c(0xffffffff, (uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum)); if (crc32 == *((uint32_t*)th->csum)) return true; WARN("hash was %08x, expected %08x\n", crc32, *((uint32_t*)th->csum)); break; } case CSUM_TYPE_XXHASH: { uint64_t hash = XXH64((uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum), 0); if (hash == *((uint64_t*)th->csum)) return true; WARN("hash was %I64x, expected %I64x\n", hash, *((uint64_t*)th->csum)); break; } case CSUM_TYPE_SHA256: { uint8_t hash[SHA256_HASH_SIZE]; calc_sha256(hash, (uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum)); if (RtlCompareMemory(hash, th, SHA256_HASH_SIZE) == SHA256_HASH_SIZE) return true; WARN("hash was invalid\n"); break; } case CSUM_TYPE_BLAKE2: { uint8_t hash[BLAKE2_HASH_SIZE]; blake2b(hash, sizeof(hash), (uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum)); if (RtlCompareMemory(hash, th, BLAKE2_HASH_SIZE) == BLAKE2_HASH_SIZE) return true; WARN("hash was invalid\n"); break; } } return false; } void get_sector_csum(device_extension* Vcb, void* buf, void* csum) { switch (Vcb->superblock.csum_type) { case CSUM_TYPE_CRC32C: *(uint32_t*)csum = ~calc_crc32c(0xffffffff, buf, Vcb->superblock.sector_size); break; case CSUM_TYPE_XXHASH: *(uint64_t*)csum = XXH64(buf, Vcb->superblock.sector_size, 0); break; case CSUM_TYPE_SHA256: calc_sha256(csum, buf, Vcb->superblock.sector_size); break; case CSUM_TYPE_BLAKE2: blake2b(csum, BLAKE2_HASH_SIZE, buf, Vcb->superblock.sector_size); break; } } bool check_sector_csum(device_extension* Vcb, void* buf, void* csum) { switch (Vcb->superblock.csum_type) { case CSUM_TYPE_CRC32C: { uint32_t crc32 = ~calc_crc32c(0xffffffff, buf, Vcb->superblock.sector_size); return *(uint32_t*)csum == crc32; } case CSUM_TYPE_XXHASH: { uint64_t hash = XXH64(buf, Vcb->superblock.sector_size, 0); return *(uint64_t*)csum == hash; } case CSUM_TYPE_SHA256: { uint8_t hash[SHA256_HASH_SIZE]; calc_sha256(hash, buf, Vcb->superblock.sector_size); return RtlCompareMemory(hash, csum, SHA256_HASH_SIZE) == SHA256_HASH_SIZE; } case CSUM_TYPE_BLAKE2: { uint8_t hash[BLAKE2_HASH_SIZE]; blake2b(hash, sizeof(hash), buf, Vcb->superblock.sector_size); return RtlCompareMemory(hash, csum, BLAKE2_HASH_SIZE) == BLAKE2_HASH_SIZE; } } return false; } static NTSTATUS read_data_dup(device_extension* Vcb, uint8_t* buf, uint64_t addr, read_data_context* context, CHUNK_ITEM* ci, device** devices, uint64_t generation) { bool checksum_error = false; uint16_t j, stripe = 0; NTSTATUS Status; CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1]; for (j = 0; j < ci->num_stripes; j++) { if (context->stripes[j].status == ReadDataStatus_Error) { WARN("stripe %u returned error %08lx\n", j, context->stripes[j].iosb.Status); log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS); return context->stripes[j].iosb.Status; } else if (context->stripes[j].status == ReadDataStatus_Success) { stripe = j; break; } } if (context->stripes[stripe].status != ReadDataStatus_Success) return STATUS_INTERNAL_ERROR; if (context->tree) { tree_header* th = (tree_header*)buf; if (th->address != context->address || !check_tree_checksum(Vcb, th)) { checksum_error = true; log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } else if (generation != 0 && th->generation != generation) { checksum_error = true; log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_GENERATION_ERRORS); } } else if (context->csum) { Status = check_csum(Vcb, buf, (ULONG)context->stripes[stripe].Irp->IoStatus.Information / context->sector_size, context->csum); if (Status == STATUS_CRC_ERROR) { checksum_error = true; log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } else if (!NT_SUCCESS(Status)) { ERR("check_csum returned %08lx\n", Status); return Status; } } if (!checksum_error) return STATUS_SUCCESS; if (ci->num_stripes == 1) return STATUS_CRC_ERROR; if (context->tree) { tree_header* t2; bool recovered = false; t2 = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size, ALLOC_TAG); if (!t2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } for (j = 0; j < ci->num_stripes; j++) { if (j != stripe && devices[j] && devices[j]->devobj) { Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + context->stripes[stripe].stripestart, Vcb->superblock.node_size, (uint8_t*)t2, false); if (!NT_SUCCESS(Status)) { WARN("sync_read_phys returned %08lx\n", Status); log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS); } else { bool checksum_error = !check_tree_checksum(Vcb, t2); if (t2->address == addr && !checksum_error && (generation == 0 || t2->generation == generation)) { RtlCopyMemory(buf, t2, Vcb->superblock.node_size); ERR("recovering from checksum error at %I64x, device %I64x\n", addr, devices[stripe]->devitem.dev_id); recovered = true; if (!Vcb->readonly && !devices[stripe]->readonly) { // write good data over bad Status = write_data_phys(devices[stripe]->devobj, devices[stripe]->fileobj, cis[stripe].offset + context->stripes[stripe].stripestart, t2, Vcb->superblock.node_size); if (!NT_SUCCESS(Status)) { WARN("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_WRITE_ERRORS); } } break; } else if (t2->address != addr || checksum_error) log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_CORRUPTION_ERRORS); else log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_GENERATION_ERRORS); } } } if (!recovered) { ERR("unrecoverable checksum error at %I64x\n", addr); ExFreePool(t2); return STATUS_CRC_ERROR; } ExFreePool(t2); } else { ULONG sectors = (ULONG)context->stripes[stripe].Irp->IoStatus.Information >> Vcb->sector_shift; uint8_t* sector; void* ptr = context->csum; sector = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.sector_size, ALLOC_TAG); if (!sector) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } for (ULONG i = 0; i < sectors; i++) { if (!check_sector_csum(Vcb, buf + (i << Vcb->sector_shift), ptr)) { bool recovered = false; for (j = 0; j < ci->num_stripes; j++) { if (j != stripe && devices[j] && devices[j]->devobj) { Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + context->stripes[stripe].stripestart + ((uint64_t)i << Vcb->sector_shift), Vcb->superblock.sector_size, sector, false); if (!NT_SUCCESS(Status)) { WARN("sync_read_phys returned %08lx\n", Status); log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS); } else { if (check_sector_csum(Vcb, sector, ptr)) { RtlCopyMemory(buf + (i << Vcb->sector_shift), sector, Vcb->superblock.sector_size); ERR("recovering from checksum error at %I64x, device %I64x\n", addr + ((uint64_t)i << Vcb->sector_shift), devices[stripe]->devitem.dev_id); recovered = true; if (!Vcb->readonly && !devices[stripe]->readonly) { // write good data over bad Status = write_data_phys(devices[stripe]->devobj, devices[stripe]->fileobj, cis[stripe].offset + context->stripes[stripe].stripestart + ((uint64_t)i << Vcb->sector_shift), sector, Vcb->superblock.sector_size); if (!NT_SUCCESS(Status)) { WARN("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_WRITE_ERRORS); } } break; } else log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } } if (!recovered) { ERR("unrecoverable checksum error at %I64x\n", addr + ((uint64_t)i << Vcb->sector_shift)); ExFreePool(sector); return STATUS_CRC_ERROR; } } ptr = (uint8_t*)ptr + Vcb->csum_size; } ExFreePool(sector); } return STATUS_SUCCESS; } static NTSTATUS read_data_raid0(device_extension* Vcb, uint8_t* buf, uint64_t addr, uint32_t length, read_data_context* context, CHUNK_ITEM* ci, device** devices, uint64_t generation, uint64_t offset) { for (uint16_t i = 0; i < ci->num_stripes; i++) { if (context->stripes[i].status == ReadDataStatus_Error) { WARN("stripe %u returned error %08lx\n", i, context->stripes[i].iosb.Status); log_device_error(Vcb, devices[i], BTRFS_DEV_STAT_READ_ERRORS); return context->stripes[i].iosb.Status; } } if (context->tree) { // shouldn't happen, as trees shouldn't cross stripe boundaries tree_header* th = (tree_header*)buf; bool checksum_error = !check_tree_checksum(Vcb, th); if (checksum_error || addr != th->address || (generation != 0 && generation != th->generation)) { uint64_t off; uint16_t stripe; get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes, &off, &stripe); ERR("unrecoverable checksum error at %I64x, device %I64x\n", addr, devices[stripe]->devitem.dev_id); if (checksum_error) { log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); return STATUS_CRC_ERROR; } else if (addr != th->address) { WARN("address of tree was %I64x, not %I64x as expected\n", th->address, addr); log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); return STATUS_CRC_ERROR; } else if (generation != 0 && generation != th->generation) { WARN("generation of tree was %I64x, not %I64x as expected\n", th->generation, generation); log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_GENERATION_ERRORS); return STATUS_CRC_ERROR; } } } else if (context->csum) { NTSTATUS Status; Status = check_csum(Vcb, buf, length >> Vcb->sector_shift, context->csum); if (Status == STATUS_CRC_ERROR) { void* ptr = context->csum; for (uint32_t i = 0; i < length >> Vcb->sector_shift; i++) { if (!check_sector_csum(Vcb, buf + (i << Vcb->sector_shift), ptr)) { uint64_t off; uint16_t stripe; get_raid0_offset(addr - offset + ((uint64_t)i << Vcb->sector_shift), ci->stripe_length, ci->num_stripes, &off, &stripe); ERR("unrecoverable checksum error at %I64x, device %I64x\n", addr, devices[stripe]->devitem.dev_id); log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); return Status; } ptr = (uint8_t*)ptr + Vcb->csum_size; } return Status; } else if (!NT_SUCCESS(Status)) { ERR("check_csum returned %08lx\n", Status); return Status; } } return STATUS_SUCCESS; } static NTSTATUS read_data_raid10(device_extension* Vcb, uint8_t* buf, uint64_t addr, uint32_t length, read_data_context* context, CHUNK_ITEM* ci, device** devices, uint64_t generation, uint64_t offset) { uint16_t stripe = 0; NTSTATUS Status; bool checksum_error = false; CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1]; for (uint16_t j = 0; j < ci->num_stripes; j++) { if (context->stripes[j].status == ReadDataStatus_Error) { WARN("stripe %u returned error %08lx\n", j, context->stripes[j].iosb.Status); log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS); return context->stripes[j].iosb.Status; } else if (context->stripes[j].status == ReadDataStatus_Success) stripe = j; } if (context->tree) { tree_header* th = (tree_header*)buf; if (!check_tree_checksum(Vcb, th)) { checksum_error = true; log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } else if (addr != th->address) { WARN("address of tree was %I64x, not %I64x as expected\n", th->address, addr); checksum_error = true; log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } else if (generation != 0 && generation != th->generation) { WARN("generation of tree was %I64x, not %I64x as expected\n", th->generation, generation); checksum_error = true; log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_GENERATION_ERRORS); } } else if (context->csum) { Status = check_csum(Vcb, buf, length >> Vcb->sector_shift, context->csum); if (Status == STATUS_CRC_ERROR) checksum_error = true; else if (!NT_SUCCESS(Status)) { ERR("check_csum returned %08lx\n", Status); return Status; } } if (!checksum_error) return STATUS_SUCCESS; if (context->tree) { tree_header* t2; uint64_t off; uint16_t badsubstripe = 0; bool recovered = false; t2 = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size, ALLOC_TAG); if (!t2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes / ci->sub_stripes, &off, &stripe); stripe *= ci->sub_stripes; for (uint16_t j = 0; j < ci->sub_stripes; j++) { if (context->stripes[stripe + j].status == ReadDataStatus_Success) { badsubstripe = j; break; } } for (uint16_t j = 0; j < ci->sub_stripes; j++) { if (context->stripes[stripe + j].status != ReadDataStatus_Success && devices[stripe + j] && devices[stripe + j]->devobj) { Status = sync_read_phys(devices[stripe + j]->devobj, devices[stripe + j]->fileobj, cis[stripe + j].offset + off, Vcb->superblock.node_size, (uint8_t*)t2, false); if (!NT_SUCCESS(Status)) { WARN("sync_read_phys returned %08lx\n", Status); log_device_error(Vcb, devices[stripe + j], BTRFS_DEV_STAT_READ_ERRORS); } else { bool checksum_error = !check_tree_checksum(Vcb, t2); if (t2->address == addr && !checksum_error && (generation == 0 || t2->generation == generation)) { RtlCopyMemory(buf, t2, Vcb->superblock.node_size); ERR("recovering from checksum error at %I64x, device %I64x\n", addr, devices[stripe + j]->devitem.dev_id); recovered = true; if (!Vcb->readonly && !devices[stripe + badsubstripe]->readonly && devices[stripe + badsubstripe]->devobj) { // write good data over bad Status = write_data_phys(devices[stripe + badsubstripe]->devobj, devices[stripe + badsubstripe]->fileobj, cis[stripe + badsubstripe].offset + off, t2, Vcb->superblock.node_size); if (!NT_SUCCESS(Status)) { WARN("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, devices[stripe + badsubstripe], BTRFS_DEV_STAT_WRITE_ERRORS); } } break; } else if (t2->address != addr || checksum_error) log_device_error(Vcb, devices[stripe + j], BTRFS_DEV_STAT_CORRUPTION_ERRORS); else log_device_error(Vcb, devices[stripe + j], BTRFS_DEV_STAT_GENERATION_ERRORS); } } } if (!recovered) { ERR("unrecoverable checksum error at %I64x\n", addr); ExFreePool(t2); return STATUS_CRC_ERROR; } ExFreePool(t2); } else { ULONG sectors = length >> Vcb->sector_shift; uint8_t* sector; void* ptr = context->csum; sector = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.sector_size, ALLOC_TAG); if (!sector) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } for (ULONG i = 0; i < sectors; i++) { if (!check_sector_csum(Vcb, buf + (i << Vcb->sector_shift), ptr)) { uint64_t off; uint16_t stripe2, badsubstripe = 0; bool recovered = false; get_raid0_offset(addr - offset + ((uint64_t)i << Vcb->sector_shift), ci->stripe_length, ci->num_stripes / ci->sub_stripes, &off, &stripe2); stripe2 *= ci->sub_stripes; for (uint16_t j = 0; j < ci->sub_stripes; j++) { if (context->stripes[stripe2 + j].status == ReadDataStatus_Success) { badsubstripe = j; break; } } log_device_error(Vcb, devices[stripe2 + badsubstripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); for (uint16_t j = 0; j < ci->sub_stripes; j++) { if (context->stripes[stripe2 + j].status != ReadDataStatus_Success && devices[stripe2 + j] && devices[stripe2 + j]->devobj) { Status = sync_read_phys(devices[stripe2 + j]->devobj, devices[stripe2 + j]->fileobj, cis[stripe2 + j].offset + off, Vcb->superblock.sector_size, sector, false); if (!NT_SUCCESS(Status)) { WARN("sync_read_phys returned %08lx\n", Status); log_device_error(Vcb, devices[stripe2 + j], BTRFS_DEV_STAT_READ_ERRORS); } else { if (check_sector_csum(Vcb, sector, ptr)) { RtlCopyMemory(buf + (i << Vcb->sector_shift), sector, Vcb->superblock.sector_size); ERR("recovering from checksum error at %I64x, device %I64x\n", addr + ((uint64_t)i << Vcb->sector_shift), devices[stripe2 + j]->devitem.dev_id); recovered = true; if (!Vcb->readonly && !devices[stripe2 + badsubstripe]->readonly && devices[stripe2 + badsubstripe]->devobj) { // write good data over bad Status = write_data_phys(devices[stripe2 + badsubstripe]->devobj, devices[stripe2 + badsubstripe]->fileobj, cis[stripe2 + badsubstripe].offset + off, sector, Vcb->superblock.sector_size); if (!NT_SUCCESS(Status)) { WARN("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, devices[stripe2 + badsubstripe], BTRFS_DEV_STAT_READ_ERRORS); } } break; } else log_device_error(Vcb, devices[stripe2 + j], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } } if (!recovered) { ERR("unrecoverable checksum error at %I64x\n", addr + ((uint64_t)i << Vcb->sector_shift)); ExFreePool(sector); return STATUS_CRC_ERROR; } } ptr = (uint8_t*)ptr + Vcb->csum_size; } ExFreePool(sector); } return STATUS_SUCCESS; } static NTSTATUS read_data_raid5(device_extension* Vcb, uint8_t* buf, uint64_t addr, uint32_t length, read_data_context* context, CHUNK_ITEM* ci, device** devices, uint64_t offset, uint64_t generation, chunk* c, bool degraded) { NTSTATUS Status; bool checksum_error = false; CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1]; uint16_t j, stripe = 0; bool no_success = true; for (j = 0; j < ci->num_stripes; j++) { if (context->stripes[j].status == ReadDataStatus_Error) { WARN("stripe %u returned error %08lx\n", j, context->stripes[j].iosb.Status); log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS); return context->stripes[j].iosb.Status; } else if (context->stripes[j].status == ReadDataStatus_Success) { stripe = j; no_success = false; } } if (c) { // check partial stripes LIST_ENTRY* le; uint64_t ps_length = (ci->num_stripes - 1) * ci->stripe_length; ExAcquireResourceSharedLite(&c->partial_stripes_lock, true); le = c->partial_stripes.Flink; while (le != &c->partial_stripes) { partial_stripe* ps = CONTAINING_RECORD(le, partial_stripe, list_entry); if (ps->address + ps_length > addr && ps->address < addr + length) { ULONG runlength, index; runlength = RtlFindFirstRunClear(&ps->bmp, &index); while (runlength != 0) { if (index >= ps->bmplen) break; if (index + runlength >= ps->bmplen) { runlength = ps->bmplen - index; if (runlength == 0) break; } uint64_t runstart = ps->address + (index << Vcb->sector_shift); uint64_t runend = runstart + (runlength << Vcb->sector_shift); uint64_t start = max(runstart, addr); uint64_t end = min(runend, addr + length); if (end > start) RtlCopyMemory(buf + start - addr, &ps->data[start - ps->address], (ULONG)(end - start)); runlength = RtlFindNextForwardRunClear(&ps->bmp, index + runlength, &index); } } else if (ps->address >= addr + length) break; le = le->Flink; } ExReleaseResourceLite(&c->partial_stripes_lock); } if (context->tree) { tree_header* th = (tree_header*)buf; if (addr != th->address || !check_tree_checksum(Vcb, th)) { checksum_error = true; if (!no_success && !degraded) log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } else if (generation != 0 && generation != th->generation) { checksum_error = true; if (!no_success && !degraded) log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_GENERATION_ERRORS); } } else if (context->csum) { Status = check_csum(Vcb, buf, length >> Vcb->sector_shift, context->csum); if (Status == STATUS_CRC_ERROR) { if (!degraded) WARN("checksum error\n"); checksum_error = true; } else if (!NT_SUCCESS(Status)) { ERR("check_csum returned %08lx\n", Status); return Status; } } else if (degraded) checksum_error = true; if (!checksum_error) return STATUS_SUCCESS; if (context->tree) { uint16_t parity; uint64_t off; bool recovered = false, first = true, failed = false; uint8_t* t2; t2 = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size * 2, ALLOC_TAG); if (!t2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes - 1, &off, &stripe); parity = (((addr - offset) / ((ci->num_stripes - 1) * ci->stripe_length)) + ci->num_stripes - 1) % ci->num_stripes; stripe = (parity + stripe + 1) % ci->num_stripes; for (j = 0; j < ci->num_stripes; j++) { if (j != stripe) { if (devices[j] && devices[j]->devobj) { if (first) { Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + off, Vcb->superblock.node_size, t2, false); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS); failed = true; break; } first = false; } else { Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + off, Vcb->superblock.node_size, t2 + Vcb->superblock.node_size, false); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS); failed = true; break; } do_xor(t2, t2 + Vcb->superblock.node_size, Vcb->superblock.node_size); } } else { failed = true; break; } } } if (!failed) { tree_header* t3 = (tree_header*)t2; if (t3->address == addr && check_tree_checksum(Vcb, t3) && (generation == 0 || t3->generation == generation)) { RtlCopyMemory(buf, t2, Vcb->superblock.node_size); if (!degraded) ERR("recovering from checksum error at %I64x, device %I64x\n", addr, devices[stripe]->devitem.dev_id); recovered = true; if (!Vcb->readonly && devices[stripe] && !devices[stripe]->readonly && devices[stripe]->devobj) { // write good data over bad Status = write_data_phys(devices[stripe]->devobj, devices[stripe]->fileobj, cis[stripe].offset + off, t2, Vcb->superblock.node_size); if (!NT_SUCCESS(Status)) { WARN("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_WRITE_ERRORS); } } } } if (!recovered) { ERR("unrecoverable checksum error at %I64x\n", addr); ExFreePool(t2); return STATUS_CRC_ERROR; } ExFreePool(t2); } else { ULONG sectors = length >> Vcb->sector_shift; uint8_t* sector; void* ptr = context->csum; sector = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.sector_size * 2, ALLOC_TAG); if (!sector) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } for (ULONG i = 0; i < sectors; i++) { uint16_t parity; uint64_t off; get_raid0_offset(addr - offset + ((uint64_t)i << Vcb->sector_shift), ci->stripe_length, ci->num_stripes - 1, &off, &stripe); parity = (((addr - offset + ((uint64_t)i << Vcb->sector_shift)) / ((ci->num_stripes - 1) * ci->stripe_length)) + ci->num_stripes - 1) % ci->num_stripes; stripe = (parity + stripe + 1) % ci->num_stripes; if (!devices[stripe] || !devices[stripe]->devobj || (ptr && !check_sector_csum(Vcb, buf + (i << Vcb->sector_shift), ptr))) { bool recovered = false, first = true, failed = false; if (devices[stripe] && devices[stripe]->devobj) log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_READ_ERRORS); for (j = 0; j < ci->num_stripes; j++) { if (j != stripe) { if (devices[j] && devices[j]->devobj) { if (first) { Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + off, Vcb->superblock.sector_size, sector, false); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); failed = true; log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS); break; } first = false; } else { Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + off, Vcb->superblock.sector_size, sector + Vcb->superblock.sector_size, false); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); failed = true; log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS); break; } do_xor(sector, sector + Vcb->superblock.sector_size, Vcb->superblock.sector_size); } } else { failed = true; break; } } } if (!failed) { if (!ptr || check_sector_csum(Vcb, sector, ptr)) { RtlCopyMemory(buf + (i << Vcb->sector_shift), sector, Vcb->superblock.sector_size); if (!degraded) ERR("recovering from checksum error at %I64x, device %I64x\n", addr + ((uint64_t)i << Vcb->sector_shift), devices[stripe]->devitem.dev_id); recovered = true; if (!Vcb->readonly && devices[stripe] && !devices[stripe]->readonly && devices[stripe]->devobj) { // write good data over bad Status = write_data_phys(devices[stripe]->devobj, devices[stripe]->fileobj, cis[stripe].offset + off, sector, Vcb->superblock.sector_size); if (!NT_SUCCESS(Status)) { WARN("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_WRITE_ERRORS); } } } } if (!recovered) { ERR("unrecoverable checksum error at %I64x\n", addr + ((uint64_t)i << Vcb->sector_shift)); ExFreePool(sector); return STATUS_CRC_ERROR; } } if (ptr) ptr = (uint8_t*)ptr + Vcb->csum_size; } ExFreePool(sector); } return STATUS_SUCCESS; } void raid6_recover2(uint8_t* sectors, uint16_t num_stripes, ULONG sector_size, uint16_t missing1, uint16_t missing2, uint8_t* out) { if (missing1 == num_stripes - 2 || missing2 == num_stripes - 2) { // reconstruct from q and data uint16_t missing = missing1 == (num_stripes - 2) ? missing2 : missing1; uint16_t stripe; stripe = num_stripes - 3; if (stripe == missing) RtlZeroMemory(out, sector_size); else RtlCopyMemory(out, sectors + (stripe * sector_size), sector_size); do { stripe--; galois_double(out, sector_size); if (stripe != missing) do_xor(out, sectors + (stripe * sector_size), sector_size); } while (stripe > 0); do_xor(out, sectors + ((num_stripes - 1) * sector_size), sector_size); if (missing != 0) galois_divpower(out, (uint8_t)missing, sector_size); } else { // reconstruct from p and q uint16_t x = missing1, y = missing2, stripe; uint8_t gyx, gx, denom, a, b, *p, *q, *pxy, *qxy; uint32_t j; stripe = num_stripes - 3; pxy = out + sector_size; qxy = out; if (stripe == missing1 || stripe == missing2) { RtlZeroMemory(qxy, sector_size); RtlZeroMemory(pxy, sector_size); } else { RtlCopyMemory(qxy, sectors + (stripe * sector_size), sector_size); RtlCopyMemory(pxy, sectors + (stripe * sector_size), sector_size); } do { stripe--; galois_double(qxy, sector_size); if (stripe != missing1 && stripe != missing2) { do_xor(qxy, sectors + (stripe * sector_size), sector_size); do_xor(pxy, sectors + (stripe * sector_size), sector_size); } } while (stripe > 0); gyx = gpow2(y > x ? (y-x) : (255-x+y)); gx = gpow2(255-x); denom = gdiv(1, gyx ^ 1); a = gmul(gyx, denom); b = gmul(gx, denom); p = sectors + ((num_stripes - 2) * sector_size); q = sectors + ((num_stripes - 1) * sector_size); for (j = 0; j < sector_size; j++) { *qxy = gmul(a, *p ^ *pxy) ^ gmul(b, *q ^ *qxy); p++; q++; pxy++; qxy++; } do_xor(out + sector_size, out, sector_size); do_xor(out + sector_size, sectors + ((num_stripes - 2) * sector_size), sector_size); } } static NTSTATUS read_data_raid6(device_extension* Vcb, uint8_t* buf, uint64_t addr, uint32_t length, read_data_context* context, CHUNK_ITEM* ci, device** devices, uint64_t offset, uint64_t generation, chunk* c, bool degraded) { NTSTATUS Status; bool checksum_error = false; CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1]; uint16_t stripe = 0, j; bool no_success = true; for (j = 0; j < ci->num_stripes; j++) { if (context->stripes[j].status == ReadDataStatus_Error) { WARN("stripe %u returned error %08lx\n", j, context->stripes[j].iosb.Status); if (devices[j]) log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS); return context->stripes[j].iosb.Status; } else if (context->stripes[j].status == ReadDataStatus_Success) { stripe = j; no_success = false; } } if (c) { // check partial stripes LIST_ENTRY* le; uint64_t ps_length = (ci->num_stripes - 2) * ci->stripe_length; ExAcquireResourceSharedLite(&c->partial_stripes_lock, true); le = c->partial_stripes.Flink; while (le != &c->partial_stripes) { partial_stripe* ps = CONTAINING_RECORD(le, partial_stripe, list_entry); if (ps->address + ps_length > addr && ps->address < addr + length) { ULONG runlength, index; runlength = RtlFindFirstRunClear(&ps->bmp, &index); while (runlength != 0) { if (index >= ps->bmplen) break; if (index + runlength >= ps->bmplen) { runlength = ps->bmplen - index; if (runlength == 0) break; } uint64_t runstart = ps->address + (index << Vcb->sector_shift); uint64_t runend = runstart + (runlength << Vcb->sector_shift); uint64_t start = max(runstart, addr); uint64_t end = min(runend, addr + length); if (end > start) RtlCopyMemory(buf + start - addr, &ps->data[start - ps->address], (ULONG)(end - start)); runlength = RtlFindNextForwardRunClear(&ps->bmp, index + runlength, &index); } } else if (ps->address >= addr + length) break; le = le->Flink; } ExReleaseResourceLite(&c->partial_stripes_lock); } if (context->tree) { tree_header* th = (tree_header*)buf; if (addr != th->address || !check_tree_checksum(Vcb, th)) { checksum_error = true; if (!no_success && !degraded && devices[stripe]) log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } else if (generation != 0 && generation != th->generation) { checksum_error = true; if (!no_success && !degraded && devices[stripe]) log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_GENERATION_ERRORS); } } else if (context->csum) { Status = check_csum(Vcb, buf, length >> Vcb->sector_shift, context->csum); if (Status == STATUS_CRC_ERROR) { if (!degraded) WARN("checksum error\n"); checksum_error = true; } else if (!NT_SUCCESS(Status)) { ERR("check_csum returned %08lx\n", Status); return Status; } } else if (degraded) checksum_error = true; if (!checksum_error) return STATUS_SUCCESS; if (context->tree) { uint8_t* sector; uint16_t k, physstripe, parity1, parity2, error_stripe = 0; uint64_t off; bool recovered = false, failed = false; ULONG num_errors = 0; sector = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size * (ci->num_stripes + 2), ALLOC_TAG); if (!sector) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes - 2, &off, &stripe); parity1 = (((addr - offset) / ((ci->num_stripes - 2) * ci->stripe_length)) + ci->num_stripes - 2) % ci->num_stripes; parity2 = (parity1 + 1) % ci->num_stripes; physstripe = (parity2 + stripe + 1) % ci->num_stripes; j = (parity2 + 1) % ci->num_stripes; for (k = 0; k < ci->num_stripes - 1; k++) { if (j != physstripe) { if (devices[j] && devices[j]->devobj) { Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + off, Vcb->superblock.node_size, sector + (k * Vcb->superblock.node_size), false); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS); num_errors++; error_stripe = k; if (num_errors > 1) { failed = true; break; } } } else { num_errors++; error_stripe = k; if (num_errors > 1) { failed = true; break; } } } j = (j + 1) % ci->num_stripes; } if (!failed) { if (num_errors == 0) { tree_header* th = (tree_header*)(sector + (stripe * Vcb->superblock.node_size)); RtlCopyMemory(sector + (stripe * Vcb->superblock.node_size), sector + ((ci->num_stripes - 2) * Vcb->superblock.node_size), Vcb->superblock.node_size); for (j = 0; j < ci->num_stripes - 2; j++) { if (j != stripe) do_xor(sector + (stripe * Vcb->superblock.node_size), sector + (j * Vcb->superblock.node_size), Vcb->superblock.node_size); } if (th->address == addr && check_tree_checksum(Vcb, th) && (generation == 0 || th->generation == generation)) { RtlCopyMemory(buf, sector + (stripe * Vcb->superblock.node_size), Vcb->superblock.node_size); if (devices[physstripe] && devices[physstripe]->devobj) ERR("recovering from checksum error at %I64x, device %I64x\n", addr, devices[physstripe]->devitem.dev_id); recovered = true; if (!Vcb->readonly && devices[physstripe] && devices[physstripe]->devobj && !devices[physstripe]->readonly) { // write good data over bad Status = write_data_phys(devices[physstripe]->devobj, devices[physstripe]->fileobj, cis[physstripe].offset + off, sector + (stripe * Vcb->superblock.node_size), Vcb->superblock.node_size); if (!NT_SUCCESS(Status)) { WARN("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, devices[physstripe], BTRFS_DEV_STAT_WRITE_ERRORS); } } } } if (!recovered) { tree_header* th = (tree_header*)(sector + (ci->num_stripes * Vcb->superblock.node_size)); bool read_q = false; if (devices[parity2] && devices[parity2]->devobj) { Status = sync_read_phys(devices[parity2]->devobj, devices[parity2]->fileobj, cis[parity2].offset + off, Vcb->superblock.node_size, sector + ((ci->num_stripes - 1) * Vcb->superblock.node_size), false); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS); } else read_q = true; } if (read_q) { if (num_errors == 1) { raid6_recover2(sector, ci->num_stripes, Vcb->superblock.node_size, stripe, error_stripe, sector + (ci->num_stripes * Vcb->superblock.node_size)); if (th->address == addr && check_tree_checksum(Vcb, th) && (generation == 0 || th->generation == generation)) recovered = true; } else { for (j = 0; j < ci->num_stripes - 1; j++) { if (j != stripe) { raid6_recover2(sector, ci->num_stripes, Vcb->superblock.node_size, stripe, j, sector + (ci->num_stripes * Vcb->superblock.node_size)); if (th->address == addr && check_tree_checksum(Vcb, th) && (generation == 0 || th->generation == generation)) { recovered = true; error_stripe = j; break; } } } } } if (recovered) { uint16_t error_stripe_phys = (parity2 + error_stripe + 1) % ci->num_stripes; if (devices[physstripe] && devices[physstripe]->devobj) ERR("recovering from checksum error at %I64x, device %I64x\n", addr, devices[physstripe]->devitem.dev_id); RtlCopyMemory(buf, sector + (ci->num_stripes * Vcb->superblock.node_size), Vcb->superblock.node_size); if (!Vcb->readonly && devices[physstripe] && devices[physstripe]->devobj && !devices[physstripe]->readonly) { // write good data over bad Status = write_data_phys(devices[physstripe]->devobj, devices[physstripe]->fileobj, cis[physstripe].offset + off, sector + (ci->num_stripes * Vcb->superblock.node_size), Vcb->superblock.node_size); if (!NT_SUCCESS(Status)) { WARN("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, devices[physstripe], BTRFS_DEV_STAT_WRITE_ERRORS); } } if (devices[error_stripe_phys] && devices[error_stripe_phys]->devobj) { if (error_stripe == ci->num_stripes - 2) { ERR("recovering from parity error at %I64x, device %I64x\n", addr, devices[error_stripe_phys]->devitem.dev_id); log_device_error(Vcb, devices[error_stripe_phys], BTRFS_DEV_STAT_CORRUPTION_ERRORS); RtlZeroMemory(sector + ((ci->num_stripes - 2) * Vcb->superblock.node_size), Vcb->superblock.node_size); for (j = 0; j < ci->num_stripes - 2; j++) { if (j == stripe) { do_xor(sector + ((ci->num_stripes - 2) * Vcb->superblock.node_size), sector + (ci->num_stripes * Vcb->superblock.node_size), Vcb->superblock.node_size); } else { do_xor(sector + ((ci->num_stripes - 2) * Vcb->superblock.node_size), sector + (j * Vcb->superblock.node_size), Vcb->superblock.node_size); } } } else { ERR("recovering from checksum error at %I64x, device %I64x\n", addr + ((error_stripe - stripe) * ci->stripe_length), devices[error_stripe_phys]->devitem.dev_id); log_device_error(Vcb, devices[error_stripe_phys], BTRFS_DEV_STAT_CORRUPTION_ERRORS); RtlCopyMemory(sector + (error_stripe * Vcb->superblock.node_size), sector + ((ci->num_stripes + 1) * Vcb->superblock.node_size), Vcb->superblock.node_size); } } if (!Vcb->readonly && devices[error_stripe_phys] && devices[error_stripe_phys]->devobj && !devices[error_stripe_phys]->readonly) { // write good data over bad Status = write_data_phys(devices[error_stripe_phys]->devobj, devices[error_stripe_phys]->fileobj, cis[error_stripe_phys].offset + off, sector + (error_stripe * Vcb->superblock.node_size), Vcb->superblock.node_size); if (!NT_SUCCESS(Status)) { WARN("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, devices[error_stripe_phys], BTRFS_DEV_STAT_WRITE_ERRORS); } } } } } if (!recovered) { ERR("unrecoverable checksum error at %I64x\n", addr); ExFreePool(sector); return STATUS_CRC_ERROR; } ExFreePool(sector); } else { ULONG sectors = length >> Vcb->sector_shift; uint8_t* sector; void* ptr = context->csum; sector = ExAllocatePoolWithTag(NonPagedPool, (ci->num_stripes + 2) << Vcb->sector_shift, ALLOC_TAG); if (!sector) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } for (ULONG i = 0; i < sectors; i++) { uint64_t off; uint16_t physstripe, parity1, parity2; get_raid0_offset(addr - offset + ((uint64_t)i << Vcb->sector_shift), ci->stripe_length, ci->num_stripes - 2, &off, &stripe); parity1 = (((addr - offset + ((uint64_t)i << Vcb->sector_shift)) / ((ci->num_stripes - 2) * ci->stripe_length)) + ci->num_stripes - 2) % ci->num_stripes; parity2 = (parity1 + 1) % ci->num_stripes; physstripe = (parity2 + stripe + 1) % ci->num_stripes; if (!devices[physstripe] || !devices[physstripe]->devobj || (context->csum && !check_sector_csum(Vcb, buf + (i << Vcb->sector_shift), ptr))) { uint16_t error_stripe = 0; bool recovered = false, failed = false; ULONG num_errors = 0; if (devices[physstripe] && devices[physstripe]->devobj) log_device_error(Vcb, devices[physstripe], BTRFS_DEV_STAT_READ_ERRORS); j = (parity2 + 1) % ci->num_stripes; for (uint16_t k = 0; k < ci->num_stripes - 1; k++) { if (j != physstripe) { if (devices[j] && devices[j]->devobj) { Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + off, Vcb->superblock.sector_size, sector + ((ULONG)k << Vcb->sector_shift), false); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS); num_errors++; error_stripe = k; if (num_errors > 1) { failed = true; break; } } } else { num_errors++; error_stripe = k; if (num_errors > 1) { failed = true; break; } } } j = (j + 1) % ci->num_stripes; } if (!failed) { if (num_errors == 0) { RtlCopyMemory(sector + ((unsigned int)stripe << Vcb->sector_shift), sector + ((unsigned int)(ci->num_stripes - 2) << Vcb->sector_shift), Vcb->superblock.sector_size); for (j = 0; j < ci->num_stripes - 2; j++) { if (j != stripe) do_xor(sector + ((unsigned int)stripe << Vcb->sector_shift), sector + ((unsigned int)j << Vcb->sector_shift), Vcb->superblock.sector_size); } if (!ptr || check_sector_csum(Vcb, sector + ((unsigned int)stripe << Vcb->sector_shift), ptr)) { RtlCopyMemory(buf + (i << Vcb->sector_shift), sector + ((unsigned int)stripe << Vcb->sector_shift), Vcb->superblock.sector_size); if (devices[physstripe] && devices[physstripe]->devobj) ERR("recovering from checksum error at %I64x, device %I64x\n", addr + ((uint64_t)i << Vcb->sector_shift), devices[physstripe]->devitem.dev_id); recovered = true; if (!Vcb->readonly && devices[physstripe] && devices[physstripe]->devobj && !devices[physstripe]->readonly) { // write good data over bad Status = write_data_phys(devices[physstripe]->devobj, devices[physstripe]->fileobj, cis[physstripe].offset + off, sector + ((unsigned int)stripe << Vcb->sector_shift), Vcb->superblock.sector_size); if (!NT_SUCCESS(Status)) { WARN("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, devices[physstripe], BTRFS_DEV_STAT_WRITE_ERRORS); } } } } if (!recovered) { bool read_q = false; if (devices[parity2] && devices[parity2]->devobj) { Status = sync_read_phys(devices[parity2]->devobj, devices[parity2]->fileobj, cis[parity2].offset + off, Vcb->superblock.sector_size, sector + ((unsigned int)(ci->num_stripes - 1) << Vcb->sector_shift), false); if (!NT_SUCCESS(Status)) { ERR("sync_read_phys returned %08lx\n", Status); log_device_error(Vcb, devices[parity2], BTRFS_DEV_STAT_READ_ERRORS); } else read_q = true; } if (read_q) { if (num_errors == 1) { raid6_recover2(sector, ci->num_stripes, Vcb->superblock.sector_size, stripe, error_stripe, sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift)); if (!devices[physstripe] || !devices[physstripe]->devobj) recovered = true; else recovered = check_sector_csum(Vcb, sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift), ptr); } else { for (j = 0; j < ci->num_stripes - 1; j++) { if (j != stripe) { raid6_recover2(sector, ci->num_stripes, Vcb->superblock.sector_size, stripe, j, sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift)); if (check_sector_csum(Vcb, sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift), ptr)) { recovered = true; error_stripe = j; break; } } } } } if (recovered) { uint16_t error_stripe_phys = (parity2 + error_stripe + 1) % ci->num_stripes; if (devices[physstripe] && devices[physstripe]->devobj) ERR("recovering from checksum error at %I64x, device %I64x\n", addr + ((uint64_t)i << Vcb->sector_shift), devices[physstripe]->devitem.dev_id); RtlCopyMemory(buf + (i << Vcb->sector_shift), sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift), Vcb->superblock.sector_size); if (!Vcb->readonly && devices[physstripe] && devices[physstripe]->devobj && !devices[physstripe]->readonly) { // write good data over bad Status = write_data_phys(devices[physstripe]->devobj, devices[physstripe]->fileobj, cis[physstripe].offset + off, sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift), Vcb->superblock.sector_size); if (!NT_SUCCESS(Status)) { WARN("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, devices[physstripe], BTRFS_DEV_STAT_WRITE_ERRORS); } } if (devices[error_stripe_phys] && devices[error_stripe_phys]->devobj) { if (error_stripe == ci->num_stripes - 2) { ERR("recovering from parity error at %I64x, device %I64x\n", addr + ((uint64_t)i << Vcb->sector_shift), devices[error_stripe_phys]->devitem.dev_id); log_device_error(Vcb, devices[error_stripe_phys], BTRFS_DEV_STAT_CORRUPTION_ERRORS); RtlZeroMemory(sector + ((unsigned int)(ci->num_stripes - 2) << Vcb->sector_shift), Vcb->superblock.sector_size); for (j = 0; j < ci->num_stripes - 2; j++) { if (j == stripe) { do_xor(sector + ((unsigned int)(ci->num_stripes - 2) << Vcb->sector_shift), sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift), Vcb->superblock.sector_size); } else { do_xor(sector + ((unsigned int)(ci->num_stripes - 2) << Vcb->sector_shift), sector + ((unsigned int)j << Vcb->sector_shift), Vcb->superblock.sector_size); } } } else { ERR("recovering from checksum error at %I64x, device %I64x\n", addr + ((uint64_t)i << Vcb->sector_shift) + ((error_stripe - stripe) * ci->stripe_length), devices[error_stripe_phys]->devitem.dev_id); log_device_error(Vcb, devices[error_stripe_phys], BTRFS_DEV_STAT_CORRUPTION_ERRORS); RtlCopyMemory(sector + ((unsigned int)error_stripe << Vcb->sector_shift), sector + ((unsigned int)(ci->num_stripes + 1) << Vcb->sector_shift), Vcb->superblock.sector_size); } } if (!Vcb->readonly && devices[error_stripe_phys] && devices[error_stripe_phys]->devobj && !devices[error_stripe_phys]->readonly) { // write good data over bad Status = write_data_phys(devices[error_stripe_phys]->devobj, devices[error_stripe_phys]->fileobj, cis[error_stripe_phys].offset + off, sector + ((unsigned int)error_stripe << Vcb->sector_shift), Vcb->superblock.sector_size); if (!NT_SUCCESS(Status)) { WARN("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, devices[error_stripe_phys], BTRFS_DEV_STAT_WRITE_ERRORS); } } } } } if (!recovered) { ERR("unrecoverable checksum error at %I64x\n", addr + ((uint64_t)i << Vcb->sector_shift)); ExFreePool(sector); return STATUS_CRC_ERROR; } } if (ptr) ptr = (uint8_t*)ptr + Vcb->csum_size; } ExFreePool(sector); } return STATUS_SUCCESS; } NTSTATUS 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, _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, _In_ ULONG priority) { CHUNK_ITEM* ci; CHUNK_ITEM_STRIPE* cis; read_data_context context; uint64_t type, offset, total_reading = 0; NTSTATUS Status; device** devices = NULL; uint16_t i, startoffstripe, allowed_missing, missing_devices = 0; uint8_t* dummypage = NULL; PMDL dummy_mdl = NULL; bool need_to_wait; uint64_t lockaddr, locklen; if (Vcb->log_to_phys_loaded) { if (!c) { c = get_chunk_from_address(Vcb, addr); if (!c) { ERR("get_chunk_from_address failed\n"); return STATUS_INTERNAL_ERROR; } } ci = c->chunk_item; offset = c->offset; devices = c->devices; if (pc) *pc = c; } else { LIST_ENTRY* le = Vcb->sys_chunks.Flink; ci = NULL; c = NULL; while (le != &Vcb->sys_chunks) { sys_chunk* sc = CONTAINING_RECORD(le, sys_chunk, list_entry); if (sc->key.obj_id == 0x100 && sc->key.obj_type == TYPE_CHUNK_ITEM && sc->key.offset <= addr) { CHUNK_ITEM* chunk_item = sc->data; if ((addr - sc->key.offset) < chunk_item->size && chunk_item->num_stripes > 0) { ci = chunk_item; offset = sc->key.offset; cis = (CHUNK_ITEM_STRIPE*)&chunk_item[1]; devices = ExAllocatePoolWithTag(NonPagedPool, sizeof(device*) * ci->num_stripes, ALLOC_TAG); if (!devices) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } for (i = 0; i < ci->num_stripes; i++) { devices[i] = find_device_from_uuid(Vcb, &cis[i].dev_uuid); } break; } } le = le->Flink; } if (!ci) { ERR("could not find chunk for %I64x in bootstrap\n", addr); return STATUS_INTERNAL_ERROR; } if (pc) *pc = NULL; } if (ci->type & BLOCK_FLAG_DUPLICATE) { type = BLOCK_FLAG_DUPLICATE; allowed_missing = ci->num_stripes - 1; } else if (ci->type & BLOCK_FLAG_RAID0) { type = BLOCK_FLAG_RAID0; allowed_missing = 0; } else if (ci->type & BLOCK_FLAG_RAID1) { type = BLOCK_FLAG_DUPLICATE; allowed_missing = 1; } else if (ci->type & BLOCK_FLAG_RAID10) { type = BLOCK_FLAG_RAID10; allowed_missing = 1; } else if (ci->type & BLOCK_FLAG_RAID5) { type = BLOCK_FLAG_RAID5; allowed_missing = 1; } else if (ci->type & BLOCK_FLAG_RAID6) { type = BLOCK_FLAG_RAID6; allowed_missing = 2; } else if (ci->type & BLOCK_FLAG_RAID1C3) { type = BLOCK_FLAG_DUPLICATE; allowed_missing = 2; } else if (ci->type & BLOCK_FLAG_RAID1C4) { type = BLOCK_FLAG_DUPLICATE; allowed_missing = 3; } else { // SINGLE type = BLOCK_FLAG_DUPLICATE; allowed_missing = 0; } cis = (CHUNK_ITEM_STRIPE*)&ci[1]; RtlZeroMemory(&context, sizeof(read_data_context)); KeInitializeEvent(&context.Event, NotificationEvent, false); context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(read_data_stripe) * ci->num_stripes, ALLOC_TAG); if (!context.stripes) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } if (c && (type == BLOCK_FLAG_RAID5 || type == BLOCK_FLAG_RAID6)) { get_raid56_lock_range(c, addr, length, &lockaddr, &locklen); chunk_lock_range(Vcb, c, lockaddr, locklen); } RtlZeroMemory(context.stripes, sizeof(read_data_stripe) * ci->num_stripes); context.buflen = length; context.num_stripes = ci->num_stripes; context.stripes_left = context.num_stripes; context.sector_size = Vcb->superblock.sector_size; context.csum = csum; context.tree = is_tree; context.type = type; if (type == BLOCK_FLAG_RAID0) { uint64_t startoff, endoff; uint16_t endoffstripe, stripe; uint32_t *stripeoff, pos; PMDL master_mdl; PFN_NUMBER* pfns; // FIXME - test this still works if page size isn't the same as sector size // This relies on the fact that MDLs are followed in memory by the page file numbers, // so with a bit of jiggery-pokery you can trick your disks into deinterlacing your RAID0 // data for you without doing a memcpy yourself. // MDLs are officially opaque, so this might very well break in future versions of Windows. get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes, &startoff, &startoffstripe); get_raid0_offset(addr + length - offset - 1, ci->stripe_length, ci->num_stripes, &endoff, &endoffstripe); if (file_read) { // Unfortunately we can't avoid doing at least one memcpy, as Windows can give us an MDL // with duplicated dummy PFNs, which confuse check_csum. Ah well. // See https://msdn.microsoft.com/en-us/library/windows/hardware/Dn614012.aspx if you're interested. context.va = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG); if (!context.va) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } else context.va = buf; master_mdl = IoAllocateMdl(context.va, length, false, false, NULL); if (!master_mdl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Status = STATUS_SUCCESS; try { MmProbeAndLockPages(master_mdl, KernelMode, IoWriteAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); IoFreeMdl(master_mdl); goto exit; } pfns = (PFN_NUMBER*)(master_mdl + 1); for (i = 0; i < ci->num_stripes; i++) { if (startoffstripe > i) context.stripes[i].stripestart = startoff - (startoff % ci->stripe_length) + ci->stripe_length; else if (startoffstripe == i) context.stripes[i].stripestart = startoff; else context.stripes[i].stripestart = startoff - (startoff % ci->stripe_length); if (endoffstripe > i) context.stripes[i].stripeend = endoff - (endoff % ci->stripe_length) + ci->stripe_length; else if (endoffstripe == i) context.stripes[i].stripeend = endoff + 1; else context.stripes[i].stripeend = endoff - (endoff % ci->stripe_length); if (context.stripes[i].stripestart != context.stripes[i].stripeend) { context.stripes[i].mdl = IoAllocateMdl(context.va, (ULONG)(context.stripes[i].stripeend - context.stripes[i].stripestart), false, false, NULL); if (!context.stripes[i].mdl) { ERR("IoAllocateMdl failed\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } } stripeoff = ExAllocatePoolWithTag(NonPagedPool, sizeof(uint32_t) * ci->num_stripes, ALLOC_TAG); if (!stripeoff) { ERR("out of memory\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlZeroMemory(stripeoff, sizeof(uint32_t) * ci->num_stripes); pos = 0; stripe = startoffstripe; while (pos < length) { PFN_NUMBER* stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1); if (pos == 0) { uint32_t readlen = (uint32_t)min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart, ci->stripe_length - (context.stripes[stripe].stripestart % ci->stripe_length)); RtlCopyMemory(stripe_pfns, pfns, readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); stripeoff[stripe] += readlen; pos += readlen; } else if (length - pos < ci->stripe_length) { RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (length - pos) * sizeof(PFN_NUMBER) >> PAGE_SHIFT); pos = length; } else { RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(ci->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); stripeoff[stripe] += (uint32_t)ci->stripe_length; pos += (uint32_t)ci->stripe_length; } stripe = (stripe + 1) % ci->num_stripes; } MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); ExFreePool(stripeoff); } else if (type == BLOCK_FLAG_RAID10) { uint64_t startoff, endoff; uint16_t endoffstripe, j, stripe; ULONG orig_ls; PMDL master_mdl; PFN_NUMBER* pfns; uint32_t* stripeoff, pos; read_data_stripe** stripes; if (c) orig_ls = c->last_stripe; else orig_ls = 0; get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes / ci->sub_stripes, &startoff, &startoffstripe); get_raid0_offset(addr + length - offset - 1, ci->stripe_length, ci->num_stripes / ci->sub_stripes, &endoff, &endoffstripe); if ((ci->num_stripes % ci->sub_stripes) != 0) { ERR("chunk %I64x: num_stripes %x was not a multiple of sub_stripes %x!\n", offset, ci->num_stripes, ci->sub_stripes); Status = STATUS_INTERNAL_ERROR; goto exit; } if (file_read) { context.va = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG); if (!context.va) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } else context.va = buf; context.firstoff = (uint16_t)((startoff % ci->stripe_length) >> Vcb->sector_shift); context.startoffstripe = startoffstripe; context.sectors_per_stripe = (uint16_t)(ci->stripe_length >> Vcb->sector_shift); startoffstripe *= ci->sub_stripes; endoffstripe *= ci->sub_stripes; if (c) c->last_stripe = (orig_ls + 1) % ci->sub_stripes; master_mdl = IoAllocateMdl(context.va, length, false, false, NULL); if (!master_mdl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Status = STATUS_SUCCESS; try { MmProbeAndLockPages(master_mdl, KernelMode, IoWriteAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); IoFreeMdl(master_mdl); goto exit; } pfns = (PFN_NUMBER*)(master_mdl + 1); stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(read_data_stripe*) * ci->num_stripes / ci->sub_stripes, ALLOC_TAG); if (!stripes) { ERR("out of memory\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlZeroMemory(stripes, sizeof(read_data_stripe*) * ci->num_stripes / ci->sub_stripes); for (i = 0; i < ci->num_stripes; i += ci->sub_stripes) { uint64_t sstart, send; bool stripeset = false; if (startoffstripe > i) sstart = startoff - (startoff % ci->stripe_length) + ci->stripe_length; else if (startoffstripe == i) sstart = startoff; else sstart = startoff - (startoff % ci->stripe_length); if (endoffstripe > i) send = endoff - (endoff % ci->stripe_length) + ci->stripe_length; else if (endoffstripe == i) send = endoff + 1; else send = endoff - (endoff % ci->stripe_length); for (j = 0; j < ci->sub_stripes; j++) { if (j == orig_ls && devices[i+j] && devices[i+j]->devobj) { context.stripes[i+j].stripestart = sstart; context.stripes[i+j].stripeend = send; stripes[i / ci->sub_stripes] = &context.stripes[i+j]; if (sstart != send) { context.stripes[i+j].mdl = IoAllocateMdl(context.va, (ULONG)(send - sstart), false, false, NULL); if (!context.stripes[i+j].mdl) { ERR("IoAllocateMdl failed\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } stripeset = true; } else context.stripes[i+j].status = ReadDataStatus_Skip; } if (!stripeset) { for (j = 0; j < ci->sub_stripes; j++) { if (devices[i+j] && devices[i+j]->devobj) { context.stripes[i+j].stripestart = sstart; context.stripes[i+j].stripeend = send; context.stripes[i+j].status = ReadDataStatus_Pending; stripes[i / ci->sub_stripes] = &context.stripes[i+j]; if (sstart != send) { context.stripes[i+j].mdl = IoAllocateMdl(context.va, (ULONG)(send - sstart), false, false, NULL); if (!context.stripes[i+j].mdl) { ERR("IoAllocateMdl failed\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } stripeset = true; break; } } if (!stripeset) { ERR("could not find stripe to read\n"); Status = STATUS_DEVICE_NOT_READY; goto exit; } } } stripeoff = ExAllocatePoolWithTag(NonPagedPool, sizeof(uint32_t) * ci->num_stripes / ci->sub_stripes, ALLOC_TAG); if (!stripeoff) { ERR("out of memory\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlZeroMemory(stripeoff, sizeof(uint32_t) * ci->num_stripes / ci->sub_stripes); pos = 0; stripe = startoffstripe / ci->sub_stripes; while (pos < length) { PFN_NUMBER* stripe_pfns = (PFN_NUMBER*)(stripes[stripe]->mdl + 1); if (pos == 0) { uint32_t readlen = (uint32_t)min(stripes[stripe]->stripeend - stripes[stripe]->stripestart, ci->stripe_length - (stripes[stripe]->stripestart % ci->stripe_length)); RtlCopyMemory(stripe_pfns, pfns, readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); stripeoff[stripe] += readlen; pos += readlen; } else if (length - pos < ci->stripe_length) { RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (length - pos) * sizeof(PFN_NUMBER) >> PAGE_SHIFT); pos = length; } else { RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(ci->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); stripeoff[stripe] += (ULONG)ci->stripe_length; pos += (ULONG)ci->stripe_length; } stripe = (stripe + 1) % (ci->num_stripes / ci->sub_stripes); } MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); ExFreePool(stripeoff); ExFreePool(stripes); } else if (type == BLOCK_FLAG_DUPLICATE) { uint64_t orig_ls; if (c) orig_ls = i = c->last_stripe; else orig_ls = i = 0; while (!devices[i] || !devices[i]->devobj) { i = (i + 1) % ci->num_stripes; if (i == orig_ls) { ERR("no devices available to service request\n"); Status = STATUS_DEVICE_NOT_READY; goto exit; } } if (c) c->last_stripe = (i + 1) % ci->num_stripes; context.stripes[i].stripestart = addr - offset; context.stripes[i].stripeend = context.stripes[i].stripestart + length; if (file_read) { context.va = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG); if (!context.va) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } context.stripes[i].mdl = IoAllocateMdl(context.va, length, false, false, NULL); if (!context.stripes[i].mdl) { ERR("IoAllocateMdl failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } MmBuildMdlForNonPagedPool(context.stripes[i].mdl); } else { context.stripes[i].mdl = IoAllocateMdl(buf, length, false, false, NULL); if (!context.stripes[i].mdl) { ERR("IoAllocateMdl failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Status = STATUS_SUCCESS; try { MmProbeAndLockPages(context.stripes[i].mdl, KernelMode, IoWriteAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); goto exit; } } } else if (type == BLOCK_FLAG_RAID5) { uint64_t startoff, endoff; uint16_t endoffstripe, parity; uint32_t *stripeoff, pos; PMDL master_mdl; PFN_NUMBER *pfns, dummy = 0; bool need_dummy = false; get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes - 1, &startoff, &startoffstripe); get_raid0_offset(addr + length - offset - 1, ci->stripe_length, ci->num_stripes - 1, &endoff, &endoffstripe); if (file_read) { context.va = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG); if (!context.va) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } else context.va = buf; master_mdl = IoAllocateMdl(context.va, length, false, false, NULL); if (!master_mdl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Status = STATUS_SUCCESS; try { MmProbeAndLockPages(master_mdl, KernelMode, IoWriteAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); IoFreeMdl(master_mdl); goto exit; } pfns = (PFN_NUMBER*)(master_mdl + 1); pos = 0; while (pos < length) { parity = (((addr - offset + pos) / ((ci->num_stripes - 1) * ci->stripe_length)) + ci->num_stripes - 1) % ci->num_stripes; if (pos == 0) { uint16_t stripe = (parity + startoffstripe + 1) % ci->num_stripes; ULONG skip, readlen; i = startoffstripe; while (stripe != parity) { if (i == startoffstripe) { readlen = min(length, (ULONG)(ci->stripe_length - (startoff % ci->stripe_length))); context.stripes[stripe].stripestart = startoff; context.stripes[stripe].stripeend = startoff + readlen; pos += readlen; if (pos == length) break; } else { readlen = min(length - pos, (ULONG)ci->stripe_length); context.stripes[stripe].stripestart = startoff - (startoff % ci->stripe_length); context.stripes[stripe].stripeend = context.stripes[stripe].stripestart + readlen; pos += readlen; if (pos == length) break; } i++; stripe = (stripe + 1) % ci->num_stripes; } if (pos == length) break; for (i = 0; i < startoffstripe; i++) { uint16_t stripe2 = (parity + i + 1) % ci->num_stripes; context.stripes[stripe2].stripestart = context.stripes[stripe2].stripeend = startoff - (startoff % ci->stripe_length) + ci->stripe_length; } context.stripes[parity].stripestart = context.stripes[parity].stripeend = startoff - (startoff % ci->stripe_length) + ci->stripe_length; if (length - pos > ci->num_stripes * (ci->num_stripes - 1) * ci->stripe_length) { skip = (ULONG)(((length - pos) / (ci->num_stripes * (ci->num_stripes - 1) * ci->stripe_length)) - 1); for (i = 0; i < ci->num_stripes; i++) { context.stripes[i].stripeend += skip * ci->num_stripes * ci->stripe_length; } pos += (uint32_t)(skip * (ci->num_stripes - 1) * ci->num_stripes * ci->stripe_length); need_dummy = true; } } else if (length - pos >= ci->stripe_length * (ci->num_stripes - 1)) { for (i = 0; i < ci->num_stripes; i++) { context.stripes[i].stripeend += ci->stripe_length; } pos += (uint32_t)(ci->stripe_length * (ci->num_stripes - 1)); need_dummy = true; } else { uint16_t stripe = (parity + 1) % ci->num_stripes; i = 0; while (stripe != parity) { if (endoffstripe == i) { context.stripes[stripe].stripeend = endoff + 1; break; } else if (endoffstripe > i) context.stripes[stripe].stripeend = endoff - (endoff % ci->stripe_length) + ci->stripe_length; i++; stripe = (stripe + 1) % ci->num_stripes; } break; } } for (i = 0; i < ci->num_stripes; i++) { if (context.stripes[i].stripestart != context.stripes[i].stripeend) { context.stripes[i].mdl = IoAllocateMdl(context.va, (ULONG)(context.stripes[i].stripeend - context.stripes[i].stripestart), false, false, NULL); if (!context.stripes[i].mdl) { ERR("IoAllocateMdl failed\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } } if (need_dummy) { dummypage = ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, ALLOC_TAG); if (!dummypage) { ERR("out of memory\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } dummy_mdl = IoAllocateMdl(dummypage, PAGE_SIZE, false, false, NULL); if (!dummy_mdl) { ERR("IoAllocateMdl failed\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } MmBuildMdlForNonPagedPool(dummy_mdl); dummy = *(PFN_NUMBER*)(dummy_mdl + 1); } stripeoff = ExAllocatePoolWithTag(NonPagedPool, sizeof(uint32_t) * ci->num_stripes, ALLOC_TAG); if (!stripeoff) { ERR("out of memory\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlZeroMemory(stripeoff, sizeof(uint32_t) * ci->num_stripes); pos = 0; while (pos < length) { PFN_NUMBER* stripe_pfns; parity = (((addr - offset + pos) / ((ci->num_stripes - 1) * ci->stripe_length)) + ci->num_stripes - 1) % ci->num_stripes; if (pos == 0) { uint16_t stripe = (parity + startoffstripe + 1) % ci->num_stripes; uint32_t readlen = min(length - pos, (uint32_t)min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart, ci->stripe_length - (context.stripes[stripe].stripestart % ci->stripe_length))); stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1); RtlCopyMemory(stripe_pfns, pfns, readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); stripeoff[stripe] = readlen; pos += readlen; stripe = (stripe + 1) % ci->num_stripes; while (stripe != parity) { stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1); readlen = min(length - pos, (uint32_t)min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart, ci->stripe_length)); if (readlen == 0) break; RtlCopyMemory(stripe_pfns, &pfns[pos >> PAGE_SHIFT], readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); stripeoff[stripe] = readlen; pos += readlen; stripe = (stripe + 1) % ci->num_stripes; } } else if (length - pos >= ci->stripe_length * (ci->num_stripes - 1)) { uint16_t stripe = (parity + 1) % ci->num_stripes; ULONG k; while (stripe != parity) { stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1); RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(ci->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); stripeoff[stripe] += (uint32_t)ci->stripe_length; pos += (uint32_t)ci->stripe_length; stripe = (stripe + 1) % ci->num_stripes; } stripe_pfns = (PFN_NUMBER*)(context.stripes[parity].mdl + 1); for (k = 0; k < ci->stripe_length >> PAGE_SHIFT; k++) { stripe_pfns[stripeoff[parity] >> PAGE_SHIFT] = dummy; stripeoff[parity] += PAGE_SIZE; } } else { uint16_t stripe = (parity + 1) % ci->num_stripes; uint32_t readlen; while (pos < length) { stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1); readlen = min(length - pos, (ULONG)min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart, ci->stripe_length)); if (readlen == 0) break; RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); stripeoff[stripe] += readlen; pos += readlen; stripe = (stripe + 1) % ci->num_stripes; } } } MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); ExFreePool(stripeoff); } else if (type == BLOCK_FLAG_RAID6) { uint64_t startoff, endoff; uint16_t endoffstripe, parity1; uint32_t *stripeoff, pos; PMDL master_mdl; PFN_NUMBER *pfns, dummy = 0; bool need_dummy = false; get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes - 2, &startoff, &startoffstripe); get_raid0_offset(addr + length - offset - 1, ci->stripe_length, ci->num_stripes - 2, &endoff, &endoffstripe); if (file_read) { context.va = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG); if (!context.va) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } else context.va = buf; master_mdl = IoAllocateMdl(context.va, length, false, false, NULL); if (!master_mdl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Status = STATUS_SUCCESS; try { MmProbeAndLockPages(master_mdl, KernelMode, IoWriteAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); IoFreeMdl(master_mdl); goto exit; } pfns = (PFN_NUMBER*)(master_mdl + 1); pos = 0; while (pos < length) { parity1 = (((addr - offset + pos) / ((ci->num_stripes - 2) * ci->stripe_length)) + ci->num_stripes - 2) % ci->num_stripes; if (pos == 0) { uint16_t stripe = (parity1 + startoffstripe + 2) % ci->num_stripes, parity2; ULONG skip, readlen; i = startoffstripe; while (stripe != parity1) { if (i == startoffstripe) { readlen = (ULONG)min(length, ci->stripe_length - (startoff % ci->stripe_length)); context.stripes[stripe].stripestart = startoff; context.stripes[stripe].stripeend = startoff + readlen; pos += readlen; if (pos == length) break; } else { readlen = min(length - pos, (ULONG)ci->stripe_length); context.stripes[stripe].stripestart = startoff - (startoff % ci->stripe_length); context.stripes[stripe].stripeend = context.stripes[stripe].stripestart + readlen; pos += readlen; if (pos == length) break; } i++; stripe = (stripe + 1) % ci->num_stripes; } if (pos == length) break; for (i = 0; i < startoffstripe; i++) { uint16_t stripe2 = (parity1 + i + 2) % ci->num_stripes; context.stripes[stripe2].stripestart = context.stripes[stripe2].stripeend = startoff - (startoff % ci->stripe_length) + ci->stripe_length; } context.stripes[parity1].stripestart = context.stripes[parity1].stripeend = startoff - (startoff % ci->stripe_length) + ci->stripe_length; parity2 = (parity1 + 1) % ci->num_stripes; context.stripes[parity2].stripestart = context.stripes[parity2].stripeend = startoff - (startoff % ci->stripe_length) + ci->stripe_length; if (length - pos > ci->num_stripes * (ci->num_stripes - 2) * ci->stripe_length) { skip = (ULONG)(((length - pos) / (ci->num_stripes * (ci->num_stripes - 2) * ci->stripe_length)) - 1); for (i = 0; i < ci->num_stripes; i++) { context.stripes[i].stripeend += skip * ci->num_stripes * ci->stripe_length; } pos += (uint32_t)(skip * (ci->num_stripes - 2) * ci->num_stripes * ci->stripe_length); need_dummy = true; } } else if (length - pos >= ci->stripe_length * (ci->num_stripes - 2)) { for (i = 0; i < ci->num_stripes; i++) { context.stripes[i].stripeend += ci->stripe_length; } pos += (uint32_t)(ci->stripe_length * (ci->num_stripes - 2)); need_dummy = true; } else { uint16_t stripe = (parity1 + 2) % ci->num_stripes; i = 0; while (stripe != parity1) { if (endoffstripe == i) { context.stripes[stripe].stripeend = endoff + 1; break; } else if (endoffstripe > i) context.stripes[stripe].stripeend = endoff - (endoff % ci->stripe_length) + ci->stripe_length; i++; stripe = (stripe + 1) % ci->num_stripes; } break; } } for (i = 0; i < ci->num_stripes; i++) { if (context.stripes[i].stripestart != context.stripes[i].stripeend) { context.stripes[i].mdl = IoAllocateMdl(context.va, (ULONG)(context.stripes[i].stripeend - context.stripes[i].stripestart), false, false, NULL); if (!context.stripes[i].mdl) { ERR("IoAllocateMdl failed\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } } if (need_dummy) { dummypage = ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, ALLOC_TAG); if (!dummypage) { ERR("out of memory\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } dummy_mdl = IoAllocateMdl(dummypage, PAGE_SIZE, false, false, NULL); if (!dummy_mdl) { ERR("IoAllocateMdl failed\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } MmBuildMdlForNonPagedPool(dummy_mdl); dummy = *(PFN_NUMBER*)(dummy_mdl + 1); } stripeoff = ExAllocatePoolWithTag(NonPagedPool, sizeof(uint32_t) * ci->num_stripes, ALLOC_TAG); if (!stripeoff) { ERR("out of memory\n"); MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlZeroMemory(stripeoff, sizeof(uint32_t) * ci->num_stripes); pos = 0; while (pos < length) { PFN_NUMBER* stripe_pfns; parity1 = (((addr - offset + pos) / ((ci->num_stripes - 2) * ci->stripe_length)) + ci->num_stripes - 2) % ci->num_stripes; if (pos == 0) { uint16_t stripe = (parity1 + startoffstripe + 2) % ci->num_stripes; uint32_t readlen = min(length - pos, (uint32_t)min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart, ci->stripe_length - (context.stripes[stripe].stripestart % ci->stripe_length))); stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1); RtlCopyMemory(stripe_pfns, pfns, readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); stripeoff[stripe] = readlen; pos += readlen; stripe = (stripe + 1) % ci->num_stripes; while (stripe != parity1) { stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1); readlen = (uint32_t)min(length - pos, min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart, ci->stripe_length)); if (readlen == 0) break; RtlCopyMemory(stripe_pfns, &pfns[pos >> PAGE_SHIFT], readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); stripeoff[stripe] = readlen; pos += readlen; stripe = (stripe + 1) % ci->num_stripes; } } else if (length - pos >= ci->stripe_length * (ci->num_stripes - 2)) { uint16_t stripe = (parity1 + 2) % ci->num_stripes; uint16_t parity2 = (parity1 + 1) % ci->num_stripes; ULONG k; while (stripe != parity1) { stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1); RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(ci->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); stripeoff[stripe] += (uint32_t)ci->stripe_length; pos += (uint32_t)ci->stripe_length; stripe = (stripe + 1) % ci->num_stripes; } stripe_pfns = (PFN_NUMBER*)(context.stripes[parity1].mdl + 1); for (k = 0; k < ci->stripe_length >> PAGE_SHIFT; k++) { stripe_pfns[stripeoff[parity1] >> PAGE_SHIFT] = dummy; stripeoff[parity1] += PAGE_SIZE; } stripe_pfns = (PFN_NUMBER*)(context.stripes[parity2].mdl + 1); for (k = 0; k < ci->stripe_length >> PAGE_SHIFT; k++) { stripe_pfns[stripeoff[parity2] >> PAGE_SHIFT] = dummy; stripeoff[parity2] += PAGE_SIZE; } } else { uint16_t stripe = (parity1 + 2) % ci->num_stripes; uint32_t readlen; while (pos < length) { stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1); readlen = (uint32_t)min(length - pos, min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart, ci->stripe_length)); if (readlen == 0) break; RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); stripeoff[stripe] += readlen; pos += readlen; stripe = (stripe + 1) % ci->num_stripes; } } } MmUnlockPages(master_mdl); IoFreeMdl(master_mdl); ExFreePool(stripeoff); } context.address = addr; for (i = 0; i < ci->num_stripes; i++) { if (!devices[i] || !devices[i]->devobj || context.stripes[i].stripestart == context.stripes[i].stripeend) { context.stripes[i].status = ReadDataStatus_MissingDevice; context.stripes_left--; if (!devices[i] || !devices[i]->devobj) missing_devices++; } } if (missing_devices > allowed_missing) { ERR("not enough devices to service request (%u missing)\n", missing_devices); Status = STATUS_UNEXPECTED_IO_ERROR; goto exit; } for (i = 0; i < ci->num_stripes; i++) { PIO_STACK_LOCATION IrpSp; if (devices[i] && devices[i]->devobj && context.stripes[i].stripestart != context.stripes[i].stripeend && context.stripes[i].status != ReadDataStatus_Skip) { context.stripes[i].context = (struct read_data_context*)&context; if (type == BLOCK_FLAG_RAID10) { context.stripes[i].stripenum = i / ci->sub_stripes; } if (!Irp) { context.stripes[i].Irp = IoAllocateIrp(devices[i]->devobj->StackSize, false); if (!context.stripes[i].Irp) { ERR("IoAllocateIrp failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } else { context.stripes[i].Irp = IoMakeAssociatedIrp(Irp, devices[i]->devobj->StackSize); if (!context.stripes[i].Irp) { ERR("IoMakeAssociatedIrp failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } IrpSp = IoGetNextIrpStackLocation(context.stripes[i].Irp); IrpSp->MajorFunction = IRP_MJ_READ; IrpSp->MinorFunction = IRP_MN_NORMAL; IrpSp->FileObject = devices[i]->fileobj; if (devices[i]->devobj->Flags & DO_BUFFERED_IO) { context.stripes[i].Irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPool, (ULONG)(context.stripes[i].stripeend - context.stripes[i].stripestart), ALLOC_TAG); if (!context.stripes[i].Irp->AssociatedIrp.SystemBuffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } context.stripes[i].Irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION; context.stripes[i].Irp->UserBuffer = MmGetSystemAddressForMdlSafe(context.stripes[i].mdl, priority); } else if (devices[i]->devobj->Flags & DO_DIRECT_IO) context.stripes[i].Irp->MdlAddress = context.stripes[i].mdl; else context.stripes[i].Irp->UserBuffer = MmGetSystemAddressForMdlSafe(context.stripes[i].mdl, priority); IrpSp->Parameters.Read.Length = (ULONG)(context.stripes[i].stripeend - context.stripes[i].stripestart); IrpSp->Parameters.Read.ByteOffset.QuadPart = context.stripes[i].stripestart + cis[i].offset; total_reading += IrpSp->Parameters.Read.Length; context.stripes[i].Irp->UserIosb = &context.stripes[i].iosb; IoSetCompletionRoutine(context.stripes[i].Irp, read_data_completion, &context.stripes[i], true, true, true); context.stripes[i].status = ReadDataStatus_Pending; } } need_to_wait = false; for (i = 0; i < ci->num_stripes; i++) { if (context.stripes[i].status != ReadDataStatus_MissingDevice && context.stripes[i].status != ReadDataStatus_Skip) { IoCallDriver(devices[i]->devobj, context.stripes[i].Irp); need_to_wait = true; } } if (need_to_wait) KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL); if (diskacc) fFsRtlUpdateDiskCounters(total_reading, 0); // check if any of the devices return a "user-induced" error for (i = 0; i < ci->num_stripes; i++) { if (context.stripes[i].status == ReadDataStatus_Error && IoIsErrorUserInduced(context.stripes[i].iosb.Status)) { Status = context.stripes[i].iosb.Status; goto exit; } } if (type == BLOCK_FLAG_RAID0) { Status = read_data_raid0(Vcb, file_read ? context.va : buf, addr, length, &context, ci, devices, generation, offset); if (!NT_SUCCESS(Status)) { ERR("read_data_raid0 returned %08lx\n", Status); if (file_read) ExFreePool(context.va); goto exit; } if (file_read) { RtlCopyMemory(buf, context.va, length); ExFreePool(context.va); } } else if (type == BLOCK_FLAG_RAID10) { Status = read_data_raid10(Vcb, file_read ? context.va : buf, addr, length, &context, ci, devices, generation, offset); if (!NT_SUCCESS(Status)) { ERR("read_data_raid10 returned %08lx\n", Status); if (file_read) ExFreePool(context.va); goto exit; } if (file_read) { RtlCopyMemory(buf, context.va, length); ExFreePool(context.va); } } else if (type == BLOCK_FLAG_DUPLICATE) { Status = read_data_dup(Vcb, file_read ? context.va : buf, addr, &context, ci, devices, generation); if (!NT_SUCCESS(Status)) { ERR("read_data_dup returned %08lx\n", Status); if (file_read) ExFreePool(context.va); goto exit; } if (file_read) { RtlCopyMemory(buf, context.va, length); ExFreePool(context.va); } } else if (type == BLOCK_FLAG_RAID5) { Status = read_data_raid5(Vcb, file_read ? context.va : buf, addr, length, &context, ci, devices, offset, generation, c, missing_devices > 0 ? true : false); if (!NT_SUCCESS(Status)) { ERR("read_data_raid5 returned %08lx\n", Status); if (file_read) ExFreePool(context.va); goto exit; } if (file_read) { RtlCopyMemory(buf, context.va, length); ExFreePool(context.va); } } else if (type == BLOCK_FLAG_RAID6) { Status = read_data_raid6(Vcb, file_read ? context.va : buf, addr, length, &context, ci, devices, offset, generation, c, missing_devices > 0 ? true : false); if (!NT_SUCCESS(Status)) { ERR("read_data_raid6 returned %08lx\n", Status); if (file_read) ExFreePool(context.va); goto exit; } if (file_read) { RtlCopyMemory(buf, context.va, length); ExFreePool(context.va); } } exit: if (c && (type == BLOCK_FLAG_RAID5 || type == BLOCK_FLAG_RAID6)) chunk_unlock_range(Vcb, c, lockaddr, locklen); if (dummy_mdl) IoFreeMdl(dummy_mdl); if (dummypage) ExFreePool(dummypage); for (i = 0; i < ci->num_stripes; i++) { if (context.stripes[i].mdl) { if (context.stripes[i].mdl->MdlFlags & MDL_PAGES_LOCKED) MmUnlockPages(context.stripes[i].mdl); IoFreeMdl(context.stripes[i].mdl); } if (context.stripes[i].Irp) IoFreeIrp(context.stripes[i].Irp); } ExFreePool(context.stripes); if (!Vcb->log_to_phys_loaded) ExFreePool(devices); return Status; } __attribute__((nonnull(1, 2))) NTSTATUS read_stream(fcb* fcb, uint8_t* data, uint64_t start, ULONG length, ULONG* pbr) { ULONG readlen; TRACE("(%p, %p, %I64x, %lx, %p)\n", fcb, data, start, length, pbr); if (pbr) *pbr = 0; if (start >= fcb->adsdata.Length) { TRACE("tried to read beyond end of stream\n"); return STATUS_END_OF_FILE; } if (length == 0) { WARN("tried to read zero bytes\n"); return STATUS_SUCCESS; } if (start + length < fcb->adsdata.Length) readlen = length; else readlen = fcb->adsdata.Length - (ULONG)start; if (readlen > 0) RtlCopyMemory(data, fcb->adsdata.Buffer + start, readlen); if (pbr) *pbr = readlen; return STATUS_SUCCESS; } typedef struct { uint64_t off; uint64_t ed_size; uint64_t ed_offset; uint64_t ed_num_bytes; } read_part_extent; typedef struct { LIST_ENTRY list_entry; uint64_t addr; chunk* c; uint32_t read; uint32_t to_read; void* csum; bool csum_free; uint8_t* buf; bool buf_free; uint32_t bumpoff; bool mdl; void* data; uint8_t compression; unsigned int num_extents; read_part_extent extents[1]; } read_part; typedef struct { LIST_ENTRY list_entry; calc_job* cj; void* decomp; void* data; unsigned int offset; size_t length; } comp_calc_job; __attribute__((nonnull(1, 2))) NTSTATUS read_file(fcb* fcb, uint8_t* data, uint64_t start, uint64_t length, ULONG* pbr, PIRP Irp) { NTSTATUS Status; uint32_t bytes_read = 0; uint64_t last_end; LIST_ENTRY* le; POOL_TYPE pool_type; LIST_ENTRY read_parts, calc_jobs; TRACE("(%p, %p, %I64x, %I64x, %p)\n", fcb, data, start, length, pbr); if (pbr) *pbr = 0; if (start >= fcb->inode_item.st_size) { WARN("Tried to read beyond end of file\n"); return STATUS_END_OF_FILE; } InitializeListHead(&read_parts); InitializeListHead(&calc_jobs); pool_type = fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? NonPagedPool : PagedPool; le = fcb->extents.Flink; last_end = start; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (!ext->ignore) { EXTENT_DATA* ed = &ext->extent_data; uint64_t len; if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) len = ((EXTENT_DATA2*)ed->data)->num_bytes; else len = ed->decoded_size; if (ext->offset + len <= start) { last_end = ext->offset + len; goto nextitem; } if (ext->offset > last_end && ext->offset > start + bytes_read) { uint32_t read = (uint32_t)min(length, ext->offset - max(start, last_end)); RtlZeroMemory(data + bytes_read, read); bytes_read += read; length -= read; } if (length == 0 || ext->offset > start + bytes_read + length) break; if (ed->encryption != BTRFS_ENCRYPTION_NONE) { WARN("Encryption not supported\n"); Status = STATUS_NOT_IMPLEMENTED; goto exit; } if (ed->encoding != BTRFS_ENCODING_NONE) { WARN("Other encodings not supported\n"); Status = STATUS_NOT_IMPLEMENTED; goto exit; } switch (ed->type) { case EXTENT_TYPE_INLINE: { uint64_t off = start + bytes_read - ext->offset; uint32_t read; if (ed->compression == BTRFS_COMPRESSION_NONE) { read = (uint32_t)min(min(len, ext->datalen) - off, length); RtlCopyMemory(data + bytes_read, &ed->data[off], read); } else if (ed->compression == BTRFS_COMPRESSION_ZLIB || ed->compression == BTRFS_COMPRESSION_LZO || ed->compression == BTRFS_COMPRESSION_ZSTD) { uint8_t* decomp; bool decomp_alloc; uint16_t inlen = ext->datalen - (uint16_t)offsetof(EXTENT_DATA, data[0]); if (ed->decoded_size == 0 || ed->decoded_size > 0xffffffff) { ERR("ed->decoded_size was invalid (%I64x)\n", ed->decoded_size); Status = STATUS_INTERNAL_ERROR; goto exit; } read = (uint32_t)min(ed->decoded_size - off, length); if (off > 0) { decomp = ExAllocatePoolWithTag(NonPagedPool, (uint32_t)ed->decoded_size, ALLOC_TAG); if (!decomp) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } decomp_alloc = true; } else { decomp = data + bytes_read; decomp_alloc = false; } if (ed->compression == BTRFS_COMPRESSION_ZLIB) { Status = zlib_decompress(ed->data, inlen, decomp, (uint32_t)(read + off)); if (!NT_SUCCESS(Status)) { ERR("zlib_decompress returned %08lx\n", Status); if (decomp_alloc) ExFreePool(decomp); goto exit; } } else if (ed->compression == BTRFS_COMPRESSION_LZO) { if (inlen < sizeof(uint32_t)) { ERR("extent data was truncated\n"); Status = STATUS_INTERNAL_ERROR; if (decomp_alloc) ExFreePool(decomp); goto exit; } else inlen -= sizeof(uint32_t); Status = lzo_decompress(ed->data + sizeof(uint32_t), inlen, decomp, (uint32_t)(read + off), sizeof(uint32_t)); if (!NT_SUCCESS(Status)) { ERR("lzo_decompress returned %08lx\n", Status); if (decomp_alloc) ExFreePool(decomp); goto exit; } } else if (ed->compression == BTRFS_COMPRESSION_ZSTD) { Status = zstd_decompress(ed->data, inlen, decomp, (uint32_t)(read + off)); if (!NT_SUCCESS(Status)) { ERR("zstd_decompress returned %08lx\n", Status); if (decomp_alloc) ExFreePool(decomp); goto exit; } } if (decomp_alloc) { RtlCopyMemory(data + bytes_read, decomp + off, read); ExFreePool(decomp); } } else { ERR("unhandled compression type %x\n", ed->compression); Status = STATUS_NOT_IMPLEMENTED; goto exit; } bytes_read += read; length -= read; break; } case EXTENT_TYPE_REGULAR: { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; read_part* rp; rp = ExAllocatePoolWithTag(pool_type, sizeof(read_part), ALLOC_TAG); if (!rp) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } rp->mdl = (Irp && Irp->MdlAddress) ? true : false; rp->extents[0].off = start + bytes_read - ext->offset; rp->bumpoff = 0; rp->num_extents = 1; rp->csum_free = false; rp->read = (uint32_t)(len - rp->extents[0].off); if (rp->read > length) rp->read = (uint32_t)length; if (ed->compression == BTRFS_COMPRESSION_NONE) { rp->addr = ed2->address + ed2->offset + rp->extents[0].off; rp->to_read = (uint32_t)sector_align(rp->read, fcb->Vcb->superblock.sector_size); if (rp->addr & (fcb->Vcb->superblock.sector_size - 1)) { rp->bumpoff = rp->addr & (fcb->Vcb->superblock.sector_size - 1); rp->addr -= rp->bumpoff; rp->to_read = (uint32_t)sector_align(rp->read + rp->bumpoff, fcb->Vcb->superblock.sector_size); } } else { rp->addr = ed2->address; rp->to_read = (uint32_t)sector_align(ed2->size, fcb->Vcb->superblock.sector_size); } if (ed->compression == BTRFS_COMPRESSION_NONE && (start & (fcb->Vcb->superblock.sector_size - 1)) == 0 && (length & (fcb->Vcb->superblock.sector_size - 1)) == 0) { rp->buf = data + bytes_read; rp->buf_free = false; } else { rp->buf = ExAllocatePoolWithTag(pool_type, rp->to_read, ALLOC_TAG); rp->buf_free = true; if (!rp->buf) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(rp); goto exit; } rp->mdl = false; } rp->c = get_chunk_from_address(fcb->Vcb, rp->addr); if (!rp->c) { ERR("get_chunk_from_address(%I64x) failed\n", rp->addr); if (rp->buf_free) ExFreePool(rp->buf); ExFreePool(rp); Status = STATUS_INTERNAL_ERROR; goto exit; } if (ext->csum) { if (ed->compression == BTRFS_COMPRESSION_NONE) { rp->csum = (uint8_t*)ext->csum + (fcb->Vcb->csum_size * (rp->extents[0].off >> fcb->Vcb->sector_shift)); } else rp->csum = ext->csum; } else rp->csum = NULL; rp->data = data + bytes_read; rp->compression = ed->compression; rp->extents[0].ed_offset = ed2->offset; rp->extents[0].ed_size = ed2->size; rp->extents[0].ed_num_bytes = ed2->num_bytes; InsertTailList(&read_parts, &rp->list_entry); bytes_read += rp->read; length -= rp->read; break; } case EXTENT_TYPE_PREALLOC: { uint64_t off = start + bytes_read - ext->offset; uint32_t read = (uint32_t)(len - off); if (read > length) read = (uint32_t)length; RtlZeroMemory(data + bytes_read, read); bytes_read += read; length -= read; break; } default: WARN("Unsupported extent data type %u\n", ed->type); Status = STATUS_NOT_IMPLEMENTED; goto exit; } last_end = ext->offset + len; if (length == 0) break; } nextitem: le = le->Flink; } if (!IsListEmpty(&read_parts) && read_parts.Flink->Flink != &read_parts) { // at least two entries in list read_part* last_rp = CONTAINING_RECORD(read_parts.Flink, read_part, list_entry); le = read_parts.Flink->Flink; while (le != &read_parts) { LIST_ENTRY* le2 = le->Flink; read_part* rp = CONTAINING_RECORD(le, read_part, list_entry); // merge together runs if (rp->compression != BTRFS_COMPRESSION_NONE && rp->compression == last_rp->compression && rp->addr == last_rp->addr + last_rp->to_read && 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))) { read_part* rp2; rp2 = ExAllocatePoolWithTag(pool_type, offsetof(read_part, extents) + (sizeof(read_part_extent) * (last_rp->num_extents + 1)), ALLOC_TAG); rp2->addr = last_rp->addr; rp2->c = last_rp->c; rp2->read = last_rp->read + rp->read; rp2->to_read = last_rp->to_read + rp->to_read; rp2->csum_free = false; if (last_rp->csum) { uint32_t sectors = (last_rp->to_read + rp->to_read) >> fcb->Vcb->sector_shift; rp2->csum = ExAllocatePoolWithTag(pool_type, sectors * fcb->Vcb->csum_size, ALLOC_TAG); if (!rp2->csum) { ERR("out of memory\n"); ExFreePool(rp2); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlCopyMemory(rp2->csum, last_rp->csum, (last_rp->to_read * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift); RtlCopyMemory((uint8_t*)rp2->csum + ((last_rp->to_read * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift), rp->csum, (rp->to_read * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift); rp2->csum_free = true; } else rp2->csum = NULL; rp2->buf = ExAllocatePoolWithTag(pool_type, rp2->to_read, ALLOC_TAG); if (!rp2->buf) { ERR("out of memory\n"); if (rp2->csum) ExFreePool(rp2->csum); ExFreePool(rp2); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } rp2->buf_free = true; rp2->bumpoff = 0; rp2->mdl = false; rp2->data = last_rp->data; rp2->compression = last_rp->compression; rp2->num_extents = last_rp->num_extents + 1; RtlCopyMemory(rp2->extents, last_rp->extents, last_rp->num_extents * sizeof(read_part_extent)); RtlCopyMemory(&rp2->extents[last_rp->num_extents], rp->extents, sizeof(read_part_extent)); InsertHeadList(le->Blink, &rp2->list_entry); if (rp->buf_free) ExFreePool(rp->buf); if (rp->csum_free) ExFreePool(rp->csum); RemoveEntryList(&rp->list_entry); ExFreePool(rp); if (last_rp->buf_free) ExFreePool(last_rp->buf); if (last_rp->csum_free) ExFreePool(last_rp->csum); RemoveEntryList(&last_rp->list_entry); ExFreePool(last_rp); last_rp = rp2; } else last_rp = rp; le = le2; } } le = read_parts.Flink; while (le != &read_parts) { read_part* rp = CONTAINING_RECORD(le, read_part, list_entry); Status = read_data(fcb->Vcb, rp->addr, rp->to_read, rp->csum, false, rp->buf, rp->c, NULL, Irp, 0, rp->mdl, fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? HighPagePriority : NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned %08lx\n", Status); goto exit; } if (rp->compression == BTRFS_COMPRESSION_NONE) { if (rp->buf_free) RtlCopyMemory(rp->data, rp->buf + rp->bumpoff, rp->read); } else { uint8_t* buf = rp->buf; for (unsigned int i = 0; i < rp->num_extents; i++) { uint8_t *decomp = NULL, *buf2; ULONG outlen, inlen, off2; uint32_t inpageoff = 0; comp_calc_job* ccj; off2 = (ULONG)(rp->extents[i].ed_offset + rp->extents[i].off); buf2 = buf; inlen = (ULONG)rp->extents[i].ed_size; if (rp->compression == BTRFS_COMPRESSION_LZO) { ULONG inoff = sizeof(uint32_t); inlen -= sizeof(uint32_t); // If reading a few sectors in, skip to the interesting bit while (off2 > LZO_PAGE_SIZE) { uint32_t partlen; if (inlen < sizeof(uint32_t)) break; partlen = *(uint32_t*)(buf2 + inoff); if (partlen < inlen) { off2 -= LZO_PAGE_SIZE; inoff += partlen + sizeof(uint32_t); inlen -= partlen + sizeof(uint32_t); if (LZO_PAGE_SIZE - (inoff % LZO_PAGE_SIZE) < sizeof(uint32_t)) inoff = ((inoff / LZO_PAGE_SIZE) + 1) * LZO_PAGE_SIZE; } else break; } buf2 = &buf2[inoff]; inpageoff = inoff % LZO_PAGE_SIZE; } /* Previous versions of this code decompressed directly into the destination buffer, * but unfortunately that can't be relied on - Windows likes to use dummy pages sometimes * when mmap-ing, which breaks the backtracking used by e.g. zstd. */ if (off2 != 0) outlen = off2 + min(rp->read, (uint32_t)(rp->extents[i].ed_num_bytes - rp->extents[i].off)); else outlen = min(rp->read, (uint32_t)(rp->extents[i].ed_num_bytes - rp->extents[i].off)); decomp = ExAllocatePoolWithTag(pool_type, outlen, ALLOC_TAG); if (!decomp) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } ccj = (comp_calc_job*)ExAllocatePoolWithTag(pool_type, sizeof(comp_calc_job), ALLOC_TAG); if (!ccj) { ERR("out of memory\n"); ExFreePool(decomp); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } ccj->data = rp->data; ccj->decomp = decomp; ccj->offset = off2; ccj->length = (size_t)min(rp->read, rp->extents[i].ed_num_bytes - rp->extents[i].off); Status = add_calc_job_decomp(fcb->Vcb, rp->compression, buf2, inlen, decomp, outlen, inpageoff, &ccj->cj); if (!NT_SUCCESS(Status)) { ERR("add_calc_job_decomp returned %08lx\n", Status); ExFreePool(decomp); ExFreePool(ccj); goto exit; } InsertTailList(&calc_jobs, &ccj->list_entry); buf += rp->extents[i].ed_size; rp->data = (uint8_t*)rp->data + rp->extents[i].ed_num_bytes - rp->extents[i].off; rp->read -= (uint32_t)(rp->extents[i].ed_num_bytes - rp->extents[i].off); } } le = le->Flink; } if (length > 0 && start + bytes_read < fcb->inode_item.st_size) { uint32_t read = (uint32_t)min(fcb->inode_item.st_size - start - bytes_read, length); RtlZeroMemory(data + bytes_read, read); bytes_read += read; length -= read; } Status = STATUS_SUCCESS; while (!IsListEmpty(&calc_jobs)) { comp_calc_job* ccj = CONTAINING_RECORD(RemoveTailList(&calc_jobs), comp_calc_job, list_entry); calc_thread_main(fcb->Vcb, ccj->cj); KeWaitForSingleObject(&ccj->cj->event, Executive, KernelMode, false, NULL); if (!NT_SUCCESS(ccj->cj->Status)) Status = ccj->cj->Status; RtlCopyMemory(ccj->data, (uint8_t*)ccj->decomp + ccj->offset, ccj->length); ExFreePool(ccj->decomp); ExFreePool(ccj); } if (pbr) *pbr = bytes_read; exit: while (!IsListEmpty(&read_parts)) { read_part* rp = CONTAINING_RECORD(RemoveHeadList(&read_parts), read_part, list_entry); if (rp->buf_free) ExFreePool(rp->buf); if (rp->csum_free) ExFreePool(rp->csum); ExFreePool(rp); } while (!IsListEmpty(&calc_jobs)) { comp_calc_job* ccj = CONTAINING_RECORD(RemoveHeadList(&calc_jobs), comp_calc_job, list_entry); KeWaitForSingleObject(&ccj->cj->event, Executive, KernelMode, false, NULL); if (ccj->decomp) ExFreePool(ccj->decomp); ExFreePool(ccj->cj); ExFreePool(ccj); } return Status; } NTSTATUS do_read(PIRP Irp, bool wait, ULONG* bytes_read) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; fcb* fcb = FileObject->FsContext; uint8_t* data = NULL; ULONG length = IrpSp->Parameters.Read.Length, addon = 0; uint64_t start = IrpSp->Parameters.Read.ByteOffset.QuadPart; *bytes_read = 0; if (!fcb || !fcb->Vcb || !fcb->subvol) return STATUS_INTERNAL_ERROR; TRACE("fcb = %p\n", fcb); TRACE("offset = %I64x, length = %lx\n", start, length); TRACE("paging_io = %s, no cache = %s\n", Irp->Flags & IRP_PAGING_IO ? "true" : "false", Irp->Flags & IRP_NOCACHE ? "true" : "false"); if (!fcb->ads && fcb->type == BTRFS_TYPE_DIRECTORY) return STATUS_INVALID_DEVICE_REQUEST; if (!(Irp->Flags & IRP_PAGING_IO) && !FsRtlCheckLockForReadAccess(&fcb->lock, Irp)) { WARN("tried to read locked region\n"); return STATUS_FILE_LOCK_CONFLICT; } if (length == 0) { TRACE("tried to read zero bytes\n"); return STATUS_SUCCESS; } if (start >= (uint64_t)fcb->Header.FileSize.QuadPart) { TRACE("tried to read with offset after file end (%I64x >= %I64x)\n", start, fcb->Header.FileSize.QuadPart); return STATUS_END_OF_FILE; } 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); if (!(Irp->Flags & IRP_NOCACHE) && IrpSp->MinorFunction & IRP_MN_MDL) { NTSTATUS Status = STATUS_SUCCESS; try { if (!FileObject->PrivateCacheMap) { CC_FILE_SIZES ccfs; ccfs.AllocationSize = fcb->Header.AllocationSize; ccfs.FileSize = fcb->Header.FileSize; ccfs.ValidDataLength = fcb->Header.ValidDataLength; init_file_cache(FileObject, &ccfs); } CcMdlRead(FileObject, &IrpSp->Parameters.Read.ByteOffset, length, &Irp->MdlAddress, &Irp->IoStatus); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (NT_SUCCESS(Status)) { Status = Irp->IoStatus.Status; Irp->IoStatus.Information += addon; *bytes_read = (ULONG)Irp->IoStatus.Information; } else ERR("EXCEPTION - %08lx\n", Status); return Status; } data = map_user_buffer(Irp, fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? HighPagePriority : NormalPagePriority); if (Irp->MdlAddress && !data) { ERR("MmGetSystemAddressForMdlSafe returned NULL\n"); return STATUS_INSUFFICIENT_RESOURCES; } if (start >= (uint64_t)fcb->Header.ValidDataLength.QuadPart) { length = (ULONG)min(length, min(start + length, (uint64_t)fcb->Header.FileSize.QuadPart) - fcb->Header.ValidDataLength.QuadPart); RtlZeroMemory(data, length); Irp->IoStatus.Information = *bytes_read = length; return STATUS_SUCCESS; } if (length + start > (uint64_t)fcb->Header.ValidDataLength.QuadPart) { addon = (ULONG)(min(start + length, (uint64_t)fcb->Header.FileSize.QuadPart) - fcb->Header.ValidDataLength.QuadPart); RtlZeroMemory(data + (fcb->Header.ValidDataLength.QuadPart - start), addon); length = (ULONG)(fcb->Header.ValidDataLength.QuadPart - start); } if (!(Irp->Flags & IRP_NOCACHE)) { NTSTATUS Status = STATUS_SUCCESS; try { if (!FileObject->PrivateCacheMap) { CC_FILE_SIZES ccfs; ccfs.AllocationSize = fcb->Header.AllocationSize; ccfs.FileSize = fcb->Header.FileSize; ccfs.ValidDataLength = fcb->Header.ValidDataLength; init_file_cache(FileObject, &ccfs); } if (fCcCopyReadEx) { TRACE("CcCopyReadEx(%p, %I64x, %lx, %u, %p, %p, %p)\n", FileObject, IrpSp->Parameters.Read.ByteOffset.QuadPart, length, wait, data, &Irp->IoStatus, Irp->Tail.Overlay.Thread); TRACE("sizes = %I64x, %I64x, %I64x\n", fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart); if (!fCcCopyReadEx(FileObject, &IrpSp->Parameters.Read.ByteOffset, length, wait, data, &Irp->IoStatus, Irp->Tail.Overlay.Thread)) { TRACE("CcCopyReadEx could not wait\n"); IoMarkIrpPending(Irp); return STATUS_PENDING; } TRACE("CcCopyReadEx finished\n"); } else { TRACE("CcCopyRead(%p, %I64x, %lx, %u, %p, %p)\n", FileObject, IrpSp->Parameters.Read.ByteOffset.QuadPart, length, wait, data, &Irp->IoStatus); TRACE("sizes = %I64x, %I64x, %I64x\n", fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart); if (!CcCopyRead(FileObject, &IrpSp->Parameters.Read.ByteOffset, length, wait, data, &Irp->IoStatus)) { TRACE("CcCopyRead could not wait\n"); IoMarkIrpPending(Irp); return STATUS_PENDING; } TRACE("CcCopyRead finished\n"); } } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (NT_SUCCESS(Status)) { Status = Irp->IoStatus.Status; Irp->IoStatus.Information += addon; *bytes_read = (ULONG)Irp->IoStatus.Information; } else ERR("EXCEPTION - %08lx\n", Status); return Status; } else { NTSTATUS Status; if (!wait) { IoMarkIrpPending(Irp); return STATUS_PENDING; } if (fcb->ads) { Status = read_stream(fcb, data, start, length, bytes_read); if (!NT_SUCCESS(Status)) ERR("read_stream returned %08lx\n", Status); } else { Status = read_file(fcb, data, start, length, bytes_read, Irp); if (!NT_SUCCESS(Status)) ERR("read_file returned %08lx\n", Status); } *bytes_read += addon; TRACE("read %lu bytes\n", *bytes_read); Irp->IoStatus.Information = *bytes_read; if (diskacc && Status != STATUS_PENDING) { PETHREAD thread = NULL; if (Irp->Tail.Overlay.Thread && !IoIsSystemThread(Irp->Tail.Overlay.Thread)) thread = Irp->Tail.Overlay.Thread; else if (!IoIsSystemThread(PsGetCurrentThread())) thread = PsGetCurrentThread(); else if (IoIsSystemThread(PsGetCurrentThread()) && IoGetTopLevelIrp() == Irp) thread = PsGetCurrentThread(); if (thread) fPsUpdateDiskCounters(PsGetThreadProcess(thread), *bytes_read, 0, 1, 0, 0); } return Status; } } _Dispatch_type_(IRP_MJ_READ) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_read(PDEVICE_OBJECT DeviceObject, PIRP Irp) { device_extension* Vcb = DeviceObject->DeviceExtension; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; ULONG bytes_read = 0; NTSTATUS Status; bool top_level; fcb* fcb; ccb* ccb; bool acquired_fcb_lock = false, wait; FsRtlEnterFileSystem(); top_level = is_top_level(Irp); TRACE("read\n"); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = vol_read(DeviceObject, Irp); goto exit2; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } Irp->IoStatus.Information = 0; if (IrpSp->MinorFunction & IRP_MN_COMPLETE) { CcMdlReadComplete(IrpSp->FileObject, Irp->MdlAddress); Irp->MdlAddress = NULL; Status = STATUS_SUCCESS; goto exit; } fcb = FileObject->FsContext; if (!fcb) { ERR("fcb was NULL\n"); Status = STATUS_INVALID_PARAMETER; goto exit; } ccb = FileObject->FsContext2; if (!ccb) { ERR("ccb was NULL\n"); Status = STATUS_INVALID_PARAMETER; goto exit; } if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_READ_DATA)) { WARN("insufficient privileges\n"); Status = STATUS_ACCESS_DENIED; goto exit; } if (fcb == Vcb->volume_fcb) { TRACE("reading volume FCB\n"); IoSkipCurrentIrpStackLocation(Irp); Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp); goto exit2; } if (!(Irp->Flags & IRP_PAGING_IO)) FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL); wait = IoIsOperationSynchronous(Irp); // Don't offload jobs when doing paging IO - otherwise this can lead to // deadlocks in CcCopyRead. if (Irp->Flags & IRP_PAGING_IO) wait = true; if (!(Irp->Flags & IRP_PAGING_IO) && FileObject->SectionObjectPointer && FileObject->SectionObjectPointer->DataSectionObject) { IO_STATUS_BLOCK iosb; CcFlushCache(FileObject->SectionObjectPointer, &IrpSp->Parameters.Read.ByteOffset, IrpSp->Parameters.Read.Length, &iosb); if (!NT_SUCCESS(iosb.Status)) { ERR("CcFlushCache returned %08lx\n", iosb.Status); Status = iosb.Status; goto exit; } } if (!ExIsResourceAcquiredSharedLite(fcb->Header.Resource)) { if (!ExAcquireResourceSharedLite(fcb->Header.Resource, wait)) { Status = STATUS_PENDING; IoMarkIrpPending(Irp); goto exit; } acquired_fcb_lock = true; } Status = do_read(Irp, wait, &bytes_read); if (acquired_fcb_lock) ExReleaseResourceLite(fcb->Header.Resource); exit: if (FileObject->Flags & FO_SYNCHRONOUS_IO && !(Irp->Flags & IRP_PAGING_IO)) FileObject->CurrentByteOffset.QuadPart = IrpSp->Parameters.Read.ByteOffset.QuadPart + (NT_SUCCESS(Status) ? bytes_read : 0); end: Irp->IoStatus.Status = Status; TRACE("Irp->IoStatus.Status = %08lx\n", Irp->IoStatus.Status); TRACE("Irp->IoStatus.Information = %Iu\n", Irp->IoStatus.Information); TRACE("returning %08lx\n", Status); if (Status != STATUS_PENDING) IoCompleteRequest(Irp, IO_NO_INCREMENT); else { if (!add_thread_job(Vcb, Irp)) Status = do_read_job(Irp); } exit2: if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } ================================================ FILE: src/registry.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include "zstd/lib/zstd.h" extern UNICODE_STRING log_device, log_file, registry_path; extern LIST_ENTRY uid_map_list, gid_map_list; extern ERESOURCE mapping_lock; #ifdef _DEBUG extern HANDLE log_handle; extern ERESOURCE log_lock; extern PFILE_OBJECT comfo; extern PDEVICE_OBJECT comdo; #endif WORK_QUEUE_ITEM wqi; static const WCHAR option_mounted[] = L"Mounted"; NTSTATUS registry_load_volume_options(device_extension* Vcb) { BTRFS_UUID* uuid = &Vcb->superblock.uuid; mount_options* options = &Vcb->options; UNICODE_STRING path, ignoreus, compressus, compressforceus, compresstypeus, readonlyus, zliblevelus, flushintervalus, maxinlineus, subvolidus, skipbalanceus, nobarrierus, notrimus, clearcacheus, allowdegradedus, zstdlevelus, norootdirus, nodatacowus; OBJECT_ATTRIBUTES oa; NTSTATUS Status; ULONG i, j, kvfilen, index, retlen; KEY_VALUE_FULL_INFORMATION* kvfi = NULL; HANDLE h; options->compress = mount_compress; options->compress_force = mount_compress_force; options->compress_type = mount_compress_type > BTRFS_COMPRESSION_ZSTD ? 0 : mount_compress_type; options->readonly = mount_readonly; options->zlib_level = mount_zlib_level; options->zstd_level = mount_zstd_level; options->flush_interval = mount_flush_interval; options->max_inline = min(mount_max_inline, Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) - sizeof(EXTENT_DATA) + 1); options->skip_balance = mount_skip_balance; options->no_barrier = mount_no_barrier; options->no_trim = mount_no_trim; options->clear_cache = mount_clear_cache; options->allow_degraded = mount_allow_degraded; options->subvol_id = 0; options->no_root_dir = mount_no_root_dir; options->nodatacow = mount_nodatacow; path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR)); path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG); if (!path.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length); i = registry_path.Length / sizeof(WCHAR); path.Buffer[i] = '\\'; i++; for (j = 0; j < 16; j++) { path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4); path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF); i += 2; if (j == 3 || j == 5 || j == 7 || j == 9) { path.Buffer[i] = '-'; i++; } } kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) - sizeof(WCHAR) + (255 * sizeof(WCHAR)); kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwOpenKey(&h, KEY_QUERY_VALUE, &oa); if (Status == STATUS_OBJECT_NAME_NOT_FOUND) { Status = STATUS_SUCCESS; goto end; } else if (!NT_SUCCESS(Status)) { ERR("ZwOpenKey returned %08lx\n", Status); goto end; } index = 0; RtlInitUnicodeString(&ignoreus, L"Ignore"); RtlInitUnicodeString(&compressus, L"Compress"); RtlInitUnicodeString(&compressforceus, L"CompressForce"); RtlInitUnicodeString(&compresstypeus, L"CompressType"); RtlInitUnicodeString(&readonlyus, L"Readonly"); RtlInitUnicodeString(&zliblevelus, L"ZlibLevel"); RtlInitUnicodeString(&flushintervalus, L"FlushInterval"); RtlInitUnicodeString(&maxinlineus, L"MaxInline"); RtlInitUnicodeString(&subvolidus, L"SubvolId"); RtlInitUnicodeString(&skipbalanceus, L"SkipBalance"); RtlInitUnicodeString(&nobarrierus, L"NoBarrier"); RtlInitUnicodeString(¬rimus, L"NoTrim"); RtlInitUnicodeString(&clearcacheus, L"ClearCache"); RtlInitUnicodeString(&allowdegradedus, L"AllowDegraded"); RtlInitUnicodeString(&zstdlevelus, L"ZstdLevel"); RtlInitUnicodeString(&norootdirus, L"NoRootDir"); RtlInitUnicodeString(&nodatacowus, L"NoDataCOW"); do { Status = ZwEnumerateValueKey(h, index, KeyValueFullInformation, kvfi, kvfilen, &retlen); index++; if (NT_SUCCESS(Status)) { UNICODE_STRING us; us.Length = us.MaximumLength = (USHORT)kvfi->NameLength; us.Buffer = kvfi->Name; if (FsRtlAreNamesEqual(&ignoreus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->ignore = *val != 0 ? true : false; } else if (FsRtlAreNamesEqual(&compressus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->compress = *val != 0 ? true : false; } else if (FsRtlAreNamesEqual(&compressforceus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->compress_force = *val != 0 ? true : false; } else if (FsRtlAreNamesEqual(&compresstypeus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->compress_type = (uint8_t)(*val > BTRFS_COMPRESSION_ZSTD ? 0 : *val); } else if (FsRtlAreNamesEqual(&readonlyus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->readonly = *val != 0 ? true : false; } else if (FsRtlAreNamesEqual(&zliblevelus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->zlib_level = *val; } else if (FsRtlAreNamesEqual(&flushintervalus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->flush_interval = *val; } else if (FsRtlAreNamesEqual(&maxinlineus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->max_inline = min(*val, Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) - sizeof(EXTENT_DATA) + 1); } else if (FsRtlAreNamesEqual(&subvolidus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_QWORD) { uint64_t* val = (uint64_t*)((uint8_t*)kvfi + kvfi->DataOffset); options->subvol_id = *val; } else if (FsRtlAreNamesEqual(&skipbalanceus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->skip_balance = *val; } else if (FsRtlAreNamesEqual(&nobarrierus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->no_barrier = *val; } else if (FsRtlAreNamesEqual(¬rimus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->no_trim = *val; } else if (FsRtlAreNamesEqual(&clearcacheus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->clear_cache = *val; } else if (FsRtlAreNamesEqual(&allowdegradedus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->allow_degraded = *val; } else if (FsRtlAreNamesEqual(&zstdlevelus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->zstd_level = *val; } else if (FsRtlAreNamesEqual(&norootdirus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->no_root_dir = *val; } else if (FsRtlAreNamesEqual(&nodatacowus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->nodatacow = *val; } } else if (Status != STATUS_NO_MORE_ENTRIES) { ERR("ZwEnumerateValueKey returned %08lx\n", Status); goto end2; } } while (NT_SUCCESS(Status)); if (!options->compress && options->compress_force) options->compress = true; if (options->zlib_level > 9) options->zlib_level = 9; if (options->zstd_level > (uint32_t)ZSTD_maxCLevel()) options->zstd_level = ZSTD_maxCLevel(); if (options->flush_interval == 0) options->flush_interval = mount_flush_interval; Status = STATUS_SUCCESS; end2: ZwClose(h); end: ExFreePool(path.Buffer); if (kvfi) ExFreePool(kvfi); return Status; } NTSTATUS registry_mark_volume_mounted(BTRFS_UUID* uuid) { UNICODE_STRING path, mountedus; ULONG i, j; NTSTATUS Status; OBJECT_ATTRIBUTES oa; HANDLE h; DWORD data; path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR)); path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG); if (!path.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length); i = registry_path.Length / sizeof(WCHAR); path.Buffer[i] = '\\'; i++; for (j = 0; j < 16; j++) { path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4); path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF); i += 2; if (j == 3 || j == 5 || j == 7 || j == 9) { path.Buffer[i] = '-'; i++; } } InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateKey(&h, KEY_SET_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, NULL); if (!NT_SUCCESS(Status)) { ERR("ZwCreateKey returned %08lx\n", Status); goto end; } mountedus.Buffer = (WCHAR*)option_mounted; mountedus.Length = mountedus.MaximumLength = sizeof(option_mounted) - sizeof(WCHAR); data = 1; Status = ZwSetValueKey(h, &mountedus, 0, REG_DWORD, &data, sizeof(DWORD)); if (!NT_SUCCESS(Status)) { ERR("ZwSetValueKey returned %08lx\n", Status); goto end2; } Status = STATUS_SUCCESS; end2: ZwClose(h); end: ExFreePool(path.Buffer); return Status; } static NTSTATUS registry_mark_volume_unmounted_path(PUNICODE_STRING path) { HANDLE h; OBJECT_ATTRIBUTES oa; NTSTATUS Status; ULONG index, kvbilen = sizeof(KEY_VALUE_BASIC_INFORMATION) - sizeof(WCHAR) + (255 * sizeof(WCHAR)), retlen; KEY_VALUE_BASIC_INFORMATION* kvbi; bool has_options = false; UNICODE_STRING mountedus; // If a volume key has any options in it, we set Mounted to 0 and return. Otherwise, // we delete the whole thing. kvbi = ExAllocatePoolWithTag(PagedPool, kvbilen, ALLOC_TAG); if (!kvbi) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } InitializeObjectAttributes(&oa, path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwOpenKey(&h, KEY_QUERY_VALUE | KEY_SET_VALUE | DELETE, &oa); if (!NT_SUCCESS(Status)) { ERR("ZwOpenKey returned %08lx\n", Status); goto end; } index = 0; mountedus.Buffer = (WCHAR*)option_mounted; mountedus.Length = mountedus.MaximumLength = sizeof(option_mounted) - sizeof(WCHAR); do { Status = ZwEnumerateValueKey(h, index, KeyValueBasicInformation, kvbi, kvbilen, &retlen); index++; if (NT_SUCCESS(Status)) { UNICODE_STRING us; us.Length = us.MaximumLength = (USHORT)kvbi->NameLength; us.Buffer = kvbi->Name; if (!FsRtlAreNamesEqual(&mountedus, &us, true, NULL)) { has_options = true; break; } } else if (Status != STATUS_NO_MORE_ENTRIES) { ERR("ZwEnumerateValueKey returned %08lx\n", Status); goto end2; } } while (NT_SUCCESS(Status)); if (has_options) { DWORD data = 0; Status = ZwSetValueKey(h, &mountedus, 0, REG_DWORD, &data, sizeof(DWORD)); if (!NT_SUCCESS(Status)) { ERR("ZwSetValueKey returned %08lx\n", Status); goto end2; } } else { Status = ZwDeleteKey(h); if (!NT_SUCCESS(Status)) { ERR("ZwDeleteKey returned %08lx\n", Status); goto end2; } } Status = STATUS_SUCCESS; end2: ZwClose(h); end: ExFreePool(kvbi); return Status; } NTSTATUS registry_mark_volume_unmounted(BTRFS_UUID* uuid) { UNICODE_STRING path; NTSTATUS Status; ULONG i, j; path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR)); path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG); if (!path.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length); i = registry_path.Length / sizeof(WCHAR); path.Buffer[i] = '\\'; i++; for (j = 0; j < 16; j++) { path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4); path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF); i += 2; if (j == 3 || j == 5 || j == 7 || j == 9) { path.Buffer[i] = '-'; i++; } } Status = registry_mark_volume_unmounted_path(&path); if (!NT_SUCCESS(Status)) { ERR("registry_mark_volume_unmounted_path returned %08lx\n", Status); goto end; } Status = STATUS_SUCCESS; end: ExFreePool(path.Buffer); return Status; } #define is_hex(c) ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) static bool is_uuid(ULONG namelen, WCHAR* name) { ULONG i; if (namelen != 36 * sizeof(WCHAR)) return false; for (i = 0; i < 36; i++) { if (i == 8 || i == 13 || i == 18 || i == 23) { if (name[i] != '-') return false; } else if (!is_hex(name[i])) return false; } return true; } typedef struct { UNICODE_STRING name; LIST_ENTRY list_entry; } key_name; static void reset_subkeys(HANDLE h, PUNICODE_STRING reg_path) { NTSTATUS Status; KEY_BASIC_INFORMATION* kbi; ULONG kbilen = sizeof(KEY_BASIC_INFORMATION) - sizeof(WCHAR) + (255 * sizeof(WCHAR)), retlen, index = 0; LIST_ENTRY key_names, *le; InitializeListHead(&key_names); kbi = ExAllocatePoolWithTag(PagedPool, kbilen, ALLOC_TAG); if (!kbi) { ERR("out of memory\n"); return; } do { Status = ZwEnumerateKey(h, index, KeyBasicInformation, kbi, kbilen, &retlen); index++; if (NT_SUCCESS(Status)) { key_name* kn; TRACE("key: %.*S\n", (int)(kbi->NameLength / sizeof(WCHAR)), kbi->Name); if (is_uuid(kbi->NameLength, kbi->Name)) { kn = ExAllocatePoolWithTag(PagedPool, sizeof(key_name), ALLOC_TAG); if (!kn) { ERR("out of memory\n"); goto end; } kn->name.Length = kn->name.MaximumLength = (USHORT)min(0xffff, kbi->NameLength); kn->name.Buffer = ExAllocatePoolWithTag(PagedPool, kn->name.MaximumLength, ALLOC_TAG); if (!kn->name.Buffer) { ERR("out of memory\n"); ExFreePool(kn); goto end; } RtlCopyMemory(kn->name.Buffer, kbi->Name, kn->name.Length); InsertTailList(&key_names, &kn->list_entry); } } else if (Status != STATUS_NO_MORE_ENTRIES) ERR("ZwEnumerateKey returned %08lx\n", Status); } while (NT_SUCCESS(Status)); le = key_names.Flink; while (le != &key_names) { key_name* kn = CONTAINING_RECORD(le, key_name, list_entry); UNICODE_STRING path; path.Length = path.MaximumLength = reg_path->Length + sizeof(WCHAR) + kn->name.Length; path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG); if (!path.Buffer) { ERR("out of memory\n"); goto end; } RtlCopyMemory(path.Buffer, reg_path->Buffer, reg_path->Length); path.Buffer[reg_path->Length / sizeof(WCHAR)] = '\\'; RtlCopyMemory(&path.Buffer[(reg_path->Length / sizeof(WCHAR)) + 1], kn->name.Buffer, kn->name.Length); Status = registry_mark_volume_unmounted_path(&path); if (!NT_SUCCESS(Status)) WARN("registry_mark_volume_unmounted_path returned %08lx\n", Status); ExFreePool(path.Buffer); le = le->Flink; } end: while (!IsListEmpty(&key_names)) { key_name* kn; le = RemoveHeadList(&key_names); kn = CONTAINING_RECORD(le, key_name, list_entry); if (kn->name.Buffer) ExFreePool(kn->name.Buffer); ExFreePool(kn); } ExFreePool(kbi); } static void read_mappings(PUNICODE_STRING regpath) { WCHAR* path; UNICODE_STRING us; HANDLE h; OBJECT_ATTRIBUTES oa; ULONG dispos; NTSTATUS Status; static const WCHAR mappings[] = L"\\Mappings"; while (!IsListEmpty(&uid_map_list)) { uid_map* um = CONTAINING_RECORD(RemoveHeadList(&uid_map_list), uid_map, listentry); if (um->sid) ExFreePool(um->sid); ExFreePool(um); } path = ExAllocatePoolWithTag(PagedPool, regpath->Length + sizeof(mappings) - sizeof(WCHAR), ALLOC_TAG); if (!path) { ERR("out of memory\n"); return; } RtlCopyMemory(path, regpath->Buffer, regpath->Length); RtlCopyMemory((uint8_t*)path + regpath->Length, mappings, sizeof(mappings) - sizeof(WCHAR)); us.Buffer = path; us.Length = us.MaximumLength = regpath->Length + sizeof(mappings) - sizeof(WCHAR); InitializeObjectAttributes(&oa, &us, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateKey(&h, KEY_QUERY_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos); if (!NT_SUCCESS(Status)) { ERR("ZwCreateKey returned %08lx\n", Status); ExFreePool(path); return; } if (dispos == REG_OPENED_EXISTING_KEY) { KEY_VALUE_FULL_INFORMATION* kvfi; ULONG kvfilen, retlen, i; kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) + 256; kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); ExFreePool(path); ZwClose(h); return; } i = 0; do { Status = ZwEnumerateValueKey(h, i, KeyValueFullInformation, kvfi, kvfilen, &retlen); if (NT_SUCCESS(Status) && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { uint32_t val = 0; RtlCopyMemory(&val, (uint8_t*)kvfi + kvfi->DataOffset, min(kvfi->DataLength, sizeof(uint32_t))); TRACE("entry %lu = %.*S = %u\n", i, (int)(kvfi->NameLength / sizeof(WCHAR)), kvfi->Name, val); add_user_mapping(kvfi->Name, kvfi->NameLength / sizeof(WCHAR), val); } i = i + 1; } while (Status != STATUS_NO_MORE_ENTRIES); ExFreePool(kvfi); } ZwClose(h); ExFreePool(path); } static void read_group_mappings(PUNICODE_STRING regpath) { WCHAR* path; UNICODE_STRING us; HANDLE h; OBJECT_ATTRIBUTES oa; ULONG dispos; NTSTATUS Status; static const WCHAR mappings[] = L"\\GroupMappings"; while (!IsListEmpty(&gid_map_list)) { gid_map* gm = CONTAINING_RECORD(RemoveHeadList(&gid_map_list), gid_map, listentry); if (gm->sid) ExFreePool(gm->sid); ExFreePool(gm); } path = ExAllocatePoolWithTag(PagedPool, regpath->Length + sizeof(mappings) - sizeof(WCHAR), ALLOC_TAG); if (!path) { ERR("out of memory\n"); return; } RtlCopyMemory(path, regpath->Buffer, regpath->Length); RtlCopyMemory((uint8_t*)path + regpath->Length, mappings, sizeof(mappings) - sizeof(WCHAR)); us.Buffer = path; us.Length = us.MaximumLength = regpath->Length + sizeof(mappings) - sizeof(WCHAR); InitializeObjectAttributes(&oa, &us, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateKey(&h, KEY_QUERY_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos); if (!NT_SUCCESS(Status)) { ERR("ZwCreateKey returned %08lx\n", Status); ExFreePool(path); return; } ExFreePool(path); if (dispos == REG_OPENED_EXISTING_KEY) { KEY_VALUE_FULL_INFORMATION* kvfi; ULONG kvfilen, retlen, i; kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) + 256; kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); ZwClose(h); return; } i = 0; do { Status = ZwEnumerateValueKey(h, i, KeyValueFullInformation, kvfi, kvfilen, &retlen); if (NT_SUCCESS(Status) && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { uint32_t val = 0; RtlCopyMemory(&val, (uint8_t*)kvfi + kvfi->DataOffset, min(kvfi->DataLength, sizeof(uint32_t))); TRACE("entry %lu = %.*S = %u\n", i, (int)(kvfi->NameLength / sizeof(WCHAR)), kvfi->Name, val); add_group_mapping(kvfi->Name, kvfi->NameLength / sizeof(WCHAR), val); } i = i + 1; } while (Status != STATUS_NO_MORE_ENTRIES); ExFreePool(kvfi); } else if (dispos == REG_CREATED_NEW_KEY) { static const WCHAR builtin_users[] = L"S-1-5-32-545"; UNICODE_STRING us2; DWORD val; // If we're creating the key for the first time, we add a default mapping of // BUILTIN\Users to gid 100, which ought to correspond to the "users" group on Linux. us2.Length = us2.MaximumLength = sizeof(builtin_users) - sizeof(WCHAR); us2.Buffer = ExAllocatePoolWithTag(PagedPool, us2.MaximumLength, ALLOC_TAG); if (us2.Buffer) { RtlCopyMemory(us2.Buffer, builtin_users, us2.Length); val = 100; Status = ZwSetValueKey(h, &us2, 0, REG_DWORD, &val, sizeof(DWORD)); if (!NT_SUCCESS(Status)) { ERR("ZwSetValueKey returned %08lx\n", Status); ZwClose(h); return; } add_group_mapping(us2.Buffer, us2.Length / sizeof(WCHAR), val); ExFreePool(us2.Buffer); } } ZwClose(h); } static void get_registry_value(HANDLE h, WCHAR* string, ULONG type, void* val, ULONG size) { ULONG kvfilen; KEY_VALUE_FULL_INFORMATION* kvfi; UNICODE_STRING us; NTSTATUS Status; RtlInitUnicodeString(&us, string); kvfi = NULL; kvfilen = 0; Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen); if ((Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_BUFFER_OVERFLOW) && kvfilen > 0) { kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); ZwClose(h); return; } Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen); if (NT_SUCCESS(Status)) { if (kvfi->Type == type && kvfi->DataLength >= size) { RtlCopyMemory(val, ((uint8_t*)kvfi) + kvfi->DataOffset, size); } else { Status = ZwDeleteValueKey(h, &us); if (!NT_SUCCESS(Status)) { ERR("ZwDeleteValueKey returned %08lx\n", Status); } Status = ZwSetValueKey(h, &us, 0, type, val, size); if (!NT_SUCCESS(Status)) { ERR("ZwSetValueKey returned %08lx\n", Status); } } } ExFreePool(kvfi); } else if (Status == STATUS_OBJECT_NAME_NOT_FOUND) { Status = ZwSetValueKey(h, &us, 0, type, val, size); if (!NT_SUCCESS(Status)) { ERR("ZwSetValueKey returned %08lx\n", Status); } } else { ERR("ZwQueryValueKey returned %08lx\n", Status); } } void read_registry(PUNICODE_STRING regpath, bool refresh) { OBJECT_ATTRIBUTES oa; NTSTATUS Status; HANDLE h; ULONG dispos; #ifdef _DEBUG KEY_VALUE_FULL_INFORMATION* kvfi; ULONG kvfilen, old_debug_log_level = debug_log_level; UNICODE_STRING us, old_log_file, old_log_device; static const WCHAR def_log_file[] = L"\\??\\C:\\btrfs.log"; #endif ExAcquireResourceExclusiveLite(&mapping_lock, true); read_mappings(regpath); read_group_mappings(regpath); ExReleaseResourceLite(&mapping_lock); InitializeObjectAttributes(&oa, regpath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateKey(&h, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos); if (!NT_SUCCESS(Status)) { ERR("ZwCreateKey returned %08lx\n", Status); return; } if (!refresh) reset_subkeys(h, regpath); get_registry_value(h, L"Compress", REG_DWORD, &mount_compress, sizeof(mount_compress)); get_registry_value(h, L"CompressForce", REG_DWORD, &mount_compress_force, sizeof(mount_compress_force)); get_registry_value(h, L"CompressType", REG_DWORD, &mount_compress_type, sizeof(mount_compress_type)); get_registry_value(h, L"ZlibLevel", REG_DWORD, &mount_zlib_level, sizeof(mount_zlib_level)); get_registry_value(h, L"FlushInterval", REG_DWORD, &mount_flush_interval, sizeof(mount_flush_interval)); get_registry_value(h, L"MaxInline", REG_DWORD, &mount_max_inline, sizeof(mount_max_inline)); get_registry_value(h, L"SkipBalance", REG_DWORD, &mount_skip_balance, sizeof(mount_skip_balance)); get_registry_value(h, L"NoBarrier", REG_DWORD, &mount_no_barrier, sizeof(mount_no_barrier)); get_registry_value(h, L"NoTrim", REG_DWORD, &mount_no_trim, sizeof(mount_no_trim)); get_registry_value(h, L"ClearCache", REG_DWORD, &mount_clear_cache, sizeof(mount_clear_cache)); get_registry_value(h, L"AllowDegraded", REG_DWORD, &mount_allow_degraded, sizeof(mount_allow_degraded)); get_registry_value(h, L"Readonly", REG_DWORD, &mount_readonly, sizeof(mount_readonly)); get_registry_value(h, L"ZstdLevel", REG_DWORD, &mount_zstd_level, sizeof(mount_zstd_level)); get_registry_value(h, L"NoRootDir", REG_DWORD, &mount_no_root_dir, sizeof(mount_no_root_dir)); get_registry_value(h, L"NoDataCOW", REG_DWORD, &mount_nodatacow, sizeof(mount_nodatacow)); if (!refresh) get_registry_value(h, L"NoPNP", REG_DWORD, &no_pnp, sizeof(no_pnp)); if (mount_flush_interval == 0) mount_flush_interval = 1; #ifdef _DEBUG get_registry_value(h, L"DebugLogLevel", REG_DWORD, &debug_log_level, sizeof(debug_log_level)); RtlInitUnicodeString(&us, L"LogDevice"); kvfi = NULL; kvfilen = 0; Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen); old_log_device = log_device; log_device.Length = log_device.MaximumLength = 0; log_device.Buffer = NULL; if ((Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_BUFFER_OVERFLOW) && kvfilen > 0) { kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); ZwClose(h); return; } Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen); if (NT_SUCCESS(Status)) { if ((kvfi->Type == REG_SZ || kvfi->Type == REG_EXPAND_SZ) && kvfi->DataLength >= sizeof(WCHAR)) { log_device.Length = log_device.MaximumLength = (USHORT)min(0xffff, kvfi->DataLength); log_device.Buffer = ExAllocatePoolWithTag(PagedPool, log_device.MaximumLength, ALLOC_TAG); if (!log_device.Buffer) { ERR("out of memory\n"); ExFreePool(kvfi); ZwClose(h); return; } RtlCopyMemory(log_device.Buffer, ((uint8_t*)kvfi) + kvfi->DataOffset, log_device.Length); if (log_device.Buffer[(log_device.Length / sizeof(WCHAR)) - 1] == 0) log_device.Length -= sizeof(WCHAR); } else { ERR("LogDevice was type %lu, length %lu\n", kvfi->Type, kvfi->DataLength); Status = ZwDeleteValueKey(h, &us); if (!NT_SUCCESS(Status)) { ERR("ZwDeleteValueKey returned %08lx\n", Status); } } } ExFreePool(kvfi); } else if (Status != STATUS_OBJECT_NAME_NOT_FOUND) { ERR("ZwQueryValueKey returned %08lx\n", Status); } ExAcquireResourceExclusiveLite(&log_lock, true); if (refresh && (log_device.Length != old_log_device.Length || RtlCompareMemory(log_device.Buffer, old_log_device.Buffer, log_device.Length) != log_device.Length || (!comfo && log_device.Length > 0) || (old_debug_log_level == 0 && debug_log_level > 0) || (old_debug_log_level > 0 && debug_log_level == 0))) { if (comfo) ObDereferenceObject(comfo); if (log_handle) { ZwClose(log_handle); log_handle = NULL; } comfo = NULL; comdo = NULL; if (log_device.Length > 0 && debug_log_level > 0) { Status = IoGetDeviceObjectPointer(&log_device, FILE_WRITE_DATA, &comfo, &comdo); if (!NT_SUCCESS(Status)) DbgPrint("IoGetDeviceObjectPointer returned %08lx\n", Status); } } ExReleaseResourceLite(&log_lock); if (old_log_device.Buffer) ExFreePool(old_log_device.Buffer); RtlInitUnicodeString(&us, L"LogFile"); kvfi = NULL; kvfilen = 0; Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen); old_log_file = log_file; if ((Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_BUFFER_OVERFLOW) && kvfilen > 0) { kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); ZwClose(h); return; } Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen); if (NT_SUCCESS(Status)) { if ((kvfi->Type == REG_SZ || kvfi->Type == REG_EXPAND_SZ) && kvfi->DataLength >= sizeof(WCHAR)) { log_file.Length = log_file.MaximumLength = (USHORT)min(0xffff, kvfi->DataLength); log_file.Buffer = ExAllocatePoolWithTag(PagedPool, log_file.MaximumLength, ALLOC_TAG); if (!log_file.Buffer) { ERR("out of memory\n"); ExFreePool(kvfi); ZwClose(h); return; } RtlCopyMemory(log_file.Buffer, ((uint8_t*)kvfi) + kvfi->DataOffset, log_file.Length); if (log_file.Buffer[(log_file.Length / sizeof(WCHAR)) - 1] == 0) log_file.Length -= sizeof(WCHAR); } else { ERR("LogFile was type %lu, length %lu\n", kvfi->Type, kvfi->DataLength); Status = ZwDeleteValueKey(h, &us); if (!NT_SUCCESS(Status)) ERR("ZwDeleteValueKey returned %08lx\n", Status); log_file.Length = 0; } } else { ERR("ZwQueryValueKey returned %08lx\n", Status); log_file.Length = 0; } ExFreePool(kvfi); } else if (Status == STATUS_OBJECT_NAME_NOT_FOUND) { Status = ZwSetValueKey(h, &us, 0, REG_SZ, (void*)def_log_file, sizeof(def_log_file)); if (!NT_SUCCESS(Status)) ERR("ZwSetValueKey returned %08lx\n", Status); log_file.Length = 0; } else { ERR("ZwQueryValueKey returned %08lx\n", Status); log_file.Length = 0; } if (log_file.Length == 0) { log_file.Length = log_file.MaximumLength = sizeof(def_log_file) - sizeof(WCHAR); log_file.Buffer = ExAllocatePoolWithTag(PagedPool, log_file.MaximumLength, ALLOC_TAG); if (!log_file.Buffer) { ERR("out of memory\n"); ZwClose(h); return; } RtlCopyMemory(log_file.Buffer, def_log_file, log_file.Length); } ExAcquireResourceExclusiveLite(&log_lock, true); if (refresh && (log_file.Length != old_log_file.Length || RtlCompareMemory(log_file.Buffer, old_log_file.Buffer, log_file.Length) != log_file.Length || (!log_handle && log_file.Length > 0) || (old_debug_log_level == 0 && debug_log_level > 0) || (old_debug_log_level > 0 && debug_log_level == 0))) { if (log_handle) { ZwClose(log_handle); log_handle = NULL; } if (!comfo && log_file.Length > 0 && refresh && debug_log_level > 0) { IO_STATUS_BLOCK iosb; InitializeObjectAttributes(&oa, &log_file, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateFile(&log_handle, FILE_WRITE_DATA, &oa, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_WRITE_THROUGH | FILE_SYNCHRONOUS_IO_ALERT, NULL, 0); if (!NT_SUCCESS(Status)) { DbgPrint("ZwCreateFile returned %08lx\n", Status); log_handle = NULL; } } } ExReleaseResourceLite(&log_lock); if (old_log_file.Buffer) ExFreePool(old_log_file.Buffer); #endif ZwClose(h); } _Function_class_(WORKER_THREAD_ROUTINE) static void __stdcall registry_work_item(PVOID Parameter) { NTSTATUS Status; HANDLE regh = (HANDLE)Parameter; IO_STATUS_BLOCK iosb; TRACE("registry changed\n"); read_registry(®istry_path, true); Status = ZwNotifyChangeKey(regh, NULL, (PVOID)&wqi, (PVOID)DelayedWorkQueue, &iosb, REG_NOTIFY_CHANGE_LAST_SET, true, NULL, 0, true); if (!NT_SUCCESS(Status)) ERR("ZwNotifyChangeKey returned %08lx\n", Status); } void watch_registry(HANDLE regh) { NTSTATUS Status; IO_STATUS_BLOCK iosb; ExInitializeWorkItem(&wqi, registry_work_item, regh); Status = ZwNotifyChangeKey(regh, NULL, (PVOID)&wqi, (PVOID)DelayedWorkQueue, &iosb, REG_NOTIFY_CHANGE_LAST_SET, true, NULL, 0, true); if (!NT_SUCCESS(Status)) ERR("ZwNotifyChangeKey returned %08lx\n", Status); } ================================================ FILE: src/reparse.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" extern tFsRtlValidateReparsePointBuffer fFsRtlValidateReparsePointBuffer; typedef struct { uint32_t unknown; char name[1]; } REPARSE_DATA_BUFFER_LX_SYMLINK; NTSTATUS get_reparse_point(PFILE_OBJECT FileObject, void* buffer, DWORD buflen, ULONG_PTR* retlen) { USHORT subnamelen, printnamelen, i; ULONG stringlen; DWORD reqlen; REPARSE_DATA_BUFFER* rdb = buffer; fcb* fcb = FileObject->FsContext; ccb* ccb = FileObject->FsContext2; NTSTATUS Status; TRACE("(%p, %p, %lx, %p)\n", FileObject, buffer, buflen, retlen); if (!ccb) return STATUS_INVALID_PARAMETER; ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true); ExAcquireResourceSharedLite(fcb->Header.Resource, true); if (fcb->type == BTRFS_TYPE_SYMLINK) { if (ccb->lxss) { reqlen = offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + sizeof(uint32_t); if (buflen < reqlen) { Status = STATUS_BUFFER_OVERFLOW; goto end; } rdb->ReparseTag = IO_REPARSE_TAG_LX_SYMLINK; rdb->ReparseDataLength = offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + sizeof(uint32_t); rdb->Reserved = 0; *((uint32_t*)rdb->GenericReparseBuffer.DataBuffer) = 1; *retlen = reqlen; } else { char* data; if (fcb->inode_item.st_size == 0 || fcb->inode_item.st_size > 0xffff) { Status = STATUS_INVALID_PARAMETER; goto end; } data = ExAllocatePoolWithTag(PagedPool, (ULONG)fcb->inode_item.st_size, ALLOC_TAG); if (!data) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } TRACE("data = %p, size = %I64x\n", data, fcb->inode_item.st_size); Status = read_file(fcb, (uint8_t*)data, 0, fcb->inode_item.st_size, NULL, NULL); if (!NT_SUCCESS(Status)) { ERR("read_file returned %08lx\n", Status); ExFreePool(data); goto end; } Status = utf8_to_utf16(NULL, 0, &stringlen, data, (ULONG)fcb->inode_item.st_size); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 1 returned %08lx\n", Status); ExFreePool(data); goto end; } subnamelen = (uint16_t)stringlen; printnamelen = (uint16_t)stringlen; reqlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + subnamelen + printnamelen; if (buflen >= offsetof(REPARSE_DATA_BUFFER, ReparseDataLength)) rdb->ReparseTag = IO_REPARSE_TAG_SYMLINK; if (buflen >= offsetof(REPARSE_DATA_BUFFER, Reserved)) rdb->ReparseDataLength = (USHORT)(reqlen - offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer)); if (buflen >= offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.SubstituteNameOffset)) rdb->Reserved = 0; if (buflen < reqlen) { ExFreePool(data); Status = STATUS_BUFFER_OVERFLOW; *retlen = min(buflen, offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.SubstituteNameOffset)); goto end; } rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0; rdb->SymbolicLinkReparseBuffer.SubstituteNameLength = subnamelen; rdb->SymbolicLinkReparseBuffer.PrintNameOffset = subnamelen; rdb->SymbolicLinkReparseBuffer.PrintNameLength = printnamelen; rdb->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE; Status = utf8_to_utf16(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], stringlen, &stringlen, data, (ULONG)fcb->inode_item.st_size); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 2 returned %08lx\n", Status); ExFreePool(data); goto end; } for (i = 0; i < stringlen / sizeof(WCHAR); i++) { if (rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] == '/') rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] = '\\'; } RtlCopyMemory(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)], &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], rdb->SymbolicLinkReparseBuffer.SubstituteNameLength); *retlen = reqlen; ExFreePool(data); } Status = STATUS_SUCCESS; } else if (fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) { if (fcb->type == BTRFS_TYPE_FILE) { ULONG len; Status = read_file(fcb, buffer, 0, buflen, &len, NULL); if (!NT_SUCCESS(Status)) { ERR("read_file returned %08lx\n", Status); } *retlen = len; } else if (fcb->type == BTRFS_TYPE_DIRECTORY) { if (!fcb->reparse_xattr.Buffer || fcb->reparse_xattr.Length < sizeof(ULONG)) { Status = STATUS_NOT_A_REPARSE_POINT; goto end; } if (buflen > 0) { *retlen = min(buflen, fcb->reparse_xattr.Length); RtlCopyMemory(buffer, fcb->reparse_xattr.Buffer, *retlen); } else *retlen = 0; Status = *retlen == fcb->reparse_xattr.Length ? STATUS_SUCCESS : STATUS_BUFFER_OVERFLOW; } else Status = STATUS_NOT_A_REPARSE_POINT; } else { Status = STATUS_NOT_A_REPARSE_POINT; } end: ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&fcb->Vcb->tree_lock); return Status; } static NTSTATUS set_symlink(PIRP Irp, file_ref* fileref, fcb* fcb, ccb* ccb, REPARSE_DATA_BUFFER* rdb, ULONG buflen, LIST_ENTRY* rollback) { NTSTATUS Status; ULONG tlength; ANSI_STRING target; bool target_alloc = false; LARGE_INTEGER offset, time; BTRFS_TIME now; if (rdb->ReparseTag == IO_REPARSE_TAG_SYMLINK) { UNICODE_STRING subname; ULONG minlen, len; minlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + sizeof(WCHAR); if (buflen < minlen) { WARN("buffer was less than minimum length (%lu < %lu)\n", buflen, minlen); return STATUS_INVALID_PARAMETER; } if (rdb->SymbolicLinkReparseBuffer.SubstituteNameLength < sizeof(WCHAR)) { WARN("rdb->SymbolicLinkReparseBuffer.SubstituteNameLength was too short\n"); return STATUS_INVALID_PARAMETER; } subname.Buffer = &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)]; subname.MaximumLength = subname.Length = rdb->SymbolicLinkReparseBuffer.SubstituteNameLength; TRACE("substitute name = %.*S\n", (int)(subname.Length / sizeof(WCHAR)), subname.Buffer); Status = utf16_to_utf8(NULL, 0, &len, subname.Buffer, subname.Length); if (!NT_SUCCESS(Status)) { ERR("utf16_to_utf8 1 failed with error %08lx\n", Status); return Status; } target.MaximumLength = target.Length = (USHORT)len; target.Buffer = ExAllocatePoolWithTag(PagedPool, target.MaximumLength, ALLOC_TAG); if (!target.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } target_alloc = true; Status = utf16_to_utf8(target.Buffer, target.Length, &len, subname.Buffer, subname.Length); if (!NT_SUCCESS(Status)) { ERR("utf16_to_utf8 2 failed with error %08lx\n", Status); ExFreePool(target.Buffer); return Status; } for (USHORT i = 0; i < target.Length; i++) { if (target.Buffer[i] == '\\') target.Buffer[i] = '/'; } } else if (rdb->ReparseTag == IO_REPARSE_TAG_LX_SYMLINK) { REPARSE_DATA_BUFFER_LX_SYMLINK* buf; if (buflen < offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + rdb->ReparseDataLength) { WARN("buffer was less than expected length (%lu < %lu)\n", buflen, (unsigned long)(offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + rdb->ReparseDataLength)); return STATUS_INVALID_PARAMETER; } buf = (REPARSE_DATA_BUFFER_LX_SYMLINK*)rdb->GenericReparseBuffer.DataBuffer; if (buflen < offsetof(REPARSE_DATA_BUFFER_LX_SYMLINK, name)) { WARN("buffer was less than minimum length (%u < %lu)\n", rdb->ReparseDataLength, (unsigned long)(offsetof(REPARSE_DATA_BUFFER_LX_SYMLINK, name))); return STATUS_INVALID_PARAMETER; } target.Buffer = buf->name; target.Length = target.MaximumLength = rdb->ReparseDataLength - offsetof(REPARSE_DATA_BUFFER_LX_SYMLINK, name); } else { ERR("unexpected reparse tag %08lx\n", rdb->ReparseTag); return STATUS_INTERNAL_ERROR; } fcb->type = BTRFS_TYPE_SYMLINK; fcb->inode_item.st_mode &= ~__S_IFMT; fcb->inode_item.st_mode |= __S_IFLNK; fcb->inode_item.generation = fcb->Vcb->superblock.generation; // so we don't confuse btrfs send on Linux if (fileref && fileref->dc) fileref->dc->type = fcb->type; Status = truncate_file(fcb, 0, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("truncate_file returned %08lx\n", Status); if (target_alloc) ExFreePool(target.Buffer); return Status; } offset.QuadPart = 0; tlength = target.Length; Status = write_file2(fcb->Vcb, Irp, offset, target.Buffer, &tlength, false, true, true, false, false, rollback); if (target_alloc) ExFreePool(target.Buffer); KeQuerySystemTime(&time); win_time_to_unix(time, &now); fcb->inode_item.transid = fcb->Vcb->superblock.generation; fcb->inode_item.sequence++; if (!ccb || !ccb->user_set_change_time) fcb->inode_item.st_ctime = now; if (!ccb || !ccb->user_set_write_time) fcb->inode_item.st_mtime = now; fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation; fcb->subvol->root_item.ctime = now; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); if (fileref) mark_fileref_dirty(fileref); return Status; } NTSTATUS set_reparse_point2(fcb* fcb, REPARSE_DATA_BUFFER* rdb, ULONG buflen, ccb* ccb, file_ref* fileref, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; ULONG tag; if (fcb->type == BTRFS_TYPE_SYMLINK) { WARN("tried to set a reparse point on an existing symlink\n"); return STATUS_INVALID_PARAMETER; } // FIXME - fail if we already have the attribute FILE_ATTRIBUTE_REPARSE_POINT // FIXME - die if not file or directory if (fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0) { TRACE("directory not empty\n"); return STATUS_DIRECTORY_NOT_EMPTY; } if (buflen < sizeof(ULONG)) { WARN("buffer was not long enough to hold tag\n"); return STATUS_INVALID_BUFFER_SIZE; } Status = fFsRtlValidateReparsePointBuffer(buflen, rdb); if (!NT_SUCCESS(Status)) { ERR("FsRtlValidateReparsePointBuffer returned %08lx\n", Status); return Status; } tag = *(ULONG*)rdb; if (tag == IO_REPARSE_TAG_MOUNT_POINT && fcb->type != BTRFS_TYPE_DIRECTORY) return STATUS_NOT_A_DIRECTORY; if (fcb->type == BTRFS_TYPE_FILE && ((tag == IO_REPARSE_TAG_SYMLINK && rdb->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) || tag == IO_REPARSE_TAG_LX_SYMLINK)) { Status = set_symlink(Irp, fileref, fcb, ccb, rdb, buflen, rollback); fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT; } else { LARGE_INTEGER offset, time; BTRFS_TIME now; if (fcb->type == BTRFS_TYPE_DIRECTORY || fcb->type == BTRFS_TYPE_CHARDEV || fcb->type == BTRFS_TYPE_BLOCKDEV) { // store as xattr ANSI_STRING buf; buf.Buffer = ExAllocatePoolWithTag(PagedPool, buflen, ALLOC_TAG); if (!buf.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } buf.Length = buf.MaximumLength = (uint16_t)buflen; if (fcb->reparse_xattr.Buffer) ExFreePool(fcb->reparse_xattr.Buffer); fcb->reparse_xattr = buf; RtlCopyMemory(buf.Buffer, rdb, buflen); fcb->reparse_xattr_changed = true; Status = STATUS_SUCCESS; } else { // otherwise, store as file data Status = truncate_file(fcb, 0, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("truncate_file returned %08lx\n", Status); return Status; } offset.QuadPart = 0; Status = write_file2(fcb->Vcb, Irp, offset, rdb, &buflen, false, true, true, false, false, rollback); if (!NT_SUCCESS(Status)) { ERR("write_file2 returned %08lx\n", Status); return Status; } } KeQuerySystemTime(&time); win_time_to_unix(time, &now); fcb->inode_item.transid = fcb->Vcb->superblock.generation; fcb->inode_item.sequence++; if (!ccb || !ccb->user_set_change_time) fcb->inode_item.st_ctime = now; if (!ccb || !ccb->user_set_write_time) fcb->inode_item.st_mtime = now; fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT; fcb->atts_changed = true; fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation; fcb->subvol->root_item.ctime = now; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); } return STATUS_SUCCESS; } NTSTATUS set_reparse_point(PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; void* buffer = Irp->AssociatedIrp.SystemBuffer; REPARSE_DATA_BUFFER* rdb = buffer; DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength; NTSTATUS Status = STATUS_SUCCESS; fcb* fcb; ccb* ccb; file_ref* fileref; LIST_ENTRY rollback; TRACE("(%p)\n", Irp); InitializeListHead(&rollback); if (!FileObject) { ERR("FileObject was NULL\n"); return STATUS_INVALID_PARAMETER; } // IFSTest insists on this, for some reason... if (Irp->UserBuffer) return STATUS_INVALID_PARAMETER; fcb = FileObject->FsContext; ccb = FileObject->FsContext2; if (!ccb) { ERR("ccb was NULL\n"); return STATUS_INVALID_PARAMETER; } if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA))) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } fileref = ccb->fileref; if (!fileref) { ERR("fileref was NULL\n"); return STATUS_INVALID_PARAMETER; } if (fcb->ads) { fileref = fileref->parent; fcb = fileref->fcb; } ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); Status = set_reparse_point2(fcb, rdb, buflen, ccb, fileref, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("set_reparse_point2 returned %08lx\n", Status); goto end; } queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL); end: if (NT_SUCCESS(Status)) clear_rollback(&rollback); else do_rollback(fcb->Vcb, &rollback); ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&fcb->Vcb->tree_lock); return Status; } NTSTATUS delete_reparse_point(PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; REPARSE_DATA_BUFFER* rdb = Irp->AssociatedIrp.SystemBuffer; DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength; NTSTATUS Status; fcb* fcb; ccb* ccb; file_ref* fileref; LIST_ENTRY rollback; TRACE("(%p)\n", Irp); InitializeListHead(&rollback); if (!FileObject) { ERR("FileObject was NULL\n"); return STATUS_INVALID_PARAMETER; } fcb = FileObject->FsContext; if (!fcb) { ERR("fcb was NULL\n"); return STATUS_INVALID_PARAMETER; } ccb = FileObject->FsContext2; if (!ccb) { ERR("ccb was NULL\n"); return STATUS_INVALID_PARAMETER; } if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) { WARN("insufficient privileges\n"); return STATUS_ACCESS_DENIED; } fileref = ccb->fileref; if (!fileref) { ERR("fileref was NULL\n"); return STATUS_INVALID_PARAMETER; } ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true); ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (buflen < offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer)) { ERR("buffer was too short\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (rdb->ReparseDataLength > 0) { WARN("rdb->ReparseDataLength was not zero\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (fcb->ads) { WARN("tried to delete reparse point on ADS\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (fcb->type == BTRFS_TYPE_SYMLINK) { LARGE_INTEGER time; BTRFS_TIME now; if (rdb->ReparseTag != IO_REPARSE_TAG_SYMLINK) { WARN("reparse tag was not IO_REPARSE_TAG_SYMLINK\n"); Status = STATUS_INVALID_PARAMETER; goto end; } KeQuerySystemTime(&time); win_time_to_unix(time, &now); fileref->fcb->type = BTRFS_TYPE_FILE; fileref->fcb->inode_item.st_mode &= ~__S_IFLNK; fileref->fcb->inode_item.st_mode |= __S_IFREG; fileref->fcb->inode_item.generation = fileref->fcb->Vcb->superblock.generation; // so we don't confuse btrfs send on Linux fileref->fcb->inode_item.transid = fileref->fcb->Vcb->superblock.generation; fileref->fcb->inode_item.sequence++; if (!ccb->user_set_change_time) fileref->fcb->inode_item.st_ctime = now; if (!ccb->user_set_write_time) fileref->fcb->inode_item.st_mtime = now; fileref->fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT; if (fileref->dc) fileref->dc->type = fileref->fcb->type; mark_fileref_dirty(fileref); fileref->fcb->inode_item_changed = true; mark_fcb_dirty(fileref->fcb); fileref->fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation; fileref->fcb->subvol->root_item.ctime = now; } else if (fcb->type == BTRFS_TYPE_FILE) { LARGE_INTEGER time; BTRFS_TIME now; // FIXME - do we need to check that the reparse tags match? Status = truncate_file(fcb, 0, Irp, &rollback); if (!NT_SUCCESS(Status)) { ERR("truncate_file returned %08lx\n", Status); goto end; } fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT; fcb->atts_changed = true; KeQuerySystemTime(&time); win_time_to_unix(time, &now); fcb->inode_item.transid = fcb->Vcb->superblock.generation; fcb->inode_item.sequence++; if (!ccb->user_set_change_time) fcb->inode_item.st_ctime = now; if (!ccb->user_set_write_time) fcb->inode_item.st_mtime = now; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation; fcb->subvol->root_item.ctime = now; } else if (fcb->type == BTRFS_TYPE_DIRECTORY) { LARGE_INTEGER time; BTRFS_TIME now; // FIXME - do we need to check that the reparse tags match? fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT; fcb->atts_changed = true; if (fcb->reparse_xattr.Buffer) { ExFreePool(fcb->reparse_xattr.Buffer); fcb->reparse_xattr.Buffer = NULL; } fcb->reparse_xattr_changed = true; KeQuerySystemTime(&time); win_time_to_unix(time, &now); fcb->inode_item.transid = fcb->Vcb->superblock.generation; fcb->inode_item.sequence++; if (!ccb->user_set_change_time) fcb->inode_item.st_ctime = now; if (!ccb->user_set_write_time) fcb->inode_item.st_mtime = now; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation; fcb->subvol->root_item.ctime = now; } else { ERR("unsupported file type %u\n", fcb->type); Status = STATUS_INVALID_PARAMETER; goto end; } Status = STATUS_SUCCESS; queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL); end: if (NT_SUCCESS(Status)) clear_rollback(&rollback); else do_rollback(fcb->Vcb, &rollback); ExReleaseResourceLite(fcb->Header.Resource); ExReleaseResourceLite(&fcb->Vcb->tree_lock); return Status; } ================================================ FILE: src/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by btrfs.rc // // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 101 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: src/scrub.c ================================================ /* Copyright (c) Mark Harmstone 2017 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #define SCRUB_UNIT 0x100000 // 1 MB struct _scrub_context; typedef struct { struct _scrub_context* context; PIRP Irp; uint64_t start; uint32_t length; IO_STATUS_BLOCK iosb; uint8_t* buf; bool csum_error; void* bad_csums; } scrub_context_stripe; typedef struct _scrub_context { KEVENT Event; scrub_context_stripe* stripes; LONG stripes_left; } scrub_context; typedef struct { ANSI_STRING name; bool orig_subvol; LIST_ENTRY list_entry; } path_part; static void log_file_checksum_error(device_extension* Vcb, uint64_t addr, uint64_t devid, uint64_t subvol, uint64_t inode, uint64_t offset) { LIST_ENTRY *le, parts; root* r = NULL; KEY searchkey; traverse_ptr tp; uint64_t dir; bool orig_subvol = true, not_in_tree = false; ANSI_STRING fn; scrub_error* err; NTSTATUS Status; ULONG utf16len; le = Vcb->roots.Flink; while (le != &Vcb->roots) { root* r2 = CONTAINING_RECORD(le, root, list_entry); if (r2->id == subvol) { r = r2; break; } le = le->Flink; } if (!r) { ERR("could not find subvol %I64x\n", subvol); return; } InitializeListHead(&parts); dir = inode; while (true) { if (dir == r->root_item.objid) { if (r == Vcb->root_fileref->fcb->subvol) break; searchkey.obj_id = r->id; searchkey.obj_type = TYPE_ROOT_BACKREF; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); goto end; } if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { ROOT_REF* rr = (ROOT_REF*)tp.item->data; path_part* pp; if (tp.item->size < sizeof(ROOT_REF)) { 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)); goto end; } if (tp.item->size < offsetof(ROOT_REF, name[0]) + rr->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_REF, name[0]) + rr->n); goto end; } pp = ExAllocatePoolWithTag(PagedPool, sizeof(path_part), ALLOC_TAG); if (!pp) { ERR("out of memory\n"); goto end; } pp->name.Buffer = rr->name; pp->name.Length = pp->name.MaximumLength = rr->n; pp->orig_subvol = false; InsertTailList(&parts, &pp->list_entry); r = NULL; le = Vcb->roots.Flink; while (le != &Vcb->roots) { root* r2 = CONTAINING_RECORD(le, root, list_entry); if (r2->id == tp.item->key.offset) { r = r2; break; } le = le->Flink; } if (!r) { ERR("could not find subvol %I64x\n", tp.item->key.offset); goto end; } dir = rr->dir; orig_subvol = false; } else { not_in_tree = true; break; } } else { searchkey.obj_id = dir; searchkey.obj_type = TYPE_INODE_EXTREF; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, r, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); goto end; } if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == TYPE_INODE_REF) { INODE_REF* ir = (INODE_REF*)tp.item->data; path_part* pp; if (tp.item->size < sizeof(INODE_REF)) { 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)); goto end; } if (tp.item->size < offsetof(INODE_REF, name[0]) + ir->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(INODE_REF, name[0]) + ir->n); goto end; } pp = ExAllocatePoolWithTag(PagedPool, sizeof(path_part), ALLOC_TAG); if (!pp) { ERR("out of memory\n"); goto end; } pp->name.Buffer = ir->name; pp->name.Length = pp->name.MaximumLength = ir->n; pp->orig_subvol = orig_subvol; InsertTailList(&parts, &pp->list_entry); if (dir == tp.item->key.offset) break; dir = tp.item->key.offset; } else if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == TYPE_INODE_EXTREF) { INODE_EXTREF* ier = (INODE_EXTREF*)tp.item->data; path_part* pp; if (tp.item->size < sizeof(INODE_EXTREF)) { 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_EXTREF)); goto end; } if (tp.item->size < offsetof(INODE_EXTREF, name[0]) + ier->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(INODE_EXTREF, name[0]) + ier->n); goto end; } pp = ExAllocatePoolWithTag(PagedPool, sizeof(path_part), ALLOC_TAG); if (!pp) { ERR("out of memory\n"); goto end; } pp->name.Buffer = ier->name; pp->name.Length = pp->name.MaximumLength = ier->n; pp->orig_subvol = orig_subvol; InsertTailList(&parts, &pp->list_entry); if (dir == ier->dir) break; dir = ier->dir; } else { ERR("could not find INODE_REF for inode %I64x in subvol %I64x\n", dir, r->id); goto end; } } } fn.MaximumLength = 0; if (not_in_tree) { le = parts.Blink; while (le != &parts) { path_part* pp = CONTAINING_RECORD(le, path_part, list_entry); LIST_ENTRY* le2 = le->Blink; if (pp->orig_subvol) break; RemoveTailList(&parts); ExFreePool(pp); le = le2; } } le = parts.Flink; while (le != &parts) { path_part* pp = CONTAINING_RECORD(le, path_part, list_entry); fn.MaximumLength += pp->name.Length + 1; le = le->Flink; } fn.Buffer = ExAllocatePoolWithTag(PagedPool, fn.MaximumLength, ALLOC_TAG); if (!fn.Buffer) { ERR("out of memory\n"); goto end; } fn.Length = 0; le = parts.Blink; while (le != &parts) { path_part* pp = CONTAINING_RECORD(le, path_part, list_entry); fn.Buffer[fn.Length] = '\\'; fn.Length++; RtlCopyMemory(&fn.Buffer[fn.Length], pp->name.Buffer, pp->name.Length); fn.Length += pp->name.Length; le = le->Blink; } if (not_in_tree) ERR("subvol %I64x, %.*s, offset %I64x\n", subvol, fn.Length, fn.Buffer, offset); else ERR("%.*s, offset %I64x\n", fn.Length, fn.Buffer, offset); Status = utf8_to_utf16(NULL, 0, &utf16len, fn.Buffer, fn.Length); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 1 returned %08lx\n", Status); ExFreePool(fn.Buffer); goto end; } err = ExAllocatePoolWithTag(PagedPool, offsetof(scrub_error, data.filename[0]) + utf16len, ALLOC_TAG); if (!err) { ERR("out of memory\n"); ExFreePool(fn.Buffer); goto end; } err->address = addr; err->device = devid; err->recovered = false; err->is_metadata = false; err->parity = false; err->data.subvol = not_in_tree ? subvol : 0; err->data.offset = offset; err->data.filename_length = (uint16_t)utf16len; Status = utf8_to_utf16(err->data.filename, utf16len, &utf16len, fn.Buffer, fn.Length); if (!NT_SUCCESS(Status)) { ERR("utf8_to_utf16 2 returned %08lx\n", Status); ExFreePool(fn.Buffer); ExFreePool(err); goto end; } ExAcquireResourceExclusiveLite(&Vcb->scrub.stats_lock, true); Vcb->scrub.num_errors++; InsertTailList(&Vcb->scrub.errors, &err->list_entry); ExReleaseResourceLite(&Vcb->scrub.stats_lock); ExFreePool(fn.Buffer); end: while (!IsListEmpty(&parts)) { path_part* pp = CONTAINING_RECORD(RemoveHeadList(&parts), path_part, list_entry); ExFreePool(pp); } } static void log_file_checksum_error_shared(device_extension* Vcb, uint64_t treeaddr, uint64_t addr, uint64_t devid, uint64_t extent) { tree_header* tree; NTSTATUS Status; leaf_node* ln; ULONG i; tree = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.node_size, ALLOC_TAG); if (!tree) { ERR("out of memory\n"); return; } Status = read_data(Vcb, treeaddr, Vcb->superblock.node_size, NULL, true, (uint8_t*)tree, NULL, NULL, NULL, 0, false, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned %08lx\n", Status); goto end; } if (tree->level != 0) { ERR("tree level was %x, expected 0\n", tree->level); goto end; } ln = (leaf_node*)&tree[1]; for (i = 0; i < tree->num_items; i++) { if (ln[i].key.obj_type == TYPE_EXTENT_DATA && ln[i].size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) { EXTENT_DATA* ed = (EXTENT_DATA*)((uint8_t*)tree + sizeof(tree_header) + ln[i].offset); EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; if (ed->type == EXTENT_TYPE_REGULAR && ed2->size != 0 && ed2->address == addr) log_file_checksum_error(Vcb, addr, devid, tree->tree_id, ln[i].key.obj_id, ln[i].key.offset + addr - extent); } } end: ExFreePool(tree); } static void log_tree_checksum_error(device_extension* Vcb, uint64_t addr, uint64_t devid, uint64_t root, uint8_t level, KEY* firstitem) { scrub_error* err; err = ExAllocatePoolWithTag(PagedPool, sizeof(scrub_error), ALLOC_TAG); if (!err) { ERR("out of memory\n"); return; } err->address = addr; err->device = devid; err->recovered = false; err->is_metadata = true; err->parity = false; err->metadata.root = root; err->metadata.level = level; if (firstitem) { ERR("root %I64x, level %u, first item (%I64x,%x,%I64x)\n", root, level, firstitem->obj_id, firstitem->obj_type, firstitem->offset); err->metadata.firstitem = *firstitem; } else { ERR("root %I64x, level %u\n", root, level); RtlZeroMemory(&err->metadata.firstitem, sizeof(KEY)); } ExAcquireResourceExclusiveLite(&Vcb->scrub.stats_lock, true); Vcb->scrub.num_errors++; InsertTailList(&Vcb->scrub.errors, &err->list_entry); ExReleaseResourceLite(&Vcb->scrub.stats_lock); } static void log_tree_checksum_error_shared(device_extension* Vcb, uint64_t offset, uint64_t address, uint64_t devid) { tree_header* tree; NTSTATUS Status; internal_node* in; ULONG i; tree = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.node_size, ALLOC_TAG); if (!tree) { ERR("out of memory\n"); return; } Status = read_data(Vcb, offset, Vcb->superblock.node_size, NULL, true, (uint8_t*)tree, NULL, NULL, NULL, 0, false, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned %08lx\n", Status); goto end; } if (tree->level == 0) { ERR("tree level was 0\n"); goto end; } in = (internal_node*)&tree[1]; for (i = 0; i < tree->num_items; i++) { if (in[i].address == address) { log_tree_checksum_error(Vcb, address, devid, tree->tree_id, tree->level - 1, &in[i].key); break; } } end: ExFreePool(tree); } static void log_unrecoverable_error(device_extension* Vcb, uint64_t address, uint64_t devid) { KEY searchkey; traverse_ptr tp; NTSTATUS Status; EXTENT_ITEM* ei; EXTENT_ITEM2* ei2 = NULL; uint8_t* ptr; ULONG len; uint64_t rc; // FIXME - still log even if rest of this function fails searchkey.obj_id = address; searchkey.obj_type = TYPE_METADATA_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return; } if ((tp.item->key.obj_type != TYPE_EXTENT_ITEM && tp.item->key.obj_type != TYPE_METADATA_ITEM) || tp.item->key.obj_id >= address + Vcb->superblock.sector_size || (tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->key.obj_id + tp.item->key.offset <= address) || (tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->key.obj_id + Vcb->superblock.node_size <= address) ) return; if (tp.item->size < sizeof(EXTENT_ITEM)) { 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)); return; } ei = (EXTENT_ITEM*)tp.item->data; ptr = (uint8_t*)&ei[1]; len = tp.item->size - sizeof(EXTENT_ITEM); if (tp.item->key.obj_id == TYPE_EXTENT_ITEM && ei->flags & EXTENT_ITEM_TREE_BLOCK) { if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) { 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)); return; } ei2 = (EXTENT_ITEM2*)ptr; ptr += sizeof(EXTENT_ITEM2); len -= sizeof(EXTENT_ITEM2); } rc = 0; while (len > 0) { uint8_t type = *ptr; ptr++; len--; if (type == TYPE_TREE_BLOCK_REF) { TREE_BLOCK_REF* tbr; if (len < sizeof(TREE_BLOCK_REF)) { ERR("TREE_BLOCK_REF takes up %Iu bytes, but only %lu remaining\n", sizeof(TREE_BLOCK_REF), len); break; } tbr = (TREE_BLOCK_REF*)ptr; log_tree_checksum_error(Vcb, address, devid, tbr->offset, ei2 ? ei2->level : (uint8_t)tp.item->key.offset, ei2 ? &ei2->firstitem : NULL); rc++; ptr += sizeof(TREE_BLOCK_REF); len -= sizeof(TREE_BLOCK_REF); } else if (type == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* edr; if (len < sizeof(EXTENT_DATA_REF)) { ERR("EXTENT_DATA_REF takes up %Iu bytes, but only %lu remaining\n", sizeof(EXTENT_DATA_REF), len); break; } edr = (EXTENT_DATA_REF*)ptr; log_file_checksum_error(Vcb, address, devid, edr->root, edr->objid, edr->offset + address - tp.item->key.obj_id); rc += edr->count; ptr += sizeof(EXTENT_DATA_REF); len -= sizeof(EXTENT_DATA_REF); } else if (type == TYPE_SHARED_BLOCK_REF) { SHARED_BLOCK_REF* sbr; if (len < sizeof(SHARED_BLOCK_REF)) { ERR("SHARED_BLOCK_REF takes up %Iu bytes, but only %lu remaining\n", sizeof(SHARED_BLOCK_REF), len); break; } sbr = (SHARED_BLOCK_REF*)ptr; log_tree_checksum_error_shared(Vcb, sbr->offset, address, devid); rc++; ptr += sizeof(SHARED_BLOCK_REF); len -= sizeof(SHARED_BLOCK_REF); } else if (type == TYPE_SHARED_DATA_REF) { SHARED_DATA_REF* sdr; if (len < sizeof(SHARED_DATA_REF)) { ERR("SHARED_DATA_REF takes up %Iu bytes, but only %lu remaining\n", sizeof(SHARED_DATA_REF), len); break; } sdr = (SHARED_DATA_REF*)ptr; log_file_checksum_error_shared(Vcb, sdr->offset, address, devid, tp.item->key.obj_id); rc += sdr->count; ptr += sizeof(SHARED_DATA_REF); len -= sizeof(SHARED_DATA_REF); } else { ERR("unknown extent type %x\n", type); break; } } if (rc < ei->refcount) { do { traverse_ptr next_tp; if (find_next_item(Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else break; if (tp.item->key.obj_id == address) { if (tp.item->key.obj_type == TYPE_TREE_BLOCK_REF) log_tree_checksum_error(Vcb, address, devid, tp.item->key.offset, ei2 ? ei2->level : (uint8_t)tp.item->key.offset, ei2 ? &ei2->firstitem : NULL); else if (tp.item->key.obj_type == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* edr; if (tp.item->size < sizeof(EXTENT_DATA_REF)) { 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(EXTENT_DATA_REF)); break; } edr = (EXTENT_DATA_REF*)tp.item->data; log_file_checksum_error(Vcb, address, devid, edr->root, edr->objid, edr->offset + address - tp.item->key.obj_id); } else if (tp.item->key.obj_type == TYPE_SHARED_BLOCK_REF) log_tree_checksum_error_shared(Vcb, tp.item->key.offset, address, devid); else if (tp.item->key.obj_type == TYPE_SHARED_DATA_REF) log_file_checksum_error_shared(Vcb, tp.item->key.offset, address, devid, tp.item->key.obj_id); } else break; } while (true); } } static void log_error(device_extension* Vcb, uint64_t addr, uint64_t devid, bool metadata, bool recoverable, bool parity) { if (recoverable) { scrub_error* err; if (parity) { ERR("recovering from parity error at %I64x on device %I64x\n", addr, devid); } else { if (metadata) ERR("recovering from metadata checksum error at %I64x on device %I64x\n", addr, devid); else ERR("recovering from data checksum error at %I64x on device %I64x\n", addr, devid); } err = ExAllocatePoolWithTag(PagedPool, sizeof(scrub_error), ALLOC_TAG); if (!err) { ERR("out of memory\n"); return; } err->address = addr; err->device = devid; err->recovered = true; err->is_metadata = metadata; err->parity = parity; if (metadata) RtlZeroMemory(&err->metadata, sizeof(err->metadata)); else RtlZeroMemory(&err->data, sizeof(err->data)); ExAcquireResourceExclusiveLite(&Vcb->scrub.stats_lock, true); Vcb->scrub.num_errors++; InsertTailList(&Vcb->scrub.errors, &err->list_entry); ExReleaseResourceLite(&Vcb->scrub.stats_lock); } else { if (metadata) ERR("unrecoverable metadata checksum error at %I64x\n", addr); else ERR("unrecoverable data checksum error at %I64x\n", addr); log_unrecoverable_error(Vcb, addr, devid); } } _Function_class_(IO_COMPLETION_ROUTINE) static NTSTATUS __stdcall scrub_read_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) { scrub_context_stripe* stripe = conptr; scrub_context* context = (scrub_context*)stripe->context; ULONG left = InterlockedDecrement(&context->stripes_left); UNUSED(DeviceObject); stripe->iosb = Irp->IoStatus; if (left == 0) KeSetEvent(&context->Event, 0, false); return STATUS_MORE_PROCESSING_REQUIRED; } static NTSTATUS scrub_extent_dup(device_extension* Vcb, chunk* c, uint64_t offset, void* csum, scrub_context* context) { NTSTATUS Status; bool csum_error = false; ULONG i; CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; uint16_t present_devices = 0; if (csum) { ULONG good_stripe = 0xffffffff; for (i = 0; i < c->chunk_item->num_stripes; i++) { if (c->devices[i]->devobj) { present_devices++; // if first stripe is okay, we only need to check that the others are identical to it if (good_stripe != 0xffffffff) { if (RtlCompareMemory(context->stripes[i].buf, context->stripes[good_stripe].buf, context->stripes[good_stripe].length) != context->stripes[i].length) { context->stripes[i].csum_error = true; csum_error = true; log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } else { Status = check_csum(Vcb, context->stripes[i].buf, context->stripes[i].length >> Vcb->sector_shift, csum); if (Status == STATUS_CRC_ERROR) { context->stripes[i].csum_error = true; csum_error = true; log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } else if (!NT_SUCCESS(Status)) { ERR("check_csum returned %08lx\n", Status); return Status; } else good_stripe = i; } } } } else { ULONG good_stripe = 0xffffffff; for (i = 0; i < c->chunk_item->num_stripes; i++) { ULONG j; if (c->devices[i]->devobj) { // if first stripe is okay, we only need to check that the others are identical to it if (good_stripe != 0xffffffff) { if (RtlCompareMemory(context->stripes[i].buf, context->stripes[good_stripe].buf, context->stripes[good_stripe].length) != context->stripes[i].length) { context->stripes[i].csum_error = true; csum_error = true; log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } else { for (j = 0; j < context->stripes[i].length / Vcb->superblock.node_size; j++) { tree_header* th = (tree_header*)&context->stripes[i].buf[j * Vcb->superblock.node_size]; if (!check_tree_checksum(Vcb, th) || th->address != offset + UInt32x32To64(j, Vcb->superblock.node_size)) { context->stripes[i].csum_error = true; csum_error = true; log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } if (!context->stripes[i].csum_error) good_stripe = i; } } } } if (!csum_error) return STATUS_SUCCESS; // handle checksum error for (i = 0; i < c->chunk_item->num_stripes; i++) { if (context->stripes[i].csum_error) { if (csum) { context->stripes[i].bad_csums = ExAllocatePoolWithTag(PagedPool, (context->stripes[i].length * Vcb->csum_size) >> Vcb->sector_shift, ALLOC_TAG); if (!context->stripes[i].bad_csums) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } do_calc_job(Vcb, context->stripes[i].buf, context->stripes[i].length >> Vcb->sector_shift, context->stripes[i].bad_csums); } else { ULONG j; context->stripes[i].bad_csums = ExAllocatePoolWithTag(PagedPool, (context->stripes[i].length * Vcb->csum_size) >> Vcb->sector_shift, ALLOC_TAG); if (!context->stripes[i].bad_csums) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } for (j = 0; j < context->stripes[i].length / Vcb->superblock.node_size; j++) { tree_header* th = (tree_header*)&context->stripes[i].buf[j * Vcb->superblock.node_size]; get_tree_checksum(Vcb, th, (uint8_t*)context->stripes[i].bad_csums + (Vcb->csum_size * j)); } } } } if (present_devices > 1) { ULONG good_stripe = 0xffffffff; for (i = 0; i < c->chunk_item->num_stripes; i++) { if (c->devices[i]->devobj && !context->stripes[i].csum_error) { good_stripe = i; break; } } if (good_stripe != 0xffffffff) { // log for (i = 0; i < c->chunk_item->num_stripes; i++) { if (context->stripes[i].csum_error) { ULONG j; if (csum) { for (j = 0; j < context->stripes[i].length >> Vcb->sector_shift; j++) { 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) { uint64_t addr = offset + ((uint64_t)j << Vcb->sector_shift); log_error(Vcb, addr, c->devices[i]->devitem.dev_id, false, true, false); log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } } else { for (j = 0; j < context->stripes[i].length / Vcb->superblock.node_size; j++) { tree_header* th = (tree_header*)&context->stripes[i].buf[j * Vcb->superblock.node_size]; uint64_t addr = offset + UInt32x32To64(j, Vcb->superblock.node_size); if (RtlCompareMemory((uint8_t*)context->stripes[i].bad_csums + (j * Vcb->csum_size), th, Vcb->csum_size) != Vcb->csum_size || th->address != addr) { log_error(Vcb, addr, c->devices[i]->devitem.dev_id, true, true, false); log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } } } } // write good data over bad for (i = 0; i < c->chunk_item->num_stripes; i++) { if (context->stripes[i].csum_error && !c->devices[i]->readonly) { Status = write_data_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + offset - c->offset, context->stripes[good_stripe].buf, context->stripes[i].length); if (!NT_SUCCESS(Status)) { ERR("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_WRITE_ERRORS); return Status; } } } return STATUS_SUCCESS; } // if csum errors on all stripes, check sector by sector for (i = 0; i < c->chunk_item->num_stripes; i++) { if (c->devices[i]->devobj) { if (csum) { for (ULONG j = 0; j < context->stripes[i].length >> Vcb->sector_shift; j++) { 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) { ULONG k; uint64_t addr = offset + ((uint64_t)j << Vcb->sector_shift); bool recovered = false; for (k = 0; k < c->chunk_item->num_stripes; k++) { if (i != k && c->devices[k]->devobj && RtlCompareMemory((uint8_t*)context->stripes[k].bad_csums + (j * Vcb->csum_size), (uint8_t*)csum + (j * Vcb->csum_size), Vcb->csum_size) == Vcb->csum_size) { log_error(Vcb, addr, c->devices[i]->devitem.dev_id, false, true, false); log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS); RtlCopyMemory(context->stripes[i].buf + (j << Vcb->sector_shift), context->stripes[k].buf + (j << Vcb->sector_shift), Vcb->superblock.sector_size); recovered = true; break; } } if (!recovered) { log_error(Vcb, addr, c->devices[i]->devitem.dev_id, false, false, false); log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } } } else { for (ULONG j = 0; j < context->stripes[i].length / Vcb->superblock.node_size; j++) { tree_header* th = (tree_header*)&context->stripes[i].buf[j * Vcb->superblock.node_size]; uint64_t addr = offset + UInt32x32To64(j, Vcb->superblock.node_size); if (RtlCompareMemory((uint8_t*)context->stripes[i].bad_csums + (j * Vcb->csum_size), th, Vcb->csum_size) != Vcb->csum_size || th->address != addr) { ULONG k; bool recovered = false; for (k = 0; k < c->chunk_item->num_stripes; k++) { if (i != k && c->devices[k]->devobj) { tree_header* th2 = (tree_header*)&context->stripes[k].buf[j * Vcb->superblock.node_size]; if (RtlCompareMemory((uint8_t*)context->stripes[k].bad_csums + (j * Vcb->csum_size), th2, Vcb->csum_size) == Vcb->csum_size && th2->address == addr) { log_error(Vcb, addr, c->devices[i]->devitem.dev_id, true, true, false); log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS); RtlCopyMemory(th, th2, Vcb->superblock.node_size); recovered = true; break; } } } if (!recovered) { log_error(Vcb, addr, c->devices[i]->devitem.dev_id, true, false, false); log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } } } } } // write good data over bad for (i = 0; i < c->chunk_item->num_stripes; i++) { if (c->devices[i]->devobj && !c->devices[i]->readonly) { Status = write_data_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + offset - c->offset, context->stripes[i].buf, context->stripes[i].length); if (!NT_SUCCESS(Status)) { ERR("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS); return Status; } } } return STATUS_SUCCESS; } for (i = 0; i < c->chunk_item->num_stripes; i++) { if (c->devices[i]->devobj) { ULONG j; if (csum) { for (j = 0; j < context->stripes[i].length >> Vcb->sector_shift; j++) { 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) { uint64_t addr = offset + ((uint64_t)j << Vcb->sector_shift); log_error(Vcb, addr, c->devices[i]->devitem.dev_id, false, false, false); } } } else { for (j = 0; j < context->stripes[i].length / Vcb->superblock.node_size; j++) { tree_header* th = (tree_header*)&context->stripes[i].buf[j * Vcb->superblock.node_size]; uint64_t addr = offset + UInt32x32To64(j, Vcb->superblock.node_size); if (RtlCompareMemory((uint8_t*)context->stripes[i].bad_csums + (j * Vcb->csum_size), th, Vcb->csum_size) != Vcb->csum_size || th->address != addr) log_error(Vcb, addr, c->devices[i]->devitem.dev_id, true, false, false); } } } } return STATUS_SUCCESS; } static NTSTATUS scrub_extent_raid0(device_extension* Vcb, chunk* c, uint64_t offset, uint32_t length, uint16_t startoffstripe, void* csum, scrub_context* context) { ULONG j; uint16_t stripe; uint32_t pos, *stripeoff; pos = 0; stripeoff = ExAllocatePoolWithTag(NonPagedPool, sizeof(uint32_t) * c->chunk_item->num_stripes, ALLOC_TAG); if (!stripeoff) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(stripeoff, sizeof(uint32_t) * c->chunk_item->num_stripes); stripe = startoffstripe; while (pos < length) { uint32_t readlen; if (pos == 0) readlen = (uint32_t)min(context->stripes[stripe].length, c->chunk_item->stripe_length - (context->stripes[stripe].start % c->chunk_item->stripe_length)); else readlen = min(length - pos, (uint32_t)c->chunk_item->stripe_length); if (csum) { for (j = 0; j < readlen; j += Vcb->superblock.sector_size) { if (!check_sector_csum(Vcb, context->stripes[stripe].buf + stripeoff[stripe], (uint8_t*)csum + ((pos * Vcb->csum_size) >> Vcb->sector_shift))) { uint64_t addr = offset + pos; log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, false, false, false); log_device_error(Vcb, c->devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } pos += Vcb->superblock.sector_size; stripeoff[stripe] += Vcb->superblock.sector_size; } } else { for (j = 0; j < readlen; j += Vcb->superblock.node_size) { tree_header* th = (tree_header*)(context->stripes[stripe].buf + stripeoff[stripe]); uint64_t addr = offset + pos; if (!check_tree_checksum(Vcb, th) || th->address != addr) { log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, true, false, false); log_device_error(Vcb, c->devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } pos += Vcb->superblock.node_size; stripeoff[stripe] += Vcb->superblock.node_size; } } stripe = (stripe + 1) % c->chunk_item->num_stripes; } ExFreePool(stripeoff); return STATUS_SUCCESS; } static NTSTATUS scrub_extent_raid10(device_extension* Vcb, chunk* c, uint64_t offset, uint32_t length, uint16_t startoffstripe, void* csum, scrub_context* context) { ULONG j; uint16_t stripe, sub_stripes = max(c->chunk_item->sub_stripes, 1); uint32_t pos, *stripeoff; bool csum_error = false; NTSTATUS Status; pos = 0; stripeoff = ExAllocatePoolWithTag(NonPagedPool, sizeof(uint32_t) * c->chunk_item->num_stripes / sub_stripes, ALLOC_TAG); if (!stripeoff) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(stripeoff, sizeof(uint32_t) * c->chunk_item->num_stripes / sub_stripes); stripe = startoffstripe; while (pos < length) { uint32_t readlen; if (pos == 0) readlen = (uint32_t)min(context->stripes[stripe * sub_stripes].length, c->chunk_item->stripe_length - (context->stripes[stripe * sub_stripes].start % c->chunk_item->stripe_length)); else readlen = min(length - pos, (uint32_t)c->chunk_item->stripe_length); if (csum) { ULONG good_stripe = 0xffffffff; uint16_t k; for (k = 0; k < sub_stripes; k++) { if (c->devices[(stripe * sub_stripes) + k]->devobj) { // if first stripe is okay, we only need to check that the others are identical to it if (good_stripe != 0xffffffff) { if (RtlCompareMemory(context->stripes[(stripe * sub_stripes) + k].buf + stripeoff[stripe], context->stripes[(stripe * sub_stripes) + good_stripe].buf + stripeoff[stripe], readlen) != readlen) { context->stripes[(stripe * sub_stripes) + k].csum_error = true; csum_error = true; log_device_error(Vcb, c->devices[(stripe * sub_stripes) + k], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } else { for (j = 0; j < readlen; j += Vcb->superblock.sector_size) { if (!check_sector_csum(Vcb, context->stripes[(stripe * sub_stripes) + k].buf + stripeoff[stripe] + j, (uint8_t*)csum + (((pos + j) * Vcb->csum_size) >> Vcb->sector_shift))) { csum_error = true; context->stripes[(stripe * sub_stripes) + k].csum_error = true; log_device_error(Vcb, c->devices[(stripe * sub_stripes) + k], BTRFS_DEV_STAT_CORRUPTION_ERRORS); break; } } if (!context->stripes[(stripe * sub_stripes) + k].csum_error) good_stripe = k; } } } pos += readlen; stripeoff[stripe] += readlen; } else { ULONG good_stripe = 0xffffffff; uint16_t k; for (k = 0; k < sub_stripes; k++) { if (c->devices[(stripe * sub_stripes) + k]->devobj) { // if first stripe is okay, we only need to check that the others are identical to it if (good_stripe != 0xffffffff) { if (RtlCompareMemory(context->stripes[(stripe * sub_stripes) + k].buf + stripeoff[stripe], context->stripes[(stripe * sub_stripes) + good_stripe].buf + stripeoff[stripe], readlen) != readlen) { context->stripes[(stripe * sub_stripes) + k].csum_error = true; csum_error = true; log_device_error(Vcb, c->devices[(stripe * sub_stripes) + k], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } else { for (j = 0; j < readlen; j += Vcb->superblock.node_size) { tree_header* th = (tree_header*)(context->stripes[(stripe * sub_stripes) + k].buf + stripeoff[stripe] + j); uint64_t addr = offset + pos + j; if (!check_tree_checksum(Vcb, th) || th->address != addr) { csum_error = true; context->stripes[(stripe * sub_stripes) + k].csum_error = true; log_device_error(Vcb, c->devices[(stripe * sub_stripes) + k], BTRFS_DEV_STAT_CORRUPTION_ERRORS); break; } } if (!context->stripes[(stripe * sub_stripes) + k].csum_error) good_stripe = k; } } } pos += readlen; stripeoff[stripe] += readlen; } stripe = (stripe + 1) % (c->chunk_item->num_stripes / sub_stripes); } if (!csum_error) { Status = STATUS_SUCCESS; goto end; } for (j = 0; j < c->chunk_item->num_stripes; j += sub_stripes) { ULONG goodstripe = 0xffffffff; uint16_t k; bool hasbadstripe = false; if (context->stripes[j].length == 0) continue; for (k = 0; k < sub_stripes; k++) { if (c->devices[j + k]->devobj) { if (!context->stripes[j + k].csum_error) goodstripe = k; else hasbadstripe = true; } } if (hasbadstripe) { if (goodstripe != 0xffffffff) { for (k = 0; k < sub_stripes; k++) { if (c->devices[j + k]->devobj && context->stripes[j + k].csum_error) { uint32_t so = 0; bool recovered = false; pos = 0; stripe = startoffstripe; while (pos < length) { uint32_t readlen; if (pos == 0) readlen = (uint32_t)min(context->stripes[stripe * sub_stripes].length, c->chunk_item->stripe_length - (context->stripes[stripe * sub_stripes].start % c->chunk_item->stripe_length)); else readlen = min(length - pos, (uint32_t)c->chunk_item->stripe_length); if (stripe == j / sub_stripes) { if (csum) { ULONG l; for (l = 0; l < readlen; l += Vcb->superblock.sector_size) { if (RtlCompareMemory(context->stripes[j + k].buf + so, context->stripes[j + goodstripe].buf + so, Vcb->superblock.sector_size) != Vcb->superblock.sector_size) { uint64_t addr = offset + pos; log_error(Vcb, addr, c->devices[j + k]->devitem.dev_id, false, true, false); recovered = true; } pos += Vcb->superblock.sector_size; so += Vcb->superblock.sector_size; } } else { ULONG l; for (l = 0; l < readlen; l += Vcb->superblock.node_size) { if (RtlCompareMemory(context->stripes[j + k].buf + so, context->stripes[j + goodstripe].buf + so, Vcb->superblock.node_size) != Vcb->superblock.node_size) { uint64_t addr = offset + pos; log_error(Vcb, addr, c->devices[j + k]->devitem.dev_id, true, true, false); recovered = true; } pos += Vcb->superblock.node_size; so += Vcb->superblock.node_size; } } } else pos += readlen; stripe = (stripe + 1) % (c->chunk_item->num_stripes / sub_stripes); } if (recovered) { // write good data over bad if (!c->devices[j + k]->readonly) { CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; Status = write_data_phys(c->devices[j + k]->devobj, c->devices[j + k]->fileobj, cis[j + k].offset + offset - c->offset, context->stripes[j + goodstripe].buf, context->stripes[j + goodstripe].length); if (!NT_SUCCESS(Status)) { ERR("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, c->devices[j + k], BTRFS_DEV_STAT_WRITE_ERRORS); goto end; } } } } } } else { uint32_t so = 0; bool recovered = false; if (csum) { for (k = 0; k < sub_stripes; k++) { if (c->devices[j + k]->devobj) { context->stripes[j + k].bad_csums = ExAllocatePoolWithTag(PagedPool, (context->stripes[j + k].length * Vcb->csum_size) >> Vcb->sector_shift, ALLOC_TAG); if (!context->stripes[j + k].bad_csums) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } do_calc_job(Vcb, context->stripes[j + k].buf, context->stripes[j + k].length >> Vcb->sector_shift, context->stripes[j + k].bad_csums); } } } else { for (k = 0; k < sub_stripes; k++) { if (c->devices[j + k]->devobj) { ULONG l; context->stripes[j + k].bad_csums = ExAllocatePoolWithTag(PagedPool, context->stripes[j + k].length * Vcb->csum_size / Vcb->superblock.node_size, ALLOC_TAG); if (!context->stripes[j + k].bad_csums) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } for (l = 0; l < context->stripes[j + k].length / Vcb->superblock.node_size; l++) { tree_header* th = (tree_header*)&context->stripes[j + k].buf[l * Vcb->superblock.node_size]; get_tree_checksum(Vcb, th, (uint8_t*)context->stripes[j + k].bad_csums + (Vcb->csum_size * l)); } } } } pos = 0; stripe = startoffstripe; while (pos < length) { uint32_t readlen; if (pos == 0) readlen = (uint32_t)min(context->stripes[stripe * sub_stripes].length, c->chunk_item->stripe_length - (context->stripes[stripe * sub_stripes].start % c->chunk_item->stripe_length)); else readlen = min(length - pos, (uint32_t)c->chunk_item->stripe_length); if (stripe == j / sub_stripes) { ULONG l; if (csum) { for (l = 0; l < readlen; l += Vcb->superblock.sector_size) { bool has_error = false; goodstripe = 0xffffffff; for (k = 0; k < sub_stripes; k++) { if (c->devices[j + k]->devobj) { if (RtlCompareMemory((uint8_t*)context->stripes[j + k].bad_csums + ((so * Vcb->csum_size) >> Vcb->sector_shift), (uint8_t*)csum + ((pos * Vcb->csum_size) >> Vcb->sector_shift), Vcb->csum_size) != Vcb->csum_size) { has_error = true; } else goodstripe = k; } } if (has_error) { if (goodstripe != 0xffffffff) { for (k = 0; k < sub_stripes; k++) { if (c->devices[j + k]->devobj && RtlCompareMemory((uint8_t*)context->stripes[j + k].bad_csums + ((so * Vcb->csum_size) >> Vcb->sector_shift), (uint8_t*)csum + ((pos * Vcb->csum_size) >> Vcb->sector_shift), Vcb->csum_size) != Vcb->csum_size) { uint64_t addr = offset + pos; log_error(Vcb, addr, c->devices[j + k]->devitem.dev_id, false, true, false); recovered = true; RtlCopyMemory(context->stripes[j + k].buf + so, context->stripes[j + goodstripe].buf + so, Vcb->superblock.sector_size); } } } else { uint64_t addr = offset + pos; for (k = 0; k < sub_stripes; k++) { if (c->devices[j + j]->devobj) { log_error(Vcb, addr, c->devices[j + k]->devitem.dev_id, false, false, false); log_device_error(Vcb, c->devices[j + k], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } } } pos += Vcb->superblock.sector_size; so += Vcb->superblock.sector_size; } } else { for (l = 0; l < readlen; l += Vcb->superblock.node_size) { for (k = 0; k < sub_stripes; k++) { if (c->devices[j + k]->devobj) { tree_header* th = (tree_header*)&context->stripes[j + k].buf[so]; uint64_t addr = offset + pos; 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) { ULONG m; recovered = false; for (m = 0; m < sub_stripes; m++) { if (m != k) { tree_header* th2 = (tree_header*)&context->stripes[j + m].buf[so]; 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) { log_error(Vcb, addr, c->devices[j + k]->devitem.dev_id, true, true, false); RtlCopyMemory(th, th2, Vcb->superblock.node_size); recovered = true; break; } else log_device_error(Vcb, c->devices[j + m], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } if (!recovered) log_error(Vcb, addr, c->devices[j + k]->devitem.dev_id, true, false, false); } } } pos += Vcb->superblock.node_size; so += Vcb->superblock.node_size; } } } else pos += readlen; stripe = (stripe + 1) % (c->chunk_item->num_stripes / sub_stripes); } if (recovered) { // write good data over bad for (k = 0; k < sub_stripes; k++) { if (c->devices[j + k]->devobj && !c->devices[j + k]->readonly) { CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; Status = write_data_phys(c->devices[j + k]->devobj, c->devices[j + k]->fileobj, cis[j + k].offset + offset - c->offset, context->stripes[j + k].buf, context->stripes[j + k].length); if (!NT_SUCCESS(Status)) { ERR("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, c->devices[j + k], BTRFS_DEV_STAT_WRITE_ERRORS); goto end; } } } } } } } Status = STATUS_SUCCESS; end: ExFreePool(stripeoff); return Status; } static NTSTATUS scrub_extent(device_extension* Vcb, chunk* c, ULONG type, uint64_t offset, uint32_t size, void* csum) { ULONG i; scrub_context context; CHUNK_ITEM_STRIPE* cis; NTSTATUS Status; uint16_t startoffstripe = 0, num_missing, allowed_missing; TRACE("(%p, %p, %lx, %I64x, %x, %p)\n", Vcb, c, type, offset, size, csum); context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(scrub_context_stripe) * c->chunk_item->num_stripes, ALLOC_TAG); if (!context.stripes) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlZeroMemory(context.stripes, sizeof(scrub_context_stripe) * c->chunk_item->num_stripes); context.stripes_left = 0; cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; if (type == BLOCK_FLAG_RAID0) { uint64_t startoff, endoff; uint16_t endoffstripe; get_raid0_offset(offset - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &startoff, &startoffstripe); get_raid0_offset(offset + size - c->offset - 1, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &endoff, &endoffstripe); for (i = 0; i < c->chunk_item->num_stripes; i++) { if (startoffstripe > i) context.stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; else if (startoffstripe == i) context.stripes[i].start = startoff; else context.stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length); if (endoffstripe > i) context.stripes[i].length = (uint32_t)(endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length - context.stripes[i].start); else if (endoffstripe == i) context.stripes[i].length = (uint32_t)(endoff + 1 - context.stripes[i].start); else context.stripes[i].length = (uint32_t)(endoff - (endoff % c->chunk_item->stripe_length) - context.stripes[i].start); } allowed_missing = 0; } else if (type == BLOCK_FLAG_RAID10) { uint64_t startoff, endoff; uint16_t endoffstripe, j, sub_stripes = max(c->chunk_item->sub_stripes, 1); get_raid0_offset(offset - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes / sub_stripes, &startoff, &startoffstripe); get_raid0_offset(offset + size - c->offset - 1, c->chunk_item->stripe_length, c->chunk_item->num_stripes / sub_stripes, &endoff, &endoffstripe); if ((c->chunk_item->num_stripes % sub_stripes) != 0) { ERR("chunk %I64x: num_stripes %x was not a multiple of sub_stripes %x!\n", c->offset, c->chunk_item->num_stripes, sub_stripes); Status = STATUS_INTERNAL_ERROR; goto end; } startoffstripe *= sub_stripes; endoffstripe *= sub_stripes; for (i = 0; i < c->chunk_item->num_stripes; i += sub_stripes) { if (startoffstripe > i) context.stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; else if (startoffstripe == i) context.stripes[i].start = startoff; else context.stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length); if (endoffstripe > i) context.stripes[i].length = (uint32_t)(endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length - context.stripes[i].start); else if (endoffstripe == i) context.stripes[i].length = (uint32_t)(endoff + 1 - context.stripes[i].start); else context.stripes[i].length = (uint32_t)(endoff - (endoff % c->chunk_item->stripe_length) - context.stripes[i].start); for (j = 1; j < sub_stripes; j++) { context.stripes[i+j].start = context.stripes[i].start; context.stripes[i+j].length = context.stripes[i].length; } } startoffstripe /= sub_stripes; allowed_missing = 1; } else allowed_missing = c->chunk_item->num_stripes - 1; num_missing = 0; for (i = 0; i < c->chunk_item->num_stripes; i++) { PIO_STACK_LOCATION IrpSp; context.stripes[i].context = (struct _scrub_context*)&context; if (type == BLOCK_FLAG_DUPLICATE) { context.stripes[i].start = offset - c->offset; context.stripes[i].length = size; } else if (type != BLOCK_FLAG_RAID0 && type != BLOCK_FLAG_RAID10) { ERR("unexpected chunk type %lx\n", type); Status = STATUS_INTERNAL_ERROR; goto end; } if (!c->devices[i]->devobj) { num_missing++; if (num_missing > allowed_missing) { ERR("too many missing devices (at least %u, maximum allowed %u)\n", num_missing, allowed_missing); Status = STATUS_INTERNAL_ERROR; goto end; } } else if (context.stripes[i].length > 0) { context.stripes[i].buf = ExAllocatePoolWithTag(NonPagedPool, context.stripes[i].length, ALLOC_TAG); if (!context.stripes[i].buf) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } context.stripes[i].Irp = IoAllocateIrp(c->devices[i]->devobj->StackSize, false); if (!context.stripes[i].Irp) { ERR("IoAllocateIrp failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } IrpSp = IoGetNextIrpStackLocation(context.stripes[i].Irp); IrpSp->MajorFunction = IRP_MJ_READ; IrpSp->FileObject = c->devices[i]->fileobj; if (c->devices[i]->devobj->Flags & DO_BUFFERED_IO) { context.stripes[i].Irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPool, context.stripes[i].length, ALLOC_TAG); if (!context.stripes[i].Irp->AssociatedIrp.SystemBuffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } context.stripes[i].Irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION; context.stripes[i].Irp->UserBuffer = context.stripes[i].buf; } else if (c->devices[i]->devobj->Flags & DO_DIRECT_IO) { context.stripes[i].Irp->MdlAddress = IoAllocateMdl(context.stripes[i].buf, context.stripes[i].length, false, false, NULL); if (!context.stripes[i].Irp->MdlAddress) { ERR("IoAllocateMdl failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } Status = STATUS_SUCCESS; try { MmProbeAndLockPages(context.stripes[i].Irp->MdlAddress, KernelMode, IoWriteAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); IoFreeMdl(context.stripes[i].Irp->MdlAddress); context.stripes[i].Irp->MdlAddress = NULL; goto end; } } else context.stripes[i].Irp->UserBuffer = context.stripes[i].buf; IrpSp->Parameters.Read.Length = context.stripes[i].length; IrpSp->Parameters.Read.ByteOffset.QuadPart = context.stripes[i].start + cis[i].offset; context.stripes[i].Irp->UserIosb = &context.stripes[i].iosb; IoSetCompletionRoutine(context.stripes[i].Irp, scrub_read_completion, &context.stripes[i], true, true, true); context.stripes_left++; Vcb->scrub.data_scrubbed += context.stripes[i].length; } } if (context.stripes_left == 0) { ERR("error - not reading any stripes\n"); Status = STATUS_INTERNAL_ERROR; goto end; } KeInitializeEvent(&context.Event, NotificationEvent, false); for (i = 0; i < c->chunk_item->num_stripes; i++) { if (c->devices[i]->devobj && context.stripes[i].length > 0) IoCallDriver(c->devices[i]->devobj, context.stripes[i].Irp); } KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL); // return an error if any of the stripes returned an error for (i = 0; i < c->chunk_item->num_stripes; i++) { if (!NT_SUCCESS(context.stripes[i].iosb.Status)) { Status = context.stripes[i].iosb.Status; log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_READ_ERRORS); goto end; } } if (type == BLOCK_FLAG_DUPLICATE) { Status = scrub_extent_dup(Vcb, c, offset, csum, &context); if (!NT_SUCCESS(Status)) { ERR("scrub_extent_dup returned %08lx\n", Status); goto end; } } else if (type == BLOCK_FLAG_RAID0) { Status = scrub_extent_raid0(Vcb, c, offset, size, startoffstripe, csum, &context); if (!NT_SUCCESS(Status)) { ERR("scrub_extent_raid0 returned %08lx\n", Status); goto end; } } else if (type == BLOCK_FLAG_RAID10) { Status = scrub_extent_raid10(Vcb, c, offset, size, startoffstripe, csum, &context); if (!NT_SUCCESS(Status)) { ERR("scrub_extent_raid10 returned %08lx\n", Status); goto end; } } end: if (context.stripes) { for (i = 0; i < c->chunk_item->num_stripes; i++) { if (context.stripes[i].Irp) { if (c->devices[i]->devobj->Flags & DO_DIRECT_IO && context.stripes[i].Irp->MdlAddress) { MmUnlockPages(context.stripes[i].Irp->MdlAddress); IoFreeMdl(context.stripes[i].Irp->MdlAddress); } IoFreeIrp(context.stripes[i].Irp); } if (context.stripes[i].buf) ExFreePool(context.stripes[i].buf); if (context.stripes[i].bad_csums) ExFreePool(context.stripes[i].bad_csums); } ExFreePool(context.stripes); } return Status; } static NTSTATUS scrub_data_extent(device_extension* Vcb, chunk* c, uint64_t offset, ULONG type, void* csum, RTL_BITMAP* bmp, ULONG bmplen) { NTSTATUS Status; ULONG runlength, index; runlength = RtlFindFirstRunClear(bmp, &index); while (runlength != 0) { if (index >= bmplen) break; if (index + runlength >= bmplen) { runlength = bmplen - index; if (runlength == 0) break; } do { ULONG rl; if (runlength << Vcb->sector_shift > SCRUB_UNIT) rl = SCRUB_UNIT >> Vcb->sector_shift; else rl = runlength; Status = scrub_extent(Vcb, c, type, offset + ((uint64_t)index << Vcb->sector_shift), rl << Vcb->sector_shift, (uint8_t*)csum + (index * Vcb->csum_size)); if (!NT_SUCCESS(Status)) { ERR("scrub_data_extent_dup returned %08lx\n", Status); return Status; } runlength -= rl; index += rl; } while (runlength > 0); runlength = RtlFindNextForwardRunClear(bmp, index, &index); } return STATUS_SUCCESS; } typedef struct { uint8_t* buf; PIRP Irp; void* context; IO_STATUS_BLOCK iosb; uint64_t offset; bool rewrite, missing; RTL_BITMAP error; ULONG* errorarr; } scrub_context_raid56_stripe; typedef struct { scrub_context_raid56_stripe* stripes; LONG stripes_left; KEVENT Event; RTL_BITMAP alloc; RTL_BITMAP has_csum; RTL_BITMAP is_tree; void* csum; uint8_t* parity_scratch; uint8_t* parity_scratch2; } scrub_context_raid56; _Function_class_(IO_COMPLETION_ROUTINE) static NTSTATUS __stdcall scrub_read_completion_raid56(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) { scrub_context_raid56_stripe* stripe = conptr; scrub_context_raid56* context = (scrub_context_raid56*)stripe->context; LONG left = InterlockedDecrement(&context->stripes_left); UNUSED(DeviceObject); stripe->iosb = Irp->IoStatus; if (left == 0) KeSetEvent(&context->Event, 0, false); return STATUS_MORE_PROCESSING_REQUIRED; } static void scrub_raid5_stripe(device_extension* Vcb, chunk* c, scrub_context_raid56* context, uint64_t stripe_start, uint64_t bit_start, uint64_t num, uint16_t missing_devices) { ULONG sectors_per_stripe = (ULONG)(c->chunk_item->stripe_length >> Vcb->sector_shift), off; uint16_t stripe, parity = (bit_start + num + c->chunk_item->num_stripes - 1) % c->chunk_item->num_stripes; uint64_t stripeoff; stripe = (parity + 1) % c->chunk_item->num_stripes; off = (ULONG)(bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 1); stripeoff = num * sectors_per_stripe; if (missing_devices == 0) RtlCopyMemory(context->parity_scratch, &context->stripes[parity].buf[num * c->chunk_item->stripe_length], (ULONG)c->chunk_item->stripe_length); while (stripe != parity) { RtlClearAllBits(&context->stripes[stripe].error); for (ULONG i = 0; i < sectors_per_stripe; i++) { if (c->devices[stripe]->devobj && RtlCheckBit(&context->alloc, off)) { if (RtlCheckBit(&context->is_tree, off)) { tree_header* th = (tree_header*)&context->stripes[stripe].buf[stripeoff << Vcb->sector_shift]; uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 1) * c->chunk_item->stripe_length) + (off << Vcb->sector_shift); if (!check_tree_checksum(Vcb, th) || th->address != addr) { RtlSetBits(&context->stripes[stripe].error, i, Vcb->superblock.node_size >> Vcb->sector_shift); log_device_error(Vcb, c->devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); if (missing_devices > 0) log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, true, false, false); } off += Vcb->superblock.node_size >> Vcb->sector_shift; stripeoff += Vcb->superblock.node_size >> Vcb->sector_shift; i += (Vcb->superblock.node_size >> Vcb->sector_shift) - 1; continue; } else if (RtlCheckBit(&context->has_csum, off)) { if (!check_sector_csum(Vcb, context->stripes[stripe].buf + (stripeoff << Vcb->sector_shift), (uint8_t*)context->csum + (Vcb->csum_size * off))) { RtlSetBit(&context->stripes[stripe].error, i); log_device_error(Vcb, c->devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); if (missing_devices > 0) { uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 1) * c->chunk_item->stripe_length) + (off << Vcb->sector_shift); log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, false, false, false); } } } } off++; stripeoff++; } if (missing_devices == 0) do_xor(context->parity_scratch, &context->stripes[stripe].buf[num * c->chunk_item->stripe_length], (ULONG)c->chunk_item->stripe_length); stripe = (stripe + 1) % c->chunk_item->num_stripes; stripeoff = num * sectors_per_stripe; } // check parity if (missing_devices == 0) { RtlClearAllBits(&context->stripes[parity].error); for (ULONG i = 0; i < sectors_per_stripe; i++) { ULONG o, j; o = i << Vcb->sector_shift; for (j = 0; j < Vcb->superblock.sector_size; j++) { // FIXME - use SSE if (context->parity_scratch[o] != 0) { RtlSetBit(&context->stripes[parity].error, i); break; } o++; } } } // log and fix errors if (missing_devices > 0) return; for (ULONG i = 0; i < sectors_per_stripe; i++) { ULONG num_errors = 0, bad_off = 0; uint64_t bad_stripe = 0; bool alloc = false; stripe = (parity + 1) % c->chunk_item->num_stripes; off = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 1)) + i; while (stripe != parity) { if (RtlCheckBit(&context->alloc, off)) { alloc = true; if (RtlCheckBit(&context->stripes[stripe].error, i)) { bad_stripe = stripe; bad_off = off; num_errors++; } } off += sectors_per_stripe; stripe = (stripe + 1) % c->chunk_item->num_stripes; } if (!alloc) continue; if (num_errors == 0 && !RtlCheckBit(&context->stripes[parity].error, i)) // everything fine continue; if (num_errors == 0 && RtlCheckBit(&context->stripes[parity].error, i)) { // parity error uint64_t addr; do_xor(&context->stripes[parity].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.sector_size); bad_off = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 1)) + i; addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 1) * c->chunk_item->stripe_length) + (bad_off << Vcb->sector_shift); context->stripes[parity].rewrite = true; log_error(Vcb, addr, c->devices[parity]->devitem.dev_id, false, true, true); log_device_error(Vcb, c->devices[parity], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } else if (num_errors == 1) { uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 1) * c->chunk_item->stripe_length) + (bad_off << Vcb->sector_shift); if (RtlCheckBit(&context->is_tree, bad_off)) { tree_header* th; do_xor(&context->parity_scratch[i << Vcb->sector_shift], &context->stripes[bad_stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.node_size); th = (tree_header*)&context->parity_scratch[i << Vcb->sector_shift]; if (check_tree_checksum(Vcb, th) && th->address == addr) { RtlCopyMemory(&context->stripes[bad_stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.node_size); context->stripes[bad_stripe].rewrite = true; RtlClearBits(&context->stripes[bad_stripe].error, i + 1, (Vcb->superblock.node_size >> Vcb->sector_shift) - 1); log_error(Vcb, addr, c->devices[bad_stripe]->devitem.dev_id, true, true, false); } else log_error(Vcb, addr, c->devices[bad_stripe]->devitem.dev_id, true, false, false); } else { uint8_t hash[MAX_HASH_SIZE]; do_xor(&context->parity_scratch[i << Vcb->sector_shift], &context->stripes[bad_stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.sector_size); get_sector_csum(Vcb, &context->parity_scratch[i << Vcb->sector_shift], hash); if (RtlCompareMemory(hash, (uint8_t*)context->csum + (Vcb->csum_size * bad_off), Vcb->csum_size) == Vcb->csum_size) { RtlCopyMemory(&context->stripes[bad_stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.sector_size); context->stripes[bad_stripe].rewrite = true; log_error(Vcb, addr, c->devices[bad_stripe]->devitem.dev_id, false, true, false); } else log_error(Vcb, addr, c->devices[bad_stripe]->devitem.dev_id, false, false, false); } } else { stripe = (parity + 1) % c->chunk_item->num_stripes; off = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 1)) + i; while (stripe != parity) { if (RtlCheckBit(&context->alloc, off)) { if (RtlCheckBit(&context->stripes[stripe].error, i)) { uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 1) * c->chunk_item->stripe_length) + (off << Vcb->sector_shift); log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, RtlCheckBit(&context->is_tree, off), false, false); } } off += sectors_per_stripe; stripe = (stripe + 1) % c->chunk_item->num_stripes; } } } } static void scrub_raid6_stripe(device_extension* Vcb, chunk* c, scrub_context_raid56* context, uint64_t stripe_start, uint64_t bit_start, uint64_t num, uint16_t missing_devices) { ULONG sectors_per_stripe = (ULONG)(c->chunk_item->stripe_length >> Vcb->sector_shift), off; uint16_t stripe, parity1 = (bit_start + num + c->chunk_item->num_stripes - 2) % c->chunk_item->num_stripes; uint16_t parity2 = (parity1 + 1) % c->chunk_item->num_stripes; uint64_t stripeoff; stripe = (parity1 + 2) % c->chunk_item->num_stripes; off = (ULONG)(bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 2); stripeoff = num * sectors_per_stripe; if (c->devices[parity1]->devobj) RtlCopyMemory(context->parity_scratch, &context->stripes[parity1].buf[num * c->chunk_item->stripe_length], (ULONG)c->chunk_item->stripe_length); if (c->devices[parity2]->devobj) RtlZeroMemory(context->parity_scratch2, (ULONG)c->chunk_item->stripe_length); while (stripe != parity1) { RtlClearAllBits(&context->stripes[stripe].error); for (ULONG i = 0; i < sectors_per_stripe; i++) { if (c->devices[stripe]->devobj && RtlCheckBit(&context->alloc, off)) { if (RtlCheckBit(&context->is_tree, off)) { tree_header* th = (tree_header*)&context->stripes[stripe].buf[stripeoff << Vcb->sector_shift]; uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (off << Vcb->sector_shift); if (!check_tree_checksum(Vcb, th) || th->address != addr) { RtlSetBits(&context->stripes[stripe].error, i, Vcb->superblock.node_size >> Vcb->sector_shift); log_device_error(Vcb, c->devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); if (missing_devices == 2) log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, true, false, false); } off += Vcb->superblock.node_size >> Vcb->sector_shift; stripeoff += Vcb->superblock.node_size >> Vcb->sector_shift; i += (Vcb->superblock.node_size >> Vcb->sector_shift) - 1; continue; } else if (RtlCheckBit(&context->has_csum, off)) { uint8_t hash[MAX_HASH_SIZE]; get_sector_csum(Vcb, context->stripes[stripe].buf + (stripeoff << Vcb->sector_shift), hash); if (RtlCompareMemory(hash, (uint8_t*)context->csum + (Vcb->csum_size * off), Vcb->csum_size) != Vcb->csum_size) { uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (off << Vcb->sector_shift); RtlSetBit(&context->stripes[stripe].error, i); log_device_error(Vcb, c->devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS); if (missing_devices == 2) log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, false, false, false); } } } off++; stripeoff++; } if (c->devices[parity1]->devobj) do_xor(context->parity_scratch, &context->stripes[stripe].buf[num * c->chunk_item->stripe_length], (uint32_t)c->chunk_item->stripe_length); stripe = (stripe + 1) % c->chunk_item->num_stripes; stripeoff = num * sectors_per_stripe; } RtlClearAllBits(&context->stripes[parity1].error); if (missing_devices == 0 || (missing_devices == 1 && !c->devices[parity2]->devobj)) { // check parity 1 for (ULONG i = 0; i < sectors_per_stripe; i++) { ULONG o, j; o = i << Vcb->sector_shift; for (j = 0; j < Vcb->superblock.sector_size; j++) { // FIXME - use SSE if (context->parity_scratch[o] != 0) { RtlSetBit(&context->stripes[parity1].error, i); break; } o++; } } } RtlClearAllBits(&context->stripes[parity2].error); if (missing_devices == 0 || (missing_devices == 1 && !c->devices[parity1]->devobj)) { // check parity 2 stripe = parity1 == 0 ? (c->chunk_item->num_stripes - 1) : (parity1 - 1); while (stripe != parity2) { galois_double(context->parity_scratch2, (uint32_t)c->chunk_item->stripe_length); do_xor(context->parity_scratch2, &context->stripes[stripe].buf[num * c->chunk_item->stripe_length], (uint32_t)c->chunk_item->stripe_length); stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1); } for (ULONG i = 0; i < sectors_per_stripe; i++) { if (RtlCompareMemory(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->parity_scratch2[i << Vcb->sector_shift], Vcb->superblock.sector_size) != Vcb->superblock.sector_size) RtlSetBit(&context->stripes[parity2].error, i); } } if (missing_devices == 2) return; // log and fix errors for (ULONG i = 0; i < sectors_per_stripe; i++) { ULONG num_errors = 0; uint64_t bad_stripe1 = 0, bad_stripe2 = 0; ULONG bad_off1 = 0, bad_off2 = 0; bool alloc = false; stripe = (parity1 + 2) % c->chunk_item->num_stripes; off = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 2)) + i; while (stripe != parity1) { if (RtlCheckBit(&context->alloc, off)) { alloc = true; if (!c->devices[stripe]->devobj || RtlCheckBit(&context->stripes[stripe].error, i)) { if (num_errors == 0) { bad_stripe1 = stripe; bad_off1 = off; } else if (num_errors == 1) { bad_stripe2 = stripe; bad_off2 = off; } num_errors++; } } off += sectors_per_stripe; stripe = (stripe + 1) % c->chunk_item->num_stripes; } if (!alloc) continue; if (num_errors == 0 && !RtlCheckBit(&context->stripes[parity1].error, i) && !RtlCheckBit(&context->stripes[parity2].error, i)) // everything fine continue; if (num_errors == 0) { // parity error uint64_t addr; if (RtlCheckBit(&context->stripes[parity1].error, i)) { do_xor(&context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.sector_size); bad_off1 = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 2)) + i; addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (bad_off1 << Vcb->sector_shift); context->stripes[parity1].rewrite = true; log_error(Vcb, addr, c->devices[parity1]->devitem.dev_id, false, true, true); log_device_error(Vcb, c->devices[parity1], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } if (RtlCheckBit(&context->stripes[parity2].error, i)) { RtlCopyMemory(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->parity_scratch2[i << Vcb->sector_shift], Vcb->superblock.sector_size); bad_off1 = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 2)) + i; addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (bad_off1 << Vcb->sector_shift); context->stripes[parity2].rewrite = true; log_error(Vcb, addr, c->devices[parity2]->devitem.dev_id, false, true, true); log_device_error(Vcb, c->devices[parity2], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } else if (num_errors == 1) { uint32_t len; uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (bad_off1 << Vcb->sector_shift); uint8_t* scratch; len = RtlCheckBit(&context->is_tree, bad_off1) ? Vcb->superblock.node_size : Vcb->superblock.sector_size; scratch = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG); if (!scratch) { ERR("out of memory\n"); return; } RtlZeroMemory(scratch, len); do_xor(&context->parity_scratch[i << Vcb->sector_shift], &context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len); stripe = parity1 == 0 ? (c->chunk_item->num_stripes - 1) : (parity1 - 1); if (c->devices[parity2]->devobj) { uint16_t stripe_num, bad_stripe_num = 0; stripe_num = c->chunk_item->num_stripes - 3; while (stripe != parity2) { galois_double(scratch, len); if (stripe != bad_stripe1) do_xor(scratch, &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len); else bad_stripe_num = stripe_num; stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1); stripe_num--; } do_xor(scratch, &context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len); if (bad_stripe_num != 0) galois_divpower(scratch, (uint8_t)bad_stripe_num, len); } if (RtlCheckBit(&context->is_tree, bad_off1)) { uint8_t hash1[MAX_HASH_SIZE]; uint8_t hash2[MAX_HASH_SIZE]; tree_header *th1 = NULL, *th2 = NULL; if (c->devices[parity1]->devobj) { th1 = (tree_header*)&context->parity_scratch[i << Vcb->sector_shift]; get_tree_checksum(Vcb, th1, hash1); } if (c->devices[parity2]->devobj) { th2 = (tree_header*)scratch; get_tree_checksum(Vcb, th2, hash2); } if ((c->devices[parity1]->devobj && RtlCompareMemory(hash1, th1, Vcb->csum_size) == Vcb->csum_size && th1->address == addr) || (c->devices[parity2]->devobj && RtlCompareMemory(hash2, th2, Vcb->csum_size) == Vcb->csum_size && th2->address == addr)) { if (!c->devices[parity1]->devobj || RtlCompareMemory(hash1, th1, Vcb->csum_size) != Vcb->csum_size || th1->address != addr) { RtlCopyMemory(&context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], scratch, Vcb->superblock.node_size); if (c->devices[parity1]->devobj) { // fix parity 1 stripe = (parity1 + 2) % c->chunk_item->num_stripes; RtlCopyMemory(&context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.node_size); stripe = (stripe + 1) % c->chunk_item->num_stripes; while (stripe != parity1) { do_xor(&context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.node_size); stripe = (stripe + 1) % c->chunk_item->num_stripes; } context->stripes[parity1].rewrite = true; log_error(Vcb, addr, c->devices[parity1]->devitem.dev_id, false, true, true); log_device_error(Vcb, c->devices[parity1], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } else { RtlCopyMemory(&context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.node_size); if (!c->devices[parity2]->devobj || RtlCompareMemory(hash2, th2, Vcb->csum_size) != Vcb->csum_size || th2->address != addr) { // fix parity 2 stripe = parity1 == 0 ? (c->chunk_item->num_stripes - 1) : (parity1 - 1); if (c->devices[parity2]->devobj) { RtlCopyMemory(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.node_size); stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1); while (stripe != parity2) { galois_double(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.node_size); do_xor(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.node_size); stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1); } context->stripes[parity2].rewrite = true; log_error(Vcb, addr, c->devices[parity2]->devitem.dev_id, false, true, true); log_device_error(Vcb, c->devices[parity2], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } } context->stripes[bad_stripe1].rewrite = true; RtlClearBits(&context->stripes[bad_stripe1].error, i + 1, (Vcb->superblock.node_size >> Vcb->sector_shift) - 1); log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, true, true, false); } else log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, true, false, false); } else { uint8_t hash1[MAX_HASH_SIZE]; uint8_t hash2[MAX_HASH_SIZE]; if (c->devices[parity1]->devobj) get_sector_csum(Vcb, &context->parity_scratch[i << Vcb->sector_shift], hash1); if (c->devices[parity2]->devobj) get_sector_csum(Vcb, scratch, hash2); if ((c->devices[parity1]->devobj && RtlCompareMemory(hash1, (uint8_t*)context->csum + (bad_off1 * Vcb->csum_size), Vcb->csum_size) == Vcb->csum_size) || (c->devices[parity2]->devobj && RtlCompareMemory(hash2, (uint8_t*)context->csum + (bad_off1 * Vcb->csum_size), Vcb->csum_size) == Vcb->csum_size)) { if (c->devices[parity2]->devobj && RtlCompareMemory(hash2, (uint8_t*)context->csum + (bad_off1 * Vcb->csum_size), Vcb->csum_size) == Vcb->csum_size) { RtlCopyMemory(&context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], scratch, Vcb->superblock.sector_size); if (c->devices[parity1]->devobj && RtlCompareMemory(hash1, (uint8_t*)context->csum + (bad_off1 * Vcb->csum_size), Vcb->csum_size) != Vcb->csum_size) { // fix parity 1 stripe = (parity1 + 2) % c->chunk_item->num_stripes; RtlCopyMemory(&context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.sector_size); stripe = (stripe + 1) % c->chunk_item->num_stripes; while (stripe != parity1) { do_xor(&context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.sector_size); stripe = (stripe + 1) % c->chunk_item->num_stripes; } context->stripes[parity1].rewrite = true; log_error(Vcb, addr, c->devices[parity1]->devitem.dev_id, false, true, true); log_device_error(Vcb, c->devices[parity1], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } else { RtlCopyMemory(&context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.sector_size); if (c->devices[parity2]->devobj && RtlCompareMemory(hash2, (uint8_t*)context->csum + (bad_off1 * Vcb->csum_size), Vcb->csum_size) != Vcb->csum_size) { // fix parity 2 stripe = parity1 == 0 ? (c->chunk_item->num_stripes - 1) : (parity1 - 1); RtlCopyMemory(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.sector_size); stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1); while (stripe != parity2) { galois_double(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.sector_size); do_xor(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.sector_size); stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1); } context->stripes[parity2].rewrite = true; log_error(Vcb, addr, c->devices[parity2]->devitem.dev_id, false, true, true); log_device_error(Vcb, c->devices[parity2], BTRFS_DEV_STAT_CORRUPTION_ERRORS); } } context->stripes[bad_stripe1].rewrite = true; log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, false, true, false); } else log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, false, false, false); } ExFreePool(scratch); } else if (num_errors == 2 && missing_devices == 0) { uint16_t x = 0, y = 0, k; uint64_t addr; uint32_t len = (RtlCheckBit(&context->is_tree, bad_off1) || RtlCheckBit(&context->is_tree, bad_off2)) ? Vcb->superblock.node_size : Vcb->superblock.sector_size; uint8_t gyx, gx, denom, a, b, *p, *q, *pxy, *qxy; uint32_t j; stripe = parity1 == 0 ? (c->chunk_item->num_stripes - 1) : (parity1 - 1); // put qxy in parity_scratch // put pxy in parity_scratch2 k = c->chunk_item->num_stripes - 3; if (stripe == bad_stripe1 || stripe == bad_stripe2) { RtlZeroMemory(&context->parity_scratch[i << Vcb->sector_shift], len); RtlZeroMemory(&context->parity_scratch2[i << Vcb->sector_shift], len); if (stripe == bad_stripe1) x = k; else y = k; } else { RtlCopyMemory(&context->parity_scratch[i << Vcb->sector_shift], &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len); RtlCopyMemory(&context->parity_scratch2[i << Vcb->sector_shift], &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len); } stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1); k--; do { galois_double(&context->parity_scratch[i << Vcb->sector_shift], len); if (stripe != bad_stripe1 && stripe != bad_stripe2) { do_xor(&context->parity_scratch[i << Vcb->sector_shift], &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len); do_xor(&context->parity_scratch2[i << Vcb->sector_shift], &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len); } else if (stripe == bad_stripe1) x = k; else if (stripe == bad_stripe2) y = k; stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1); k--; } while (stripe != parity2); gyx = gpow2(y > x ? (y-x) : (255-x+y)); gx = gpow2(255-x); denom = gdiv(1, gyx ^ 1); a = gmul(gyx, denom); b = gmul(gx, denom); p = &context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)]; q = &context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)]; pxy = &context->parity_scratch2[i << Vcb->sector_shift]; qxy = &context->parity_scratch[i << Vcb->sector_shift]; for (j = 0; j < len; j++) { *qxy = gmul(a, *p ^ *pxy) ^ gmul(b, *q ^ *qxy); p++; q++; pxy++; qxy++; } do_xor(&context->parity_scratch2[i << Vcb->sector_shift], &context->parity_scratch[i << Vcb->sector_shift], len); do_xor(&context->parity_scratch2[i << Vcb->sector_shift], &context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len); addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (bad_off1 << Vcb->sector_shift); if (RtlCheckBit(&context->is_tree, bad_off1)) { tree_header* th = (tree_header*)&context->parity_scratch[i << Vcb->sector_shift]; if (check_tree_checksum(Vcb, th) && th->address == addr) { RtlCopyMemory(&context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.node_size); context->stripes[bad_stripe1].rewrite = true; RtlClearBits(&context->stripes[bad_stripe1].error, i + 1, (Vcb->superblock.node_size >> Vcb->sector_shift) - 1); log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, true, true, false); } else log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, true, false, false); } else { if (check_sector_csum(Vcb, &context->parity_scratch[i << Vcb->sector_shift], (uint8_t*)context->csum + (Vcb->csum_size * bad_off1))) { RtlCopyMemory(&context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.sector_size); context->stripes[bad_stripe1].rewrite = true; log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, false, true, false); } else log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, false, false, false); } addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (bad_off2 << Vcb->sector_shift); if (RtlCheckBit(&context->is_tree, bad_off2)) { tree_header* th = (tree_header*)&context->parity_scratch2[i << Vcb->sector_shift]; if (check_tree_checksum(Vcb, th) && th->address == addr) { RtlCopyMemory(&context->stripes[bad_stripe2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->parity_scratch2[i << Vcb->sector_shift], Vcb->superblock.node_size); context->stripes[bad_stripe2].rewrite = true; RtlClearBits(&context->stripes[bad_stripe2].error, i + 1, (Vcb->superblock.node_size >> Vcb->sector_shift) - 1); log_error(Vcb, addr, c->devices[bad_stripe2]->devitem.dev_id, true, true, false); } else log_error(Vcb, addr, c->devices[bad_stripe2]->devitem.dev_id, true, false, false); } else { if (check_sector_csum(Vcb, &context->parity_scratch2[i << Vcb->sector_shift], (uint8_t*)context->csum + (Vcb->csum_size * bad_off2))) { RtlCopyMemory(&context->stripes[bad_stripe2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], &context->parity_scratch2[i << Vcb->sector_shift], Vcb->superblock.sector_size); context->stripes[bad_stripe2].rewrite = true; log_error(Vcb, addr, c->devices[bad_stripe2]->devitem.dev_id, false, true, false); } else log_error(Vcb, addr, c->devices[bad_stripe2]->devitem.dev_id, false, false, false); } } else { stripe = (parity2 + 1) % c->chunk_item->num_stripes; off = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 2)) + i; while (stripe != parity1) { if (c->devices[stripe]->devobj && RtlCheckBit(&context->alloc, off)) { if (RtlCheckBit(&context->stripes[stripe].error, i)) { uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (off << Vcb->sector_shift); log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, RtlCheckBit(&context->is_tree, off), false, false); } } off += sectors_per_stripe; stripe = (stripe + 1) % c->chunk_item->num_stripes; } } } } static NTSTATUS scrub_chunk_raid56_stripe_run(device_extension* Vcb, chunk* c, uint64_t stripe_start, uint64_t stripe_end) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; bool b; uint64_t run_start, run_end, full_stripe_len, stripe; uint32_t max_read, num_sectors; ULONG arrlen, *allocarr, *csumarr = NULL, *treearr, num_parity_stripes = c->chunk_item->type & BLOCK_FLAG_RAID6 ? 2 : 1; scrub_context_raid56 context; uint16_t i; CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; TRACE("(%p, %p, %I64x, %I64x)\n", Vcb, c, stripe_start, stripe_end); full_stripe_len = (c->chunk_item->num_stripes - num_parity_stripes) * c->chunk_item->stripe_length; run_start = c->offset + (stripe_start * full_stripe_len); run_end = c->offset + ((stripe_end + 1) * full_stripe_len); searchkey.obj_id = run_start; searchkey.obj_type = TYPE_METADATA_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } num_sectors = (uint32_t)(((stripe_end - stripe_start + 1) * full_stripe_len) >> Vcb->sector_shift); arrlen = (ULONG)sector_align((num_sectors / 8) + 1, sizeof(ULONG)); allocarr = ExAllocatePoolWithTag(PagedPool, arrlen, ALLOC_TAG); if (!allocarr) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } treearr = ExAllocatePoolWithTag(PagedPool, arrlen, ALLOC_TAG); if (!treearr) { ERR("out of memory\n"); ExFreePool(allocarr); return STATUS_INSUFFICIENT_RESOURCES; } RtlInitializeBitMap(&context.alloc, allocarr, num_sectors); RtlClearAllBits(&context.alloc); RtlInitializeBitMap(&context.is_tree, treearr, num_sectors); RtlClearAllBits(&context.is_tree); context.parity_scratch = ExAllocatePoolWithTag(PagedPool, (ULONG)c->chunk_item->stripe_length, ALLOC_TAG); if (!context.parity_scratch) { ERR("out of memory\n"); ExFreePool(allocarr); ExFreePool(treearr); return STATUS_INSUFFICIENT_RESOURCES; } if (c->chunk_item->type & BLOCK_FLAG_DATA) { csumarr = ExAllocatePoolWithTag(PagedPool, arrlen, ALLOC_TAG); if (!csumarr) { ERR("out of memory\n"); ExFreePool(allocarr); ExFreePool(treearr); ExFreePool(context.parity_scratch); return STATUS_INSUFFICIENT_RESOURCES; } RtlInitializeBitMap(&context.has_csum, csumarr, num_sectors); RtlClearAllBits(&context.has_csum); context.csum = ExAllocatePoolWithTag(PagedPool, num_sectors * Vcb->csum_size, ALLOC_TAG); if (!context.csum) { ERR("out of memory\n"); ExFreePool(allocarr); ExFreePool(treearr); ExFreePool(context.parity_scratch); ExFreePool(csumarr); return STATUS_INSUFFICIENT_RESOURCES; } } if (c->chunk_item->type & BLOCK_FLAG_RAID6) { context.parity_scratch2 = ExAllocatePoolWithTag(PagedPool, (ULONG)c->chunk_item->stripe_length, ALLOC_TAG); if (!context.parity_scratch2) { ERR("out of memory\n"); ExFreePool(allocarr); ExFreePool(treearr); ExFreePool(context.parity_scratch); if (c->chunk_item->type & BLOCK_FLAG_DATA) { ExFreePool(csumarr); ExFreePool(context.csum); } return STATUS_INSUFFICIENT_RESOURCES; } } do { traverse_ptr next_tp; if (tp.item->key.obj_id >= run_end) break; if (tp.item->key.obj_type == TYPE_EXTENT_ITEM || tp.item->key.obj_type == TYPE_METADATA_ITEM) { uint64_t size = tp.item->key.obj_type == TYPE_METADATA_ITEM ? Vcb->superblock.node_size : tp.item->key.offset; if (tp.item->key.obj_id + size > run_start) { uint64_t extent_start = max(run_start, tp.item->key.obj_id); uint64_t extent_end = min(tp.item->key.obj_id + size, run_end); bool extent_is_tree = false; RtlSetBits(&context.alloc, (ULONG)((extent_start - run_start) >> Vcb->sector_shift), (ULONG)((extent_end - extent_start) >> Vcb->sector_shift)); if (tp.item->key.obj_type == TYPE_METADATA_ITEM) extent_is_tree = true; else { EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data; if (tp.item->size < sizeof(EXTENT_ITEM)) { 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)); Status = STATUS_INTERNAL_ERROR; goto end; } if (ei->flags & EXTENT_ITEM_TREE_BLOCK) extent_is_tree = true; } if (extent_is_tree) RtlSetBits(&context.is_tree, (ULONG)((extent_start - run_start) >> Vcb->sector_shift), (ULONG)((extent_end - extent_start) >> Vcb->sector_shift)); else if (c->chunk_item->type & BLOCK_FLAG_DATA) { traverse_ptr tp2; bool b2; searchkey.obj_id = EXTENT_CSUM_ID; searchkey.obj_type = TYPE_EXTENT_CSUM; searchkey.offset = extent_start; Status = find_item(Vcb, Vcb->checksum_root, &tp2, &searchkey, false, NULL); if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) { ERR("find_item returned %08lx\n", Status); goto end; } do { traverse_ptr next_tp2; if (tp2.item->key.offset >= extent_end) break; if (tp2.item->key.offset >= extent_start) { uint64_t csum_start = max(extent_start, tp2.item->key.offset); uint64_t csum_end = min(extent_end, tp2.item->key.offset + (((uint64_t)tp2.item->size << Vcb->sector_shift) / Vcb->csum_size)); RtlSetBits(&context.has_csum, (ULONG)((csum_start - run_start) >> Vcb->sector_shift), (ULONG)((csum_end - csum_start) >> Vcb->sector_shift)); RtlCopyMemory((uint8_t*)context.csum + (((csum_start - run_start) * Vcb->csum_size) >> Vcb->sector_shift), tp2.item->data + (((csum_start - tp2.item->key.offset) * Vcb->csum_size) >> Vcb->sector_shift), (ULONG)(((csum_end - csum_start) * Vcb->csum_size) >> Vcb->sector_shift)); } b2 = find_next_item(Vcb, &tp2, &next_tp2, false, NULL); if (b2) tp2 = next_tp2; } while (b2); } } } b = find_next_item(Vcb, &tp, &next_tp, false, NULL); if (b) tp = next_tp; } while (b); context.stripes = ExAllocatePoolWithTag(PagedPool, sizeof(scrub_context_raid56_stripe) * c->chunk_item->num_stripes, ALLOC_TAG); if (!context.stripes) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } 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 for (i = 0; i < c->chunk_item->num_stripes; i++) { context.stripes[i].buf = ExAllocatePoolWithTag(PagedPool, (ULONG)(max_read * c->chunk_item->stripe_length), ALLOC_TAG); if (!context.stripes[i].buf) { uint64_t j; ERR("out of memory\n"); for (j = 0; j < i; j++) { ExFreePool(context.stripes[j].buf); } ExFreePool(context.stripes); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } context.stripes[i].errorarr = ExAllocatePoolWithTag(PagedPool, (ULONG)sector_align(((c->chunk_item->stripe_length >> Vcb->sector_shift) / 8) + 1, sizeof(ULONG)), ALLOC_TAG); if (!context.stripes[i].errorarr) { uint64_t j; ERR("out of memory\n"); ExFreePool(context.stripes[i].buf); for (j = 0; j < i; j++) { ExFreePool(context.stripes[j].buf); } ExFreePool(context.stripes); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlInitializeBitMap(&context.stripes[i].error, context.stripes[i].errorarr, (ULONG)(c->chunk_item->stripe_length >> Vcb->sector_shift)); context.stripes[i].context = &context; context.stripes[i].rewrite = false; } stripe = stripe_start; Status = STATUS_SUCCESS; chunk_lock_range(Vcb, c, run_start, run_end - run_start); do { ULONG read_stripes; uint16_t missing_devices = 0; bool need_wait = false; if (max_read < stripe_end + 1 - stripe) read_stripes = max_read; else read_stripes = (ULONG)(stripe_end + 1 - stripe); context.stripes_left = c->chunk_item->num_stripes; // read megabyte by megabyte for (i = 0; i < c->chunk_item->num_stripes; i++) { if (c->devices[i]->devobj) { PIO_STACK_LOCATION IrpSp; context.stripes[i].Irp = IoAllocateIrp(c->devices[i]->devobj->StackSize, false); if (!context.stripes[i].Irp) { ERR("IoAllocateIrp failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end3; } context.stripes[i].Irp->MdlAddress = NULL; IrpSp = IoGetNextIrpStackLocation(context.stripes[i].Irp); IrpSp->MajorFunction = IRP_MJ_READ; IrpSp->FileObject = c->devices[i]->fileobj; if (c->devices[i]->devobj->Flags & DO_BUFFERED_IO) { context.stripes[i].Irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPool, (ULONG)(read_stripes * c->chunk_item->stripe_length), ALLOC_TAG); if (!context.stripes[i].Irp->AssociatedIrp.SystemBuffer) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end3; } context.stripes[i].Irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION; context.stripes[i].Irp->UserBuffer = context.stripes[i].buf; } else if (c->devices[i]->devobj->Flags & DO_DIRECT_IO) { context.stripes[i].Irp->MdlAddress = IoAllocateMdl(context.stripes[i].buf, (ULONG)(read_stripes * c->chunk_item->stripe_length), false, false, NULL); if (!context.stripes[i].Irp->MdlAddress) { ERR("IoAllocateMdl failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end3; } Status = STATUS_SUCCESS; try { MmProbeAndLockPages(context.stripes[i].Irp->MdlAddress, KernelMode, IoWriteAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); IoFreeMdl(context.stripes[i].Irp->MdlAddress); goto end3; } } else context.stripes[i].Irp->UserBuffer = context.stripes[i].buf; context.stripes[i].offset = stripe * c->chunk_item->stripe_length; IrpSp->Parameters.Read.Length = (ULONG)(read_stripes * c->chunk_item->stripe_length); IrpSp->Parameters.Read.ByteOffset.QuadPart = cis[i].offset + context.stripes[i].offset; context.stripes[i].Irp->UserIosb = &context.stripes[i].iosb; context.stripes[i].missing = false; IoSetCompletionRoutine(context.stripes[i].Irp, scrub_read_completion_raid56, &context.stripes[i], true, true, true); Vcb->scrub.data_scrubbed += read_stripes * c->chunk_item->stripe_length; need_wait = true; } else { context.stripes[i].Irp = NULL; context.stripes[i].missing = true; missing_devices++; InterlockedDecrement(&context.stripes_left); } } if (c->chunk_item->type & BLOCK_FLAG_RAID5 && missing_devices > 1) { ERR("too many missing devices (%u, maximum 1)\n", missing_devices); Status = STATUS_UNEXPECTED_IO_ERROR; goto end3; } else if (c->chunk_item->type & BLOCK_FLAG_RAID6 && missing_devices > 2) { ERR("too many missing devices (%u, maximum 2)\n", missing_devices); Status = STATUS_UNEXPECTED_IO_ERROR; goto end3; } if (need_wait) { KeInitializeEvent(&context.Event, NotificationEvent, false); for (i = 0; i < c->chunk_item->num_stripes; i++) { if (c->devices[i]->devobj) IoCallDriver(c->devices[i]->devobj, context.stripes[i].Irp); } KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL); } // return an error if any of the stripes returned an error for (i = 0; i < c->chunk_item->num_stripes; i++) { if (!context.stripes[i].missing && !NT_SUCCESS(context.stripes[i].iosb.Status)) { Status = context.stripes[i].iosb.Status; log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_READ_ERRORS); goto end3; } } if (c->chunk_item->type & BLOCK_FLAG_RAID6) { for (i = 0; i < read_stripes; i++) { scrub_raid6_stripe(Vcb, c, &context, stripe_start, stripe, i, missing_devices); } } else { for (i = 0; i < read_stripes; i++) { scrub_raid5_stripe(Vcb, c, &context, stripe_start, stripe, i, missing_devices); } } stripe += read_stripes; end3: for (i = 0; i < c->chunk_item->num_stripes; i++) { if (context.stripes[i].Irp) { if (c->devices[i]->devobj->Flags & DO_DIRECT_IO && context.stripes[i].Irp->MdlAddress) { MmUnlockPages(context.stripes[i].Irp->MdlAddress); IoFreeMdl(context.stripes[i].Irp->MdlAddress); } IoFreeIrp(context.stripes[i].Irp); context.stripes[i].Irp = NULL; if (context.stripes[i].rewrite) { Status = write_data_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + context.stripes[i].offset, context.stripes[i].buf, (uint32_t)(read_stripes * c->chunk_item->stripe_length)); if (!NT_SUCCESS(Status)) { ERR("write_data_phys returned %08lx\n", Status); log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_WRITE_ERRORS); goto end2; } } } } if (!NT_SUCCESS(Status)) break; } while (stripe < stripe_end); end2: chunk_unlock_range(Vcb, c, run_start, run_end - run_start); for (i = 0; i < c->chunk_item->num_stripes; i++) { ExFreePool(context.stripes[i].buf); ExFreePool(context.stripes[i].errorarr); } ExFreePool(context.stripes); end: ExFreePool(treearr); ExFreePool(allocarr); ExFreePool(context.parity_scratch); if (c->chunk_item->type & BLOCK_FLAG_RAID6) ExFreePool(context.parity_scratch2); if (c->chunk_item->type & BLOCK_FLAG_DATA) { ExFreePool(csumarr); ExFreePool(context.csum); } return Status; } static NTSTATUS scrub_chunk_raid56(device_extension* Vcb, chunk* c, uint64_t* offset, bool* changed) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; bool b; uint64_t full_stripe_len, stripe, stripe_start = 0, stripe_end = 0, total_data = 0; ULONG num_extents = 0, num_parity_stripes = c->chunk_item->type & BLOCK_FLAG_RAID6 ? 2 : 1; full_stripe_len = (c->chunk_item->num_stripes - num_parity_stripes) * c->chunk_item->stripe_length; stripe = (*offset - c->offset) / full_stripe_len; *offset = c->offset + (stripe * full_stripe_len); searchkey.obj_id = *offset; searchkey.obj_type = TYPE_METADATA_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } *changed = false; do { traverse_ptr next_tp; if (tp.item->key.obj_id >= c->offset + c->chunk_item->size) break; if (tp.item->key.obj_id >= *offset && (tp.item->key.obj_type == TYPE_EXTENT_ITEM || tp.item->key.obj_type == TYPE_METADATA_ITEM)) { uint64_t size = tp.item->key.obj_type == TYPE_METADATA_ITEM ? Vcb->superblock.node_size : tp.item->key.offset; TRACE("%I64x\n", tp.item->key.obj_id); if (size < Vcb->superblock.sector_size) { ERR("extent %I64x has size less than sector_size (%I64x < %x)\n", tp.item->key.obj_id, size, Vcb->superblock.sector_size); return STATUS_INTERNAL_ERROR; } stripe = (tp.item->key.obj_id - c->offset) / full_stripe_len; if (*changed) { if (stripe > stripe_end + 1) { Status = scrub_chunk_raid56_stripe_run(Vcb, c, stripe_start, stripe_end); if (!NT_SUCCESS(Status)) { ERR("scrub_chunk_raid56_stripe_run returned %08lx\n", Status); return Status; } stripe_start = stripe; } } else stripe_start = stripe; stripe_end = (tp.item->key.obj_id + size - 1 - c->offset) / full_stripe_len; *changed = true; total_data += size; num_extents++; // only do so much at a time if (num_extents >= 64 || total_data >= 0x8000000) // 128 MB break; } b = find_next_item(Vcb, &tp, &next_tp, false, NULL); if (b) tp = next_tp; } while (b); if (*changed) { Status = scrub_chunk_raid56_stripe_run(Vcb, c, stripe_start, stripe_end); if (!NT_SUCCESS(Status)) { ERR("scrub_chunk_raid56_stripe_run returned %08lx\n", Status); return Status; } *offset = c->offset + ((stripe_end + 1) * full_stripe_len); } return STATUS_SUCCESS; } static NTSTATUS scrub_chunk(device_extension* Vcb, chunk* c, uint64_t* offset, bool* changed) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; bool b = false, tree_run = false; ULONG type, num_extents = 0; uint64_t total_data = 0, tree_run_start = 0, tree_run_end = 0; TRACE("chunk %I64x\n", c->offset); ExAcquireResourceSharedLite(&Vcb->tree_lock, true); if (c->chunk_item->type & BLOCK_FLAG_DUPLICATE) type = BLOCK_FLAG_DUPLICATE; else if (c->chunk_item->type & BLOCK_FLAG_RAID0) type = BLOCK_FLAG_RAID0; else if (c->chunk_item->type & BLOCK_FLAG_RAID1) type = BLOCK_FLAG_DUPLICATE; else if (c->chunk_item->type & BLOCK_FLAG_RAID10) type = BLOCK_FLAG_RAID10; else if (c->chunk_item->type & BLOCK_FLAG_RAID5) { Status = scrub_chunk_raid56(Vcb, c, offset, changed); goto end; } else if (c->chunk_item->type & BLOCK_FLAG_RAID6) { Status = scrub_chunk_raid56(Vcb, c, offset, changed); goto end; } else if (c->chunk_item->type & BLOCK_FLAG_RAID1C3) type = BLOCK_FLAG_DUPLICATE; else if (c->chunk_item->type & BLOCK_FLAG_RAID1C4) type = BLOCK_FLAG_DUPLICATE; else // SINGLE type = BLOCK_FLAG_DUPLICATE; searchkey.obj_id = *offset; searchkey.obj_type = TYPE_METADATA_ITEM; searchkey.offset = 0xffffffffffffffff; Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("error - find_item returned %08lx\n", Status); goto end; } do { traverse_ptr next_tp; if (tp.item->key.obj_id >= c->offset + c->chunk_item->size) break; if (tp.item->key.obj_id >= *offset && (tp.item->key.obj_type == TYPE_EXTENT_ITEM || tp.item->key.obj_type == TYPE_METADATA_ITEM)) { uint64_t size = tp.item->key.obj_type == TYPE_METADATA_ITEM ? Vcb->superblock.node_size : tp.item->key.offset; bool is_tree; void* csum = NULL; RTL_BITMAP bmp; ULONG* bmparr = NULL, bmplen; TRACE("%I64x\n", tp.item->key.obj_id); is_tree = false; if (tp.item->key.obj_type == TYPE_METADATA_ITEM) is_tree = true; else { EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data; if (tp.item->size < sizeof(EXTENT_ITEM)) { 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)); Status = STATUS_INTERNAL_ERROR; goto end; } if (ei->flags & EXTENT_ITEM_TREE_BLOCK) is_tree = true; } if (size < Vcb->superblock.sector_size) { ERR("extent %I64x has size less than sector_size (%I64x < %x)\n", tp.item->key.obj_id, size, Vcb->superblock.sector_size); Status = STATUS_INTERNAL_ERROR; goto end; } // load csum if (!is_tree) { traverse_ptr tp2; csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((Vcb->csum_size * size) >> Vcb->sector_shift), ALLOC_TAG); if (!csum) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } bmplen = (ULONG)(size >> Vcb->sector_shift); bmparr = ExAllocatePoolWithTag(PagedPool, (ULONG)(sector_align((bmplen >> 3) + 1, sizeof(ULONG))), ALLOC_TAG); if (!bmparr) { ERR("out of memory\n"); ExFreePool(csum); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlInitializeBitMap(&bmp, bmparr, bmplen); RtlSetAllBits(&bmp); // 1 = no csum, 0 = csum searchkey.obj_id = EXTENT_CSUM_ID; searchkey.obj_type = TYPE_EXTENT_CSUM; searchkey.offset = tp.item->key.obj_id; Status = find_item(Vcb, Vcb->checksum_root, &tp2, &searchkey, false, NULL); if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) { ERR("find_item returned %08lx\n", Status); ExFreePool(csum); ExFreePool(bmparr); goto end; } if (Status != STATUS_NOT_FOUND) { do { traverse_ptr next_tp2; if (tp2.item->key.obj_type == TYPE_EXTENT_CSUM) { if (tp2.item->key.offset >= tp.item->key.obj_id + size) break; 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) { uint64_t cs = max(tp.item->key.obj_id, tp2.item->key.offset); 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)); RtlCopyMemory((uint8_t*)csum + (((cs - tp.item->key.obj_id) * Vcb->csum_size) >> Vcb->sector_shift), tp2.item->data + (((cs - tp2.item->key.offset) * Vcb->csum_size) >> Vcb->sector_shift), (ULONG)(((ce - cs) * Vcb->csum_size) >> Vcb->sector_shift)); RtlClearBits(&bmp, (ULONG)((cs - tp.item->key.obj_id) >> Vcb->sector_shift), (ULONG)((ce - cs) >> Vcb->sector_shift)); if (ce == tp.item->key.obj_id + size) break; } } if (find_next_item(Vcb, &tp2, &next_tp2, false, NULL)) tp2 = next_tp2; else break; } while (true); } } if (tree_run) { if (!is_tree || tp.item->key.obj_id > tree_run_end) { Status = scrub_extent(Vcb, c, type, tree_run_start, (uint32_t)(tree_run_end - tree_run_start), NULL); if (!NT_SUCCESS(Status)) { ERR("scrub_extent returned %08lx\n", Status); goto end; } if (!is_tree) tree_run = false; else { tree_run_start = tp.item->key.obj_id; tree_run_end = tp.item->key.obj_id + Vcb->superblock.node_size; } } else tree_run_end = tp.item->key.obj_id + Vcb->superblock.node_size; } else if (is_tree) { tree_run = true; tree_run_start = tp.item->key.obj_id; tree_run_end = tp.item->key.obj_id + Vcb->superblock.node_size; } if (!is_tree) { Status = scrub_data_extent(Vcb, c, tp.item->key.obj_id, type, csum, &bmp, bmplen); if (!NT_SUCCESS(Status)) { ERR("scrub_data_extent returned %08lx\n", Status); ExFreePool(csum); ExFreePool(bmparr); goto end; } ExFreePool(csum); ExFreePool(bmparr); } *offset = tp.item->key.obj_id + size; *changed = true; total_data += size; num_extents++; // only do so much at a time if (num_extents >= 64 || total_data >= 0x8000000) // 128 MB break; } b = find_next_item(Vcb, &tp, &next_tp, false, NULL); if (b) tp = next_tp; } while (b); if (tree_run) { Status = scrub_extent(Vcb, c, type, tree_run_start, (uint32_t)(tree_run_end - tree_run_start), NULL); if (!NT_SUCCESS(Status)) { ERR("scrub_extent returned %08lx\n", Status); goto end; } } Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&Vcb->tree_lock); return Status; } _Function_class_(KSTART_ROUTINE) static void __stdcall scrub_thread(void* context) { device_extension* Vcb = context; LIST_ENTRY chunks, *le; NTSTATUS Status; LARGE_INTEGER time; KeInitializeEvent(&Vcb->scrub.finished, NotificationEvent, false); InitializeListHead(&chunks); ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); if (Vcb->need_write && !Vcb->readonly) Status = do_write(Vcb, NULL); else Status = STATUS_SUCCESS; free_trees(Vcb); if (!NT_SUCCESS(Status)) { ExReleaseResourceLite(&Vcb->tree_lock); ERR("do_write returned %08lx\n", Status); Vcb->scrub.error = Status; goto end; } ExConvertExclusiveToSharedLite(&Vcb->tree_lock); ExAcquireResourceExclusiveLite(&Vcb->scrub.stats_lock, true); KeQuerySystemTime(&Vcb->scrub.start_time); Vcb->scrub.finish_time.QuadPart = 0; Vcb->scrub.resume_time.QuadPart = Vcb->scrub.start_time.QuadPart; Vcb->scrub.duration.QuadPart = 0; Vcb->scrub.total_chunks = 0; Vcb->scrub.chunks_left = 0; Vcb->scrub.data_scrubbed = 0; Vcb->scrub.num_errors = 0; while (!IsListEmpty(&Vcb->scrub.errors)) { scrub_error* err = CONTAINING_RECORD(RemoveHeadList(&Vcb->scrub.errors), scrub_error, list_entry); ExFreePool(err); } ExAcquireResourceSharedLite(&Vcb->chunk_lock, true); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry); acquire_chunk_lock(c, Vcb); if (!c->readonly) { InsertTailList(&chunks, &c->list_entry_balance); Vcb->scrub.total_chunks++; Vcb->scrub.chunks_left++; } release_chunk_lock(c, Vcb); le = le->Flink; } ExReleaseResourceLite(&Vcb->chunk_lock); ExReleaseResource(&Vcb->scrub.stats_lock); ExReleaseResourceLite(&Vcb->tree_lock); while (!IsListEmpty(&chunks)) { chunk* c = CONTAINING_RECORD(RemoveHeadList(&chunks), chunk, list_entry_balance); uint64_t offset = c->offset; bool changed; c->reloc = true; KeWaitForSingleObject(&Vcb->scrub.event, Executive, KernelMode, false, NULL); if (!Vcb->scrub.stopping) { do { changed = false; Status = scrub_chunk(Vcb, c, &offset, &changed); if (!NT_SUCCESS(Status)) { ERR("scrub_chunk returned %08lx\n", Status); Vcb->scrub.stopping = true; Vcb->scrub.error = Status; break; } if (offset == c->offset + c->chunk_item->size || Vcb->scrub.stopping) break; KeWaitForSingleObject(&Vcb->scrub.event, Executive, KernelMode, false, NULL); } while (changed); } ExAcquireResourceExclusiveLite(&Vcb->scrub.stats_lock, true); if (!Vcb->scrub.stopping) Vcb->scrub.chunks_left--; if (IsListEmpty(&chunks)) KeQuerySystemTime(&Vcb->scrub.finish_time); ExReleaseResource(&Vcb->scrub.stats_lock); c->reloc = false; c->list_entry_balance.Flink = NULL; } KeQuerySystemTime(&time); Vcb->scrub.duration.QuadPart += time.QuadPart - Vcb->scrub.resume_time.QuadPart; end: ZwClose(Vcb->scrub.thread); Vcb->scrub.thread = NULL; KeSetEvent(&Vcb->scrub.finished, 0, false); } NTSTATUS start_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode) { NTSTATUS Status; OBJECT_ATTRIBUTES oa; if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; if (Vcb->locked) { WARN("cannot start scrub while locked\n"); return STATUS_DEVICE_NOT_READY; } if (Vcb->balance.thread) { WARN("cannot start scrub while balance running\n"); return STATUS_DEVICE_NOT_READY; } if (Vcb->scrub.thread) { WARN("scrub already running\n"); return STATUS_DEVICE_NOT_READY; } if (Vcb->readonly) return STATUS_MEDIA_WRITE_PROTECTED; Vcb->scrub.stopping = false; Vcb->scrub.paused = false; Vcb->scrub.error = STATUS_SUCCESS; KeInitializeEvent(&Vcb->scrub.event, NotificationEvent, !Vcb->scrub.paused); InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); Status = PsCreateSystemThread(&Vcb->scrub.thread, 0, &oa, NULL, NULL, scrub_thread, Vcb); if (!NT_SUCCESS(Status)) { ERR("PsCreateSystemThread returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } NTSTATUS query_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode, void* data, ULONG length) { btrfs_query_scrub* bqs = (btrfs_query_scrub*)data; ULONG len; NTSTATUS Status; LIST_ENTRY* le; btrfs_scrub_error* bse = NULL; if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; if (length < offsetof(btrfs_query_scrub, errors)) return STATUS_BUFFER_TOO_SMALL; ExAcquireResourceSharedLite(&Vcb->scrub.stats_lock, true); if (Vcb->scrub.thread && Vcb->scrub.chunks_left > 0) bqs->status = Vcb->scrub.paused ? BTRFS_SCRUB_PAUSED : BTRFS_SCRUB_RUNNING; else bqs->status = BTRFS_SCRUB_STOPPED; bqs->start_time.QuadPart = Vcb->scrub.start_time.QuadPart; bqs->finish_time.QuadPart = Vcb->scrub.finish_time.QuadPart; bqs->chunks_left = Vcb->scrub.chunks_left; bqs->total_chunks = Vcb->scrub.total_chunks; bqs->data_scrubbed = Vcb->scrub.data_scrubbed; bqs->duration = Vcb->scrub.duration.QuadPart; if (bqs->status == BTRFS_SCRUB_RUNNING) { LARGE_INTEGER time; KeQuerySystemTime(&time); bqs->duration += time.QuadPart - Vcb->scrub.resume_time.QuadPart; } bqs->error = Vcb->scrub.error; bqs->num_errors = Vcb->scrub.num_errors; len = length - offsetof(btrfs_query_scrub, errors); le = Vcb->scrub.errors.Flink; while (le != &Vcb->scrub.errors) { scrub_error* err = CONTAINING_RECORD(le, scrub_error, list_entry); ULONG errlen; if (err->is_metadata) errlen = offsetof(btrfs_scrub_error, metadata.firstitem) + sizeof(KEY); else errlen = offsetof(btrfs_scrub_error, data.filename) + err->data.filename_length; if (len < errlen) { Status = STATUS_BUFFER_OVERFLOW; goto end; } if (!bse) bse = &bqs->errors; else { ULONG lastlen; if (bse->is_metadata) lastlen = offsetof(btrfs_scrub_error, metadata.firstitem) + sizeof(KEY); else lastlen = offsetof(btrfs_scrub_error, data.filename) + bse->data.filename_length; bse->next_entry = lastlen; bse = (btrfs_scrub_error*)(((uint8_t*)bse) + lastlen); } bse->next_entry = 0; bse->address = err->address; bse->device = err->device; bse->recovered = err->recovered; bse->is_metadata = err->is_metadata; bse->parity = err->parity; if (err->is_metadata) { bse->metadata.root = err->metadata.root; bse->metadata.level = err->metadata.level; bse->metadata.firstitem = err->metadata.firstitem; } else { bse->data.subvol = err->data.subvol; bse->data.offset = err->data.offset; bse->data.filename_length = err->data.filename_length; RtlCopyMemory(bse->data.filename, err->data.filename, err->data.filename_length); } len -= errlen; le = le->Flink; } Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&Vcb->scrub.stats_lock); return Status; } NTSTATUS pause_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode) { LARGE_INTEGER time; if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; if (!Vcb->scrub.thread) return STATUS_DEVICE_NOT_READY; if (Vcb->scrub.paused) return STATUS_DEVICE_NOT_READY; Vcb->scrub.paused = true; KeClearEvent(&Vcb->scrub.event); KeQuerySystemTime(&time); Vcb->scrub.duration.QuadPart += time.QuadPart - Vcb->scrub.resume_time.QuadPart; return STATUS_SUCCESS; } NTSTATUS resume_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode) { if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; if (!Vcb->scrub.thread) return STATUS_DEVICE_NOT_READY; if (!Vcb->scrub.paused) return STATUS_DEVICE_NOT_READY; Vcb->scrub.paused = false; KeSetEvent(&Vcb->scrub.event, 0, false); KeQuerySystemTime(&Vcb->scrub.resume_time); return STATUS_SUCCESS; } NTSTATUS stop_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode) { if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; if (!Vcb->scrub.thread) return STATUS_DEVICE_NOT_READY; Vcb->scrub.paused = false; Vcb->scrub.stopping = true; KeSetEvent(&Vcb->scrub.event, 0, false); return STATUS_SUCCESS; } ================================================ FILE: src/search.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include #include #include #include #include #include #include #include #include #include extern ERESOURCE pdo_list_lock; extern LIST_ENTRY pdo_list; extern UNICODE_STRING registry_path; extern KEVENT mountmgr_thread_event; extern HANDLE mountmgr_thread_handle; extern bool shutting_down; extern PDEVICE_OBJECT busobj; extern tIoUnregisterPlugPlayNotificationEx fIoUnregisterPlugPlayNotificationEx; extern ERESOURCE boot_lock; extern PDRIVER_OBJECT drvobj; typedef void (*pnp_callback)(PUNICODE_STRING devpath); // not in mingw yet #ifndef _MSC_VER DEFINE_GUID(GUID_IO_VOLUME_FVE_STATUS_CHANGE, 0x062998b2, 0xee1f, 0x4b6a, 0xb8, 0x57, 0xe7, 0x6c, 0xbb, 0xe9, 0xa6, 0xda); #endif extern PDEVICE_OBJECT master_devobj; typedef struct { LIST_ENTRY list_entry; PDEVICE_OBJECT devobj; void* notification_entry; UNICODE_STRING devpath; WCHAR buf[1]; } fve_data; static LIST_ENTRY fve_data_list = { &fve_data_list, &fve_data_list }; KSPIN_LOCK fve_data_lock; static bool fs_ignored(BTRFS_UUID* uuid) { UNICODE_STRING path, ignoreus; NTSTATUS Status; OBJECT_ATTRIBUTES oa; KEY_VALUE_FULL_INFORMATION* kvfi; ULONG dispos, retlen, kvfilen, i, j; HANDLE h; bool ret = false; path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR)); path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG); if (!path.Buffer) { ERR("out of memory\n"); return false; } RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length); i = registry_path.Length / sizeof(WCHAR); path.Buffer[i] = '\\'; i++; for (j = 0; j < 16; j++) { path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4); path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF); i += 2; if (j == 3 || j == 5 || j == 7 || j == 9) { path.Buffer[i] = '-'; i++; } } InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateKey(&h, KEY_QUERY_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos); if (!NT_SUCCESS(Status)) { TRACE("ZwCreateKey returned %08lx\n", Status); ExFreePool(path.Buffer); return false; } RtlInitUnicodeString(&ignoreus, L"Ignore"); kvfilen = (ULONG)offsetof(KEY_VALUE_FULL_INFORMATION, Name[0]) + (255 * sizeof(WCHAR)); kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); ZwClose(h); ExFreePool(path.Buffer); return false; } Status = ZwQueryValueKey(h, &ignoreus, KeyValueFullInformation, kvfi, kvfilen, &retlen); if (NT_SUCCESS(Status)) { if (kvfi->Type == REG_DWORD && kvfi->DataLength >= sizeof(uint32_t)) { uint32_t* pr = (uint32_t*)((uint8_t*)kvfi + kvfi->DataOffset); ret = *pr; } } ZwClose(h); ExFreePool(kvfi); ExFreePool(path.Buffer); return ret; } typedef struct { PIO_WORKITEM work_item; PFILE_OBJECT fileobj; PDEVICE_OBJECT devobj; UNICODE_STRING devpath; WCHAR buf[1]; } fve_callback_context; _Function_class_(IO_WORKITEM_ROUTINE) static void __stdcall fve_callback(PDEVICE_OBJECT DeviceObject, PVOID con) { fve_callback_context* ctx = con; UNUSED(DeviceObject); if (volume_arrival(&ctx->devpath, true)) { KIRQL irql; LIST_ENTRY* le; fve_data* d = NULL; // volume no longer locked - unregister notification KeAcquireSpinLock(&fve_data_lock, &irql); le = fve_data_list.Flink; while (le != &fve_data_list) { fve_data* d2 = CONTAINING_RECORD(le, fve_data, list_entry); if (d2->devobj == ctx->devobj) { RemoveEntryList(&d2->list_entry); d = d2; break; } le = le->Flink; } KeReleaseSpinLock(&fve_data_lock, irql); if (d) { IoUnregisterPlugPlayNotification(d->notification_entry); ExFreePool(d); } } IoFreeWorkItem(ctx->work_item); ExFreePool(ctx); } static NTSTATUS __stdcall event_notification(PVOID NotificationStructure, PVOID Context) { TARGET_DEVICE_REMOVAL_NOTIFICATION* tdrn = NotificationStructure; PDEVICE_OBJECT devobj = Context; PIO_WORKITEM work_item; fve_callback_context* ctx; LIST_ENTRY* le; KIRQL irql; if (RtlCompareMemory(&tdrn->Event, &GUID_IO_VOLUME_FVE_STATUS_CHANGE, sizeof(GUID)) != sizeof(GUID)) return STATUS_SUCCESS; /* The FVE event has trailing data, presumably telling us whether the volume has * been unlocked or whatever, but unfortunately it's undocumented! */ work_item = IoAllocateWorkItem(master_devobj); if (!work_item) { ERR("out of memory\n"); return STATUS_SUCCESS; } KeAcquireSpinLock(&fve_data_lock, &irql); le = fve_data_list.Flink; while (le != &fve_data_list) { fve_data* d = CONTAINING_RECORD(le, fve_data, list_entry); if (d->devobj == devobj) { ctx = ExAllocatePoolWithTag(NonPagedPool, offsetof(fve_callback_context, buf) + d->devpath.Length, ALLOC_TAG); if (!ctx) { KeReleaseSpinLock(&fve_data_lock, irql); ERR("out of memory\n"); IoFreeWorkItem(work_item); return STATUS_SUCCESS; } RtlCopyMemory(ctx->buf, d->devpath.Buffer, d->devpath.Length); ctx->devpath.Length = ctx->devpath.MaximumLength = d->devpath.Length; KeReleaseSpinLock(&fve_data_lock, irql); ctx->devpath.Buffer = ctx->buf; ctx->fileobj = tdrn->FileObject; ctx->devobj = devobj; ctx->work_item = work_item; IoQueueWorkItem(work_item, fve_callback, DelayedWorkQueue, ctx); return STATUS_SUCCESS; } le = le->Flink; } KeReleaseSpinLock(&fve_data_lock, irql); IoFreeWorkItem(work_item); return STATUS_SUCCESS; } static void register_fve_callback(PDEVICE_OBJECT devobj, PFILE_OBJECT fileobj, PUNICODE_STRING devpath) { NTSTATUS Status; KIRQL irql; LIST_ENTRY* le; fve_data* d = ExAllocatePoolWithTag(NonPagedPool, offsetof(fve_data, buf) + devpath->Length, ALLOC_TAG); if (!d) { ERR("out of memory\n"); return; } d->devpath.Buffer = d->buf; d->devpath.Length = d->devpath.MaximumLength = devpath->Length; RtlCopyMemory(d->devpath.Buffer, devpath->Buffer, devpath->Length); KeAcquireSpinLock(&fve_data_lock, &irql); le = fve_data_list.Flink; while (le != &fve_data_list) { fve_data* d2 = CONTAINING_RECORD(le, fve_data, list_entry); if (d2->devobj == devobj) { KeReleaseSpinLock(&fve_data_lock, irql); ExFreePool(d); return; } le = le->Flink; } KeReleaseSpinLock(&fve_data_lock, irql); Status = IoRegisterPlugPlayNotification(EventCategoryTargetDeviceChange, 0, fileobj, drvobj, event_notification, devobj, &d->notification_entry); if (!NT_SUCCESS(Status)) { ERR("IoRegisterPlugPlayNotification returned %08lx\n", Status); return; } KeAcquireSpinLock(&fve_data_lock, &irql); le = fve_data_list.Flink; while (le != &fve_data_list) { fve_data* d2 = CONTAINING_RECORD(le, fve_data, list_entry); if (d2->devobj == devobj) { KeReleaseSpinLock(&fve_data_lock, irql); IoUnregisterPlugPlayNotification(d->notification_entry); ExFreePool(d); return; } le = le->Flink; } d->devobj = devobj; InsertTailList(&fve_data_list, &d->list_entry); KeReleaseSpinLock(&fve_data_lock, irql); } static bool test_vol(PDEVICE_OBJECT DeviceObject, PFILE_OBJECT FileObject, PUNICODE_STRING devpath, DWORD disk_num, DWORD part_num, uint64_t length, bool fve_callback) { NTSTATUS Status; ULONG toread; uint8_t* data = NULL; uint32_t sector_size; bool ret = true; TRACE("%.*S\n", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer); sector_size = DeviceObject->SectorSize; if (sector_size == 0) { DISK_GEOMETRY geometry; IO_STATUS_BLOCK iosb; Status = dev_ioctl(DeviceObject, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &geometry, sizeof(DISK_GEOMETRY), true, &iosb); if (!NT_SUCCESS(Status)) { ERR("%.*S had a sector size of 0, and IOCTL_DISK_GET_DRIVE_GEOMETRY returned %08lx\n", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer, Status); goto deref; } if (iosb.Information < sizeof(DISK_GEOMETRY)) { ERR("%.*S: IOCTL_DISK_GET_DRIVE_GEOMETRY returned %Iu bytes, expected %Iu\n", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer, iosb.Information, sizeof(DISK_GEOMETRY)); } sector_size = geometry.BytesPerSector; if (sector_size == 0) { ERR("%.*S had a sector size of 0\n", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer); goto deref; } } toread = (ULONG)sector_align(sizeof(superblock), sector_size); data = ExAllocatePoolWithTag(NonPagedPool, toread, ALLOC_TAG); if (!data) { ERR("out of memory\n"); goto deref; } Status = sync_read_phys(DeviceObject, FileObject, superblock_addrs[0], toread, data, true); if (NT_SUCCESS(Status) && ((superblock*)data)->magic == BTRFS_MAGIC) { superblock* sb = (superblock*)data; if (check_superblock_checksum(sb)) { TRACE("volume found\n"); if (length >= superblock_addrs[1] + toread) { ULONG i = 1; superblock* sb2 = ExAllocatePoolWithTag(NonPagedPool, toread, ALLOC_TAG); if (!sb2) { ERR("out of memory\n"); goto deref; } while (superblock_addrs[i] > 0 && length >= superblock_addrs[i] + toread) { Status = sync_read_phys(DeviceObject, FileObject, superblock_addrs[i], toread, (PUCHAR)sb2, true); if (NT_SUCCESS(Status) && sb2->magic == BTRFS_MAGIC) { if (check_superblock_checksum(sb2) && sb2->generation > sb->generation) RtlCopyMemory(sb, sb2, toread); } i++; } ExFreePool(sb2); } if (!fs_ignored(&sb->uuid)) { DeviceObject->Flags &= ~DO_VERIFY_VOLUME; add_volume_device(sb, devpath, length, disk_num, part_num); } } } else if (Status == STATUS_FVE_LOCKED_VOLUME) { if (fve_callback) ret = false; else register_fve_callback(DeviceObject, FileObject, devpath); } deref: if (data) ExFreePool(data); return ret; } NTSTATUS remove_drive_letter(PDEVICE_OBJECT mountmgr, PUNICODE_STRING devpath) { NTSTATUS Status; MOUNTMGR_MOUNT_POINT* mmp; ULONG mmpsize; MOUNTMGR_MOUNT_POINTS mmps1, *mmps2; TRACE("removing drive letter\n"); mmpsize = sizeof(MOUNTMGR_MOUNT_POINT) + devpath->Length; mmp = ExAllocatePoolWithTag(PagedPool, mmpsize, ALLOC_TAG); if (!mmp) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(mmp, mmpsize); mmp->DeviceNameOffset = sizeof(MOUNTMGR_MOUNT_POINT); mmp->DeviceNameLength = devpath->Length; RtlCopyMemory(&mmp[1], devpath->Buffer, devpath->Length); Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_DELETE_POINTS, mmp, mmpsize, &mmps1, sizeof(MOUNTMGR_MOUNT_POINTS), false, NULL); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) { ERR("IOCTL_MOUNTMGR_DELETE_POINTS 1 returned %08lx\n", Status); ExFreePool(mmp); return Status; } if (Status != STATUS_BUFFER_OVERFLOW || mmps1.Size == 0) { ExFreePool(mmp); return STATUS_NOT_FOUND; } mmps2 = ExAllocatePoolWithTag(PagedPool, mmps1.Size, ALLOC_TAG); if (!mmps2) { ERR("out of memory\n"); ExFreePool(mmp); return STATUS_INSUFFICIENT_RESOURCES; } Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_DELETE_POINTS, mmp, mmpsize, mmps2, mmps1.Size, false, NULL); if (!NT_SUCCESS(Status)) ERR("IOCTL_MOUNTMGR_DELETE_POINTS 2 returned %08lx\n", Status); ExFreePool(mmps2); ExFreePool(mmp); return Status; } void disk_arrival(PUNICODE_STRING devpath) { PFILE_OBJECT fileobj; PDEVICE_OBJECT devobj; NTSTATUS Status; STORAGE_DEVICE_NUMBER sdn; ULONG dlisize; DRIVE_LAYOUT_INFORMATION_EX* dli = NULL; IO_STATUS_BLOCK iosb; GET_LENGTH_INFORMATION gli; ExAcquireResourceSharedLite(&boot_lock, TRUE); Status = IoGetDeviceObjectPointer(devpath, FILE_READ_ATTRIBUTES, &fileobj, &devobj); if (!NT_SUCCESS(Status)) { ExReleaseResourceLite(&boot_lock); ERR("IoGetDeviceObjectPointer returned %08lx\n", Status); return; } dlisize = 0; do { dlisize += 1024; if (dli) ExFreePool(dli); dli = ExAllocatePoolWithTag(PagedPool, dlisize, ALLOC_TAG); if (!dli) { ERR("out of memory\n"); goto end; } Status = dev_ioctl(devobj, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0, dli, dlisize, true, &iosb); } while (Status == STATUS_BUFFER_TOO_SMALL); // only consider disk as a potential filesystem if it has no partitions if (NT_SUCCESS(Status) && dli->PartitionCount > 0) { ExFreePool(dli); goto end; } ExFreePool(dli); Status = dev_ioctl(devobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli), true, NULL); if (!NT_SUCCESS(Status)) { ERR("error reading length information: %08lx\n", Status); goto end; } Status = dev_ioctl(devobj, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(STORAGE_DEVICE_NUMBER), true, NULL); if (!NT_SUCCESS(Status)) { TRACE("IOCTL_STORAGE_GET_DEVICE_NUMBER returned %08lx\n", Status); sdn.DeviceNumber = 0xffffffff; sdn.PartitionNumber = 0; } else TRACE("DeviceType = %lu, DeviceNumber = %lu, PartitionNumber = %lu\n", sdn.DeviceType, sdn.DeviceNumber, sdn.PartitionNumber); test_vol(devobj, fileobj, devpath, sdn.DeviceNumber, sdn.PartitionNumber, gli.Length.QuadPart, false); end: ObDereferenceObject(fileobj); ExReleaseResourceLite(&boot_lock); } void remove_volume_child(_Inout_ _Requires_exclusive_lock_held_(_Curr_->child_lock) _Releases_exclusive_lock_(_Curr_->child_lock) _In_ volume_device_extension* vde, _In_ volume_child* vc, _In_ bool skip_dev) { NTSTATUS Status; pdo_device_extension* pdode = vde->pdode; device_extension* Vcb = vde->mounted_device ? vde->mounted_device->DeviceExtension : NULL; if (vc->notification_entry) { if (fIoUnregisterPlugPlayNotificationEx) fIoUnregisterPlugPlayNotificationEx(vc->notification_entry); else IoUnregisterPlugPlayNotification(vc->notification_entry); } if (vde->mounted_device && (!Vcb || !Vcb->options.allow_degraded)) { Status = pnp_surprise_removal(vde->mounted_device, NULL); if (!NT_SUCCESS(Status)) ERR("pnp_surprise_removal returned %08lx\n", Status); } if (!Vcb || !Vcb->options.allow_degraded) { Status = IoSetDeviceInterfaceState(&vde->bus_name, false); if (!NT_SUCCESS(Status)) WARN("IoSetDeviceInterfaceState returned %08lx\n", Status); } if (pdode->children_loaded > 0) { UNICODE_STRING mmdevpath; PFILE_OBJECT FileObject; PDEVICE_OBJECT mountmgr; LIST_ENTRY* le; if (!Vcb || !Vcb->options.allow_degraded) { RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME); Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr); if (!NT_SUCCESS(Status)) ERR("IoGetDeviceObjectPointer returned %08lx\n", Status); else { le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry); if (vc2->had_drive_letter) { // re-add entry to mountmgr MOUNTDEV_NAME mdn; Status = dev_ioctl(vc2->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status); else { MOUNTDEV_NAME* mdn2; ULONG mdnsize = (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength; mdn2 = ExAllocatePoolWithTag(PagedPool, mdnsize, ALLOC_TAG); if (!mdn2) ERR("out of memory\n"); else { Status = dev_ioctl(vc2->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, mdn2, mdnsize, true, NULL); if (!NT_SUCCESS(Status)) ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status); else { UNICODE_STRING name; name.Buffer = mdn2->Name; name.Length = name.MaximumLength = mdn2->NameLength; Status = mountmgr_add_drive_letter(mountmgr, &name); if (!NT_SUCCESS(Status)) WARN("mountmgr_add_drive_letter returned %08lx\n", Status); } ExFreePool(mdn2); } } } le = le->Flink; } ObDereferenceObject(FileObject); } } else if (!skip_dev) { ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (dev->devobj == vc->devobj) { dev->devobj = NULL; // mark as missing break; } le = le->Flink; } ExReleaseResourceLite(&Vcb->tree_lock); } if (vde->device->Characteristics & FILE_REMOVABLE_MEDIA) { vde->device->Characteristics &= ~FILE_REMOVABLE_MEDIA; le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry); if (vc2 != vc && vc2->devobj->Characteristics & FILE_REMOVABLE_MEDIA) { vde->device->Characteristics |= FILE_REMOVABLE_MEDIA; break; } le = le->Flink; } } } ObDereferenceObject(vc->fileobj); ExFreePool(vc->pnp_name.Buffer); RemoveEntryList(&vc->list_entry); ExFreePool(vc); pdode->children_loaded--; if (pdode->children_loaded == 0) { // remove volume device bool remove = false; RemoveEntryList(&pdode->list_entry); vde->removing = true; Status = IoSetDeviceInterfaceState(&vde->bus_name, false); if (!NT_SUCCESS(Status)) WARN("IoSetDeviceInterfaceState returned %08lx\n", Status); if (vde->pdo->AttachedDevice) IoDetachDevice(vde->pdo); if (vde->open_count == 0) remove = true; ExReleaseResourceLite(&pdode->child_lock); if (!no_pnp) { bus_device_extension* bde = busobj->DeviceExtension; IoInvalidateDeviceRelations(bde->buspdo, BusRelations); } if (remove) { if (vde->name.Buffer) ExFreePool(vde->name.Buffer); if (Vcb) Vcb->vde = NULL; ExDeleteResourceLite(&pdode->child_lock); IoDeleteDevice(vde->device); } } else ExReleaseResourceLite(&pdode->child_lock); } bool volume_arrival(PUNICODE_STRING devpath, bool fve_callback) { STORAGE_DEVICE_NUMBER sdn; PFILE_OBJECT fileobj; PDEVICE_OBJECT devobj; GET_LENGTH_INFORMATION gli; NTSTATUS Status; bool ret = true; TRACE("%.*S\n", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer); ExAcquireResourceSharedLite(&boot_lock, TRUE); Status = IoGetDeviceObjectPointer(devpath, FILE_READ_ATTRIBUTES, &fileobj, &devobj); if (!NT_SUCCESS(Status)) { ExReleaseResourceLite(&boot_lock); ERR("IoGetDeviceObjectPointer returned %08lx\n", Status); return false; } // make sure we're not processing devices we've created ourselves if (devobj->DriverObject == drvobj) goto end; Status = dev_ioctl(devobj, IOCTL_VOLUME_ONLINE, NULL, 0, NULL, 0, true, NULL); if (!NT_SUCCESS(Status)) TRACE("IOCTL_VOLUME_ONLINE returned %08lx\n", Status); Status = dev_ioctl(devobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli), true, NULL); if (!NT_SUCCESS(Status)) { ERR("IOCTL_DISK_GET_LENGTH_INFO returned %08lx\n", Status); goto end; } Status = dev_ioctl(devobj, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(STORAGE_DEVICE_NUMBER), true, NULL); if (!NT_SUCCESS(Status)) { TRACE("IOCTL_STORAGE_GET_DEVICE_NUMBER returned %08lx\n", Status); sdn.DeviceNumber = 0xffffffff; sdn.PartitionNumber = 0; } else TRACE("DeviceType = %lu, DeviceNumber = %lu, PartitionNumber = %lu\n", sdn.DeviceType, sdn.DeviceNumber, sdn.PartitionNumber); // If we've just added a partition to a whole-disk filesystem, unmount it if (sdn.DeviceNumber != 0xffffffff && sdn.PartitionNumber != 0) { LIST_ENTRY* le; ExAcquireResourceExclusiveLite(&pdo_list_lock, true); le = pdo_list.Flink; while (le != &pdo_list) { pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry); LIST_ENTRY* le2; bool changed = false; if (pdode->vde) { ExAcquireResourceExclusiveLite(&pdode->child_lock, true); le2 = pdode->children.Flink; while (le2 != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry); LIST_ENTRY* le3 = le2->Flink; if (vc->disk_num == sdn.DeviceNumber && vc->part_num == 0) { TRACE("removing device\n"); remove_volume_child(pdode->vde, vc, false); changed = true; break; } le2 = le3; } if (!changed) ExReleaseResourceLite(&pdode->child_lock); else break; } le = le->Flink; } ExReleaseResourceLite(&pdo_list_lock); } ret = test_vol(devobj, fileobj, devpath, sdn.DeviceNumber, sdn.PartitionNumber, gli.Length.QuadPart, fve_callback); end: ObDereferenceObject(fileobj); ExReleaseResourceLite(&boot_lock); return ret; } static void volume_arrival2(PUNICODE_STRING devpath) { volume_arrival(devpath, false); } void volume_removal(PUNICODE_STRING devpath) { LIST_ENTRY* le; UNICODE_STRING devpath2; TRACE("%.*S\n", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer); devpath2 = *devpath; if (devpath->Length > 4 * sizeof(WCHAR) && devpath->Buffer[0] == '\\' && (devpath->Buffer[1] == '\\' || devpath->Buffer[1] == '?') && devpath->Buffer[2] == '?' && devpath->Buffer[3] == '\\') { devpath2.Buffer = &devpath2.Buffer[3]; devpath2.Length -= 3 * sizeof(WCHAR); devpath2.MaximumLength -= 3 * sizeof(WCHAR); } ExAcquireResourceExclusiveLite(&pdo_list_lock, true); le = pdo_list.Flink; while (le != &pdo_list) { pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry); LIST_ENTRY* le2; bool changed = false; if (pdode->vde) { ExAcquireResourceExclusiveLite(&pdode->child_lock, true); le2 = pdode->children.Flink; while (le2 != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry); LIST_ENTRY* le3 = le2->Flink; if (vc->pnp_name.Length == devpath2.Length && RtlCompareMemory(vc->pnp_name.Buffer, devpath2.Buffer, devpath2.Length) == devpath2.Length) { TRACE("removing device\n"); if (!vc->boot_volume) { remove_volume_child(pdode->vde, vc, false); changed = true; } break; } le2 = le3; } if (!changed) ExReleaseResourceLite(&pdode->child_lock); else break; } le = le->Flink; } ExReleaseResourceLite(&pdo_list_lock); } typedef struct { UNICODE_STRING name; pnp_callback func; PIO_WORKITEM work_item; } pnp_callback_context; _Function_class_(IO_WORKITEM_ROUTINE) static void __stdcall do_pnp_callback(PDEVICE_OBJECT DeviceObject, PVOID con) { pnp_callback_context* context = con; UNUSED(DeviceObject); context->func(&context->name); if (context->name.Buffer) ExFreePool(context->name.Buffer); IoFreeWorkItem(context->work_item); ExFreePool(context); } static void enqueue_pnp_callback(PUNICODE_STRING name, pnp_callback func) { PIO_WORKITEM work_item; pnp_callback_context* context; work_item = IoAllocateWorkItem(master_devobj); if (!work_item) { ERR("out of memory\n"); return; } context = ExAllocatePoolWithTag(PagedPool, sizeof(pnp_callback_context), ALLOC_TAG); if (!context) { ERR("out of memory\n"); IoFreeWorkItem(work_item); return; } if (name->Length > 0) { context->name.Buffer = ExAllocatePoolWithTag(PagedPool, name->Length, ALLOC_TAG); if (!context->name.Buffer) { ERR("out of memory\n"); ExFreePool(context); IoFreeWorkItem(work_item); return; } RtlCopyMemory(context->name.Buffer, name->Buffer, name->Length); context->name.Length = context->name.MaximumLength = name->Length; } else { context->name.Length = context->name.MaximumLength = 0; context->name.Buffer = NULL; } context->func = func; context->work_item = work_item; IoQueueWorkItem(work_item, do_pnp_callback, DelayedWorkQueue, context); } _Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE) NTSTATUS __stdcall volume_notification(PVOID NotificationStructure, PVOID Context) { DEVICE_INTERFACE_CHANGE_NOTIFICATION* dicn = (DEVICE_INTERFACE_CHANGE_NOTIFICATION*)NotificationStructure; UNUSED(Context); if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_ARRIVAL, sizeof(GUID)) == sizeof(GUID)) enqueue_pnp_callback(dicn->SymbolicLinkName, volume_arrival2); else if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_REMOVAL, sizeof(GUID)) == sizeof(GUID)) enqueue_pnp_callback(dicn->SymbolicLinkName, volume_removal); return STATUS_SUCCESS; } _Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE) NTSTATUS __stdcall pnp_notification(PVOID NotificationStructure, PVOID Context) { DEVICE_INTERFACE_CHANGE_NOTIFICATION* dicn = (DEVICE_INTERFACE_CHANGE_NOTIFICATION*)NotificationStructure; UNUSED(Context); if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_ARRIVAL, sizeof(GUID)) == sizeof(GUID)) enqueue_pnp_callback(dicn->SymbolicLinkName, disk_arrival); else if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_REMOVAL, sizeof(GUID)) == sizeof(GUID)) enqueue_pnp_callback(dicn->SymbolicLinkName, volume_removal); return STATUS_SUCCESS; } static void mountmgr_process_drive(PDEVICE_OBJECT mountmgr, PUNICODE_STRING device_name) { NTSTATUS Status; LIST_ENTRY* le; bool need_remove = false; volume_child* vc2 = NULL; ExAcquireResourceSharedLite(&pdo_list_lock, true); le = pdo_list.Flink; while (le != &pdo_list) { pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry); LIST_ENTRY* le2; ExAcquireResourceSharedLite(&pdode->child_lock, true); le2 = pdode->children.Flink; while (le2 != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry); if (vc->devobj) { MOUNTDEV_NAME mdn; Status = dev_ioctl(vc->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status); else { MOUNTDEV_NAME* mdn2; ULONG mdnsize = (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength; mdn2 = ExAllocatePoolWithTag(NonPagedPool, mdnsize, ALLOC_TAG); if (!mdn2) ERR("out of memory\n"); else { Status = dev_ioctl(vc->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, mdn2, mdnsize, true, NULL); if (!NT_SUCCESS(Status)) ERR("IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\n", Status); else { if (mdn2->NameLength == device_name->Length && RtlCompareMemory(mdn2->Name, device_name->Buffer, device_name->Length) == device_name->Length) { vc2 = vc; need_remove = true; break; } } ExFreePool(mdn2); } } } le2 = le2->Flink; } ExReleaseResourceLite(&pdode->child_lock); if (need_remove) break; le = le->Flink; } ExReleaseResourceLite(&pdo_list_lock); if (need_remove) { Status = remove_drive_letter(mountmgr, device_name); if (!NT_SUCCESS(Status)) ERR("remove_drive_letter returned %08lx\n", Status); else vc2->had_drive_letter = true; } } static void mountmgr_updated(PDEVICE_OBJECT mountmgr, MOUNTMGR_MOUNT_POINTS* mmps) { ULONG i; static const WCHAR pref[] = L"\\DosDevices\\"; for (i = 0; i < mmps->NumberOfMountPoints; i++) { UNICODE_STRING symlink, device_name; if (mmps->MountPoints[i].SymbolicLinkNameOffset != 0) { symlink.Buffer = (WCHAR*)(((uint8_t*)mmps) + mmps->MountPoints[i].SymbolicLinkNameOffset); symlink.Length = symlink.MaximumLength = mmps->MountPoints[i].SymbolicLinkNameLength; } else { symlink.Buffer = NULL; symlink.Length = symlink.MaximumLength = 0; } if (mmps->MountPoints[i].DeviceNameOffset != 0) { device_name.Buffer = (WCHAR*)(((uint8_t*)mmps) + mmps->MountPoints[i].DeviceNameOffset); device_name.Length = device_name.MaximumLength = mmps->MountPoints[i].DeviceNameLength; } else { device_name.Buffer = NULL; device_name.Length = device_name.MaximumLength = 0; } if (symlink.Length > sizeof(pref) - sizeof(WCHAR) && RtlCompareMemory(symlink.Buffer, pref, sizeof(pref) - sizeof(WCHAR)) == sizeof(pref) - sizeof(WCHAR)) mountmgr_process_drive(mountmgr, &device_name); } } _Function_class_(KSTART_ROUTINE) void __stdcall mountmgr_thread(_In_ void* context) { UNICODE_STRING mmdevpath; NTSTATUS Status; PFILE_OBJECT FileObject; PDEVICE_OBJECT mountmgr; MOUNTMGR_CHANGE_NOTIFY_INFO mcni; UNUSED(context); RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME); Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr); if (!NT_SUCCESS(Status)) { ERR("IoGetDeviceObjectPointer returned %08lx\n", Status); return; } mcni.EpicNumber = 0; while (true) { PIRP Irp; MOUNTMGR_MOUNT_POINT mmp; MOUNTMGR_MOUNT_POINTS mmps; IO_STATUS_BLOCK iosb; KeClearEvent(&mountmgr_thread_event); Irp = IoBuildDeviceIoControlRequest(IOCTL_MOUNTMGR_CHANGE_NOTIFY, mountmgr, &mcni, sizeof(MOUNTMGR_CHANGE_NOTIFY_INFO), &mcni, sizeof(MOUNTMGR_CHANGE_NOTIFY_INFO), false, &mountmgr_thread_event, &iosb); if (!Irp) { ERR("out of memory\n"); break; } Status = IoCallDriver(mountmgr, Irp); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&mountmgr_thread_event, Executive, KernelMode, false, NULL); Status = iosb.Status; } if (shutting_down) break; if (!NT_SUCCESS(Status)) { ERR("IOCTL_MOUNTMGR_CHANGE_NOTIFY returned %08lx\n", Status); break; } TRACE("mountmgr changed\n"); RtlZeroMemory(&mmp, sizeof(MOUNTMGR_MOUNT_POINT)); Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_QUERY_POINTS, &mmp, sizeof(MOUNTMGR_MOUNT_POINT), &mmps, sizeof(MOUNTMGR_MOUNT_POINTS), false, NULL); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) ERR("IOCTL_MOUNTMGR_QUERY_POINTS 1 returned %08lx\n", Status); else if (mmps.Size > 0) { MOUNTMGR_MOUNT_POINTS* mmps2; mmps2 = ExAllocatePoolWithTag(NonPagedPool, mmps.Size, ALLOC_TAG); if (!mmps2) { ERR("out of memory\n"); break; } Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_QUERY_POINTS, &mmp, sizeof(MOUNTMGR_MOUNT_POINT), mmps2, mmps.Size, false, NULL); if (!NT_SUCCESS(Status)) ERR("IOCTL_MOUNTMGR_QUERY_POINTS returned %08lx\n", Status); else mountmgr_updated(mountmgr, mmps2); ExFreePool(mmps2); } } ObDereferenceObject(FileObject); mountmgr_thread_handle = NULL; PsTerminateSystemThread(STATUS_SUCCESS); } ================================================ FILE: src/security.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #define SEF_DACL_AUTO_INHERIT 0x01 #define SEF_SACL_AUTO_INHERIT 0x02 typedef struct { UCHAR revision; UCHAR elements; UCHAR auth[6]; uint32_t nums[8]; } sid_header; static sid_header sid_BA = { 1, 2, SECURITY_NT_AUTHORITY, {32, 544}}; // BUILTIN\Administrators static sid_header sid_SY = { 1, 1, SECURITY_NT_AUTHORITY, {18}}; // NT AUTHORITY\SYSTEM static sid_header sid_BU = { 1, 2, SECURITY_NT_AUTHORITY, {32, 545}}; // BUILTIN\Users static sid_header sid_AU = { 1, 1, SECURITY_NT_AUTHORITY, {11}}; // NT AUTHORITY\Authenticated Users typedef struct { UCHAR flags; ACCESS_MASK mask; sid_header* sid; } dacl; static dacl def_dacls[] = { { 0, FILE_ALL_ACCESS, &sid_BA }, { OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE, FILE_ALL_ACCESS, &sid_BA }, { 0, FILE_ALL_ACCESS, &sid_SY }, { OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE, FILE_ALL_ACCESS, &sid_SY }, { OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE, FILE_GENERIC_READ | FILE_GENERIC_EXECUTE, &sid_BU }, { OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE, &sid_AU }, { 0, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE, &sid_AU }, // FIXME - Mandatory Label\High Mandatory Level:(OI)(NP)(IO)(NW) { 0, 0, NULL } }; extern LIST_ENTRY uid_map_list, gid_map_list; extern ERESOURCE mapping_lock; void add_user_mapping(WCHAR* sidstring, ULONG sidstringlength, uint32_t uid) { unsigned int i, np; uint8_t numdashes; uint64_t val; ULONG sidsize; sid_header* sid; uid_map* um; if (sidstringlength < 4 || sidstring[0] != 'S' || sidstring[1] != '-' || sidstring[2] != '1' || sidstring[3] != '-') { ERR("invalid SID\n"); return; } sidstring = &sidstring[4]; sidstringlength -= 4; numdashes = 0; for (i = 0; i < sidstringlength; i++) { if (sidstring[i] == '-') { numdashes++; sidstring[i] = 0; } } sidsize = 8 + (numdashes * 4); sid = ExAllocatePoolWithTag(PagedPool, sidsize, ALLOC_TAG); if (!sid) { ERR("out of memory\n"); return; } sid->revision = 0x01; sid->elements = numdashes; np = 0; while (sidstringlength > 0) { val = 0; i = 0; while (sidstring[i] != '-' && i < sidstringlength) { if (sidstring[i] >= '0' && sidstring[i] <= '9') { val *= 10; val += sidstring[i] - '0'; } else break; i++; } i++; TRACE("val = %u, i = %u, ssl = %lu\n", (uint32_t)val, i, sidstringlength); if (np == 0) { sid->auth[0] = (uint8_t)((val & 0xff0000000000) >> 40); sid->auth[1] = (uint8_t)((val & 0xff00000000) >> 32); sid->auth[2] = (uint8_t)((val & 0xff000000) >> 24); sid->auth[3] = (uint8_t)((val & 0xff0000) >> 16); sid->auth[4] = (uint8_t)((val & 0xff00) >> 8); sid->auth[5] = val & 0xff; } else { sid->nums[np-1] = (uint32_t)val; } np++; if (sidstringlength > i) { sidstringlength -= i; sidstring = &sidstring[i]; } else break; } um = ExAllocatePoolWithTag(PagedPool, sizeof(uid_map), ALLOC_TAG); if (!um) { ERR("out of memory\n"); ExFreePool(sid); return; } um->sid = sid; um->uid = uid; InsertTailList(&uid_map_list, &um->listentry); } void add_group_mapping(WCHAR* sidstring, ULONG sidstringlength, uint32_t gid) { unsigned int i, np; uint8_t numdashes; uint64_t val; ULONG sidsize; sid_header* sid; gid_map* gm; if (sidstringlength < 4 || sidstring[0] != 'S' || sidstring[1] != '-' || sidstring[2] != '1' || sidstring[3] != '-') { ERR("invalid SID\n"); return; } sidstring = &sidstring[4]; sidstringlength -= 4; numdashes = 0; for (i = 0; i < sidstringlength; i++) { if (sidstring[i] == '-') { numdashes++; sidstring[i] = 0; } } sidsize = 8 + (numdashes * 4); sid = ExAllocatePoolWithTag(PagedPool, sidsize, ALLOC_TAG); if (!sid) { ERR("out of memory\n"); return; } sid->revision = 0x01; sid->elements = numdashes; np = 0; while (sidstringlength > 0) { val = 0; i = 0; while (i < sidstringlength && sidstring[i] != '-') { if (sidstring[i] >= '0' && sidstring[i] <= '9') { val *= 10; val += sidstring[i] - '0'; } else break; i++; } i++; TRACE("val = %u, i = %u, ssl = %lu\n", (uint32_t)val, i, sidstringlength); if (np == 0) { sid->auth[0] = (uint8_t)((val & 0xff0000000000) >> 40); sid->auth[1] = (uint8_t)((val & 0xff00000000) >> 32); sid->auth[2] = (uint8_t)((val & 0xff000000) >> 24); sid->auth[3] = (uint8_t)((val & 0xff0000) >> 16); sid->auth[4] = (uint8_t)((val & 0xff00) >> 8); sid->auth[5] = val & 0xff; } else sid->nums[np-1] = (uint32_t)val; np++; if (sidstringlength > i) { sidstringlength -= i; sidstring = &sidstring[i]; } else break; } gm = ExAllocatePoolWithTag(PagedPool, sizeof(gid_map), ALLOC_TAG); if (!gm) { ERR("out of memory\n"); ExFreePool(sid); return; } gm->sid = sid; gm->gid = gid; InsertTailList(&gid_map_list, &gm->listentry); } NTSTATUS uid_to_sid(uint32_t uid, PSID* sid) { LIST_ENTRY* le; sid_header* sh; UCHAR els; ExAcquireResourceSharedLite(&mapping_lock, true); le = uid_map_list.Flink; while (le != &uid_map_list) { uid_map* um = CONTAINING_RECORD(le, uid_map, listentry); if (um->uid == uid) { *sid = ExAllocatePoolWithTag(PagedPool, RtlLengthSid(um->sid), ALLOC_TAG); if (!*sid) { ERR("out of memory\n"); ExReleaseResourceLite(&mapping_lock); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(*sid, um->sid, RtlLengthSid(um->sid)); ExReleaseResourceLite(&mapping_lock); return STATUS_SUCCESS; } le = le->Flink; } ExReleaseResourceLite(&mapping_lock); if (uid == 0) { // root // FIXME - find actual Administrator account, rather than SYSTEM (S-1-5-18) // (of form S-1-5-21-...-500) els = 1; sh = ExAllocatePoolWithTag(PagedPool, sizeof(sid_header) + ((els - 1) * sizeof(uint32_t)), ALLOC_TAG); if (!sh) { ERR("out of memory\n"); *sid = NULL; return STATUS_INSUFFICIENT_RESOURCES; } sh->revision = 1; sh->elements = els; sh->auth[0] = 0; sh->auth[1] = 0; sh->auth[2] = 0; sh->auth[3] = 0; sh->auth[4] = 0; sh->auth[5] = 5; sh->nums[0] = 18; } else { // fallback to S-1-22-1-X, Samba's SID scheme sh = ExAllocatePoolWithTag(PagedPool, sizeof(sid_header), ALLOC_TAG); if (!sh) { ERR("out of memory\n"); *sid = NULL; return STATUS_INSUFFICIENT_RESOURCES; } sh->revision = 1; sh->elements = 2; sh->auth[0] = 0; sh->auth[1] = 0; sh->auth[2] = 0; sh->auth[3] = 0; sh->auth[4] = 0; sh->auth[5] = 22; sh->nums[0] = 1; sh->nums[1] = uid; } *sid = sh; return STATUS_SUCCESS; } uint32_t sid_to_uid(PSID sid) { LIST_ENTRY* le; sid_header* sh = sid; ExAcquireResourceSharedLite(&mapping_lock, true); le = uid_map_list.Flink; while (le != &uid_map_list) { uid_map* um = CONTAINING_RECORD(le, uid_map, listentry); if (RtlEqualSid(sid, um->sid)) { ExReleaseResourceLite(&mapping_lock); return um->uid; } le = le->Flink; } ExReleaseResourceLite(&mapping_lock); if (RtlEqualSid(sid, &sid_SY)) return 0; // root // Samba's SID scheme: S-1-22-1-X if (sh->revision == 1 && sh->elements == 2 && sh->auth[0] == 0 && sh->auth[1] == 0 && sh->auth[2] == 0 && sh->auth[3] == 0 && sh->auth[4] == 0 && sh->auth[5] == 22 && sh->nums[0] == 1) return sh->nums[1]; return UID_NOBODY; } static void gid_to_sid(uint32_t gid, PSID* sid) { sid_header* sh; UCHAR els; // FIXME - do this properly? // fallback to S-1-22-2-X, Samba's SID scheme els = 2; sh = ExAllocatePoolWithTag(PagedPool, sizeof(sid_header) + ((els - 1) * sizeof(uint32_t)), ALLOC_TAG); if (!sh) { ERR("out of memory\n"); *sid = NULL; return; } sh->revision = 1; sh->elements = els; sh->auth[0] = 0; sh->auth[1] = 0; sh->auth[2] = 0; sh->auth[3] = 0; sh->auth[4] = 0; sh->auth[5] = 22; sh->nums[0] = 2; sh->nums[1] = gid; *sid = sh; } static ACL* load_default_acl() { uint16_t size, i; ACL* acl; ACCESS_ALLOWED_ACE* aaa; size = sizeof(ACL); i = 0; while (def_dacls[i].sid) { size += sizeof(ACCESS_ALLOWED_ACE); size += 8 + (def_dacls[i].sid->elements * sizeof(uint32_t)) - sizeof(ULONG); i++; } acl = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG); if (!acl) { ERR("out of memory\n"); return NULL; } acl->AclRevision = ACL_REVISION; acl->Sbz1 = 0; acl->AclSize = size; acl->AceCount = i; acl->Sbz2 = 0; aaa = (ACCESS_ALLOWED_ACE*)&acl[1]; i = 0; while (def_dacls[i].sid) { aaa->Header.AceType = ACCESS_ALLOWED_ACE_TYPE; aaa->Header.AceFlags = def_dacls[i].flags; aaa->Header.AceSize = sizeof(ACCESS_ALLOWED_ACE) - sizeof(ULONG) + 8 + (def_dacls[i].sid->elements * sizeof(uint32_t)); aaa->Mask = def_dacls[i].mask; RtlCopyMemory(&aaa->SidStart, def_dacls[i].sid, 8 + (def_dacls[i].sid->elements * sizeof(uint32_t))); aaa = (ACCESS_ALLOWED_ACE*)((uint8_t*)aaa + aaa->Header.AceSize); i++; } return acl; } static void get_top_level_sd(fcb* fcb) { NTSTATUS Status; SECURITY_DESCRIPTOR sd; ULONG buflen; ACL* acl = NULL; PSID usersid = NULL, groupsid = NULL; Status = RtlCreateSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); if (!NT_SUCCESS(Status)) { ERR("RtlCreateSecurityDescriptor returned %08lx\n", Status); goto end; } Status = uid_to_sid(fcb->inode_item.st_uid, &usersid); if (!NT_SUCCESS(Status)) { ERR("uid_to_sid returned %08lx\n", Status); goto end; } Status = RtlSetOwnerSecurityDescriptor(&sd, usersid, false); if (!NT_SUCCESS(Status)) { ERR("RtlSetOwnerSecurityDescriptor returned %08lx\n", Status); goto end; } gid_to_sid(fcb->inode_item.st_gid, &groupsid); if (!groupsid) { ERR("out of memory\n"); goto end; } Status = RtlSetGroupSecurityDescriptor(&sd, groupsid, false); if (!NT_SUCCESS(Status)) { ERR("RtlSetGroupSecurityDescriptor returned %08lx\n", Status); goto end; } acl = load_default_acl(); if (!acl) { ERR("out of memory\n"); goto end; } Status = RtlSetDaclSecurityDescriptor(&sd, true, acl, false); if (!NT_SUCCESS(Status)) { ERR("RtlSetDaclSecurityDescriptor returned %08lx\n", Status); goto end; } // FIXME - SACL_SECURITY_INFORMATION buflen = 0; // get sd size Status = RtlAbsoluteToSelfRelativeSD(&sd, NULL, &buflen); if (Status != STATUS_SUCCESS && Status != STATUS_BUFFER_TOO_SMALL) { ERR("RtlAbsoluteToSelfRelativeSD 1 returned %08lx\n", Status); goto end; } if (buflen == 0 || Status == STATUS_SUCCESS) { TRACE("RtlAbsoluteToSelfRelativeSD said SD is zero-length\n"); goto end; } fcb->sd = ExAllocatePoolWithTag(PagedPool, buflen, ALLOC_TAG); if (!fcb->sd) { ERR("out of memory\n"); goto end; } Status = RtlAbsoluteToSelfRelativeSD(&sd, fcb->sd, &buflen); if (!NT_SUCCESS(Status)) { ERR("RtlAbsoluteToSelfRelativeSD 2 returned %08lx\n", Status); ExFreePool(fcb->sd); fcb->sd = NULL; goto end; } end: if (acl) ExFreePool(acl); if (usersid) ExFreePool(usersid); if (groupsid) ExFreePool(groupsid); } void fcb_get_sd(fcb* fcb, struct _fcb* parent, bool look_for_xattr, PIRP Irp) { NTSTATUS Status; PSID usersid = NULL, groupsid = NULL; SECURITY_SUBJECT_CONTEXT subjcont; ULONG buflen; PSECURITY_DESCRIPTOR* abssd; PSECURITY_DESCRIPTOR newsd; PACL dacl, sacl; PSID owner, group; ULONG abssdlen = 0, dacllen = 0, sacllen = 0, ownerlen = 0, grouplen = 0; uint8_t* buf; 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)) return; if (!parent) { get_top_level_sd(fcb); return; } SeCaptureSubjectContext(&subjcont); Status = SeAssignSecurityEx(parent->sd, NULL, (void**)&fcb->sd, NULL, fcb->type == BTRFS_TYPE_DIRECTORY, SEF_DACL_AUTO_INHERIT, &subjcont, IoGetFileObjectGenericMapping(), PagedPool); if (!NT_SUCCESS(Status)) { ERR("SeAssignSecurityEx returned %08lx\n", Status); return; } Status = RtlSelfRelativeToAbsoluteSD(fcb->sd, NULL, &abssdlen, NULL, &dacllen, NULL, &sacllen, NULL, &ownerlen, NULL, &grouplen); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_TOO_SMALL) { ERR("RtlSelfRelativeToAbsoluteSD returned %08lx\n", Status); return; } if (abssdlen + dacllen + sacllen + ownerlen + grouplen == 0) { ERR("RtlSelfRelativeToAbsoluteSD returned zero lengths\n"); return; } buf = (uint8_t*)ExAllocatePoolWithTag(PagedPool, abssdlen + dacllen + sacllen + ownerlen + grouplen, ALLOC_TAG); if (!buf) { ERR("out of memory\n"); return; } abssd = (PSECURITY_DESCRIPTOR)buf; dacl = (PACL)(buf + abssdlen); sacl = (PACL)(buf + abssdlen + dacllen); owner = (PSID)(buf + abssdlen + dacllen + sacllen); group = (PSID)(buf + abssdlen + dacllen + sacllen + ownerlen); Status = RtlSelfRelativeToAbsoluteSD(fcb->sd, abssd, &abssdlen, dacl, &dacllen, sacl, &sacllen, owner, &ownerlen, group, &grouplen); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_TOO_SMALL) { ERR("RtlSelfRelativeToAbsoluteSD returned %08lx\n", Status); ExFreePool(buf); return; } Status = uid_to_sid(fcb->inode_item.st_uid, &usersid); if (!NT_SUCCESS(Status)) { ERR("uid_to_sid returned %08lx\n", Status); ExFreePool(buf); return; } RtlSetOwnerSecurityDescriptor(abssd, usersid, false); gid_to_sid(fcb->inode_item.st_gid, &groupsid); if (!groupsid) { ERR("out of memory\n"); ExFreePool(usersid); ExFreePool(buf); return; } RtlSetGroupSecurityDescriptor(abssd, groupsid, false); buflen = 0; Status = RtlAbsoluteToSelfRelativeSD(abssd, NULL, &buflen); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_TOO_SMALL) { ERR("RtlAbsoluteToSelfRelativeSD returned %08lx\n", Status); ExFreePool(usersid); ExFreePool(groupsid); ExFreePool(buf); return; } if (buflen == 0) { ERR("RtlAbsoluteToSelfRelativeSD returned a buffer size of 0\n"); ExFreePool(usersid); ExFreePool(groupsid); ExFreePool(buf); return; } newsd = ExAllocatePoolWithTag(PagedPool, buflen, ALLOC_TAG); if (!newsd) { ERR("out of memory\n"); ExFreePool(usersid); ExFreePool(groupsid); ExFreePool(buf); return; } Status = RtlAbsoluteToSelfRelativeSD(abssd, newsd, &buflen); if (!NT_SUCCESS(Status)) { ERR("RtlAbsoluteToSelfRelativeSD returned %08lx\n", Status); ExFreePool(usersid); ExFreePool(groupsid); ExFreePool(buf); return; } ExFreePool(fcb->sd); fcb->sd = newsd; ExFreePool(usersid); ExFreePool(groupsid); ExFreePool(buf); } static NTSTATUS get_file_security(PFILE_OBJECT FileObject, SECURITY_DESCRIPTOR* relsd, ULONG* buflen, SECURITY_INFORMATION flags) { NTSTATUS Status; fcb* fcb = FileObject->FsContext; ccb* ccb = FileObject->FsContext2; file_ref* fileref = ccb ? ccb->fileref : NULL; if (fcb->ads) { if (fileref && fileref->parent) fcb = fileref->parent->fcb; else { ERR("could not get parent fcb for stream\n"); return STATUS_INTERNAL_ERROR; } } // Why (void**)? Is this a bug in mingw? Status = SeQuerySecurityDescriptorInfo(&flags, relsd, buflen, (void**)&fcb->sd); if (Status == STATUS_BUFFER_TOO_SMALL) TRACE("SeQuerySecurityDescriptorInfo returned %08lx\n", Status); else if (!NT_SUCCESS(Status)) ERR("SeQuerySecurityDescriptorInfo returned %08lx\n", Status); return Status; } _Dispatch_type_(IRP_MJ_QUERY_SECURITY) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_query_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS Status; SECURITY_DESCRIPTOR* sd; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); device_extension* Vcb = DeviceObject->DeviceExtension; ULONG buflen; bool top_level; PFILE_OBJECT FileObject = IrpSp->FileObject; ccb* ccb = FileObject ? FileObject->FsContext2 : NULL; FsRtlEnterFileSystem(); TRACE("query security\n"); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = STATUS_INVALID_DEVICE_REQUEST; goto end; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } if (!ccb) { ERR("no ccb\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (Irp->RequestorMode == UserMode && !(ccb->access & READ_CONTROL)) { WARN("insufficient permissions\n"); Status = STATUS_ACCESS_DENIED; goto end; } Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; if (IrpSp->Parameters.QuerySecurity.SecurityInformation & OWNER_SECURITY_INFORMATION) TRACE("OWNER_SECURITY_INFORMATION\n"); if (IrpSp->Parameters.QuerySecurity.SecurityInformation & GROUP_SECURITY_INFORMATION) TRACE("GROUP_SECURITY_INFORMATION\n"); if (IrpSp->Parameters.QuerySecurity.SecurityInformation & DACL_SECURITY_INFORMATION) TRACE("DACL_SECURITY_INFORMATION\n"); if (IrpSp->Parameters.QuerySecurity.SecurityInformation & SACL_SECURITY_INFORMATION) TRACE("SACL_SECURITY_INFORMATION\n"); TRACE("length = %lu\n", IrpSp->Parameters.QuerySecurity.Length); sd = map_user_buffer(Irp, NormalPagePriority); TRACE("sd = %p\n", sd); if (Irp->MdlAddress && !sd) { ERR("MmGetSystemAddressForMdlSafe returned NULL\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } buflen = IrpSp->Parameters.QuerySecurity.Length; Status = get_file_security(IrpSp->FileObject, sd, &buflen, IrpSp->Parameters.QuerySecurity.SecurityInformation); if (NT_SUCCESS(Status)) Irp->IoStatus.Information = IrpSp->Parameters.QuerySecurity.Length; else if (Status == STATUS_BUFFER_TOO_SMALL) { Irp->IoStatus.Information = buflen; Status = STATUS_BUFFER_OVERFLOW; } else Irp->IoStatus.Information = 0; end: TRACE("Irp->IoStatus.Information = %Iu\n", Irp->IoStatus.Information); Irp->IoStatus.Status = Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); if (top_level) IoSetTopLevelIrp(NULL); TRACE("returning %08lx\n", Status); FsRtlExitFileSystem(); return Status; } static NTSTATUS set_file_security(device_extension* Vcb, PFILE_OBJECT FileObject, SECURITY_DESCRIPTOR* sd, PSECURITY_INFORMATION flags, PIRP Irp) { NTSTATUS Status; fcb* fcb = FileObject->FsContext; ccb* ccb = FileObject->FsContext2; file_ref* fileref = ccb ? ccb->fileref : NULL; SECURITY_DESCRIPTOR* oldsd; LARGE_INTEGER time; BTRFS_TIME now; TRACE("(%p, %p, %p, %lx)\n", Vcb, FileObject, sd, *flags); if (Vcb->readonly) return STATUS_MEDIA_WRITE_PROTECTED; if (fcb->ads) { if (fileref && fileref->parent) fcb = fileref->parent->fcb; else { ERR("could not find parent fcb for stream\n"); return STATUS_INTERNAL_ERROR; } } if (!fcb || !ccb) return STATUS_INVALID_PARAMETER; ExAcquireResourceExclusiveLite(fcb->Header.Resource, true); if (is_subvol_readonly(fcb->subvol, Irp)) { Status = STATUS_ACCESS_DENIED; goto end; } oldsd = fcb->sd; Status = SeSetSecurityDescriptorInfo(NULL, flags, sd, (void**)&fcb->sd, PagedPool, IoGetFileObjectGenericMapping()); if (!NT_SUCCESS(Status)) { ERR("SeSetSecurityDescriptorInfo returned %08lx\n", Status); goto end; } ExFreePool(oldsd); KeQuerySystemTime(&time); win_time_to_unix(time, &now); fcb->inode_item.transid = Vcb->superblock.generation; if (!ccb->user_set_change_time) fcb->inode_item.st_ctime = now; fcb->inode_item.sequence++; fcb->sd_dirty = true; fcb->sd_deleted = false; fcb->inode_item_changed = true; fcb->subvol->root_item.ctransid = Vcb->superblock.generation; fcb->subvol->root_item.ctime = now; mark_fcb_dirty(fcb); queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_SECURITY, FILE_ACTION_MODIFIED, NULL); end: ExReleaseResourceLite(fcb->Header.Resource); return Status; } _Dispatch_type_(IRP_MJ_SET_SECURITY) _Function_class_(DRIVER_DISPATCH) NTSTATUS __stdcall drv_set_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS Status; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; ccb* ccb = FileObject ? FileObject->FsContext2 : NULL; device_extension* Vcb = DeviceObject->DeviceExtension; ULONG access_req = 0; bool top_level; FsRtlEnterFileSystem(); TRACE("set security\n"); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = STATUS_INVALID_DEVICE_REQUEST; goto end; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } if (!ccb) { ERR("no ccb\n"); Status = STATUS_INVALID_PARAMETER; goto end; } Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; if (IrpSp->Parameters.SetSecurity.SecurityInformation & OWNER_SECURITY_INFORMATION) { TRACE("OWNER_SECURITY_INFORMATION\n"); access_req |= WRITE_OWNER; } if (IrpSp->Parameters.SetSecurity.SecurityInformation & GROUP_SECURITY_INFORMATION) { TRACE("GROUP_SECURITY_INFORMATION\n"); access_req |= WRITE_OWNER; } if (IrpSp->Parameters.SetSecurity.SecurityInformation & DACL_SECURITY_INFORMATION) { TRACE("DACL_SECURITY_INFORMATION\n"); access_req |= WRITE_DAC; } if (IrpSp->Parameters.SetSecurity.SecurityInformation & SACL_SECURITY_INFORMATION) { TRACE("SACL_SECURITY_INFORMATION\n"); access_req |= ACCESS_SYSTEM_SECURITY; } if (Irp->RequestorMode == UserMode && (ccb->access & access_req) != access_req) { Status = STATUS_ACCESS_DENIED; WARN("insufficient privileges\n"); goto end; } Status = set_file_security(DeviceObject->DeviceExtension, FileObject, IrpSp->Parameters.SetSecurity.SecurityDescriptor, &IrpSp->Parameters.SetSecurity.SecurityInformation, Irp); end: Irp->IoStatus.Status = Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); TRACE("returning %08lx\n", Status); if (top_level) IoSetTopLevelIrp(NULL); FsRtlExitFileSystem(); return Status; } static bool search_for_gid(fcb* fcb, PSID sid) { LIST_ENTRY* le; le = gid_map_list.Flink; while (le != &gid_map_list) { gid_map* gm = CONTAINING_RECORD(le, gid_map, listentry); if (RtlEqualSid(sid, gm->sid)) { fcb->inode_item.st_gid = gm->gid; return true; } le = le->Flink; } return false; } void find_gid(struct _fcb* fcb, struct _fcb* parfcb, PSECURITY_SUBJECT_CONTEXT subjcont) { NTSTATUS Status; TOKEN_OWNER* to; TOKEN_PRIMARY_GROUP* tpg; TOKEN_GROUPS* tg; if (parfcb && parfcb->inode_item.st_mode & S_ISGID) { fcb->inode_item.st_gid = parfcb->inode_item.st_gid; return; } ExAcquireResourceSharedLite(&mapping_lock, true); if (!subjcont || !subjcont->PrimaryToken || IsListEmpty(&gid_map_list)) { ExReleaseResourceLite(&mapping_lock); return; } Status = SeQueryInformationToken(subjcont->PrimaryToken, TokenOwner, (void**)&to); if (!NT_SUCCESS(Status)) ERR("SeQueryInformationToken returned %08lx\n", Status); else { if (search_for_gid(fcb, to->Owner)) { ExReleaseResourceLite(&mapping_lock); ExFreePool(to); return; } ExFreePool(to); } Status = SeQueryInformationToken(subjcont->PrimaryToken, TokenPrimaryGroup, (void**)&tpg); if (!NT_SUCCESS(Status)) ERR("SeQueryInformationToken returned %08lx\n", Status); else { if (search_for_gid(fcb, tpg->PrimaryGroup)) { ExReleaseResourceLite(&mapping_lock); ExFreePool(tpg); return; } ExFreePool(tpg); } Status = SeQueryInformationToken(subjcont->PrimaryToken, TokenGroups, (void**)&tg); if (!NT_SUCCESS(Status)) ERR("SeQueryInformationToken returned %08lx\n", Status); else { ULONG i; for (i = 0; i < tg->GroupCount; i++) { if (search_for_gid(fcb, tg->Groups[i].Sid)) { ExReleaseResourceLite(&mapping_lock); ExFreePool(tg); return; } } ExFreePool(tg); } ExReleaseResourceLite(&mapping_lock); } NTSTATUS fcb_get_new_sd(fcb* fcb, file_ref* parfileref, ACCESS_STATE* as) { NTSTATUS Status; PSID owner; BOOLEAN defaulted; Status = SeAssignSecurityEx(parfileref ? parfileref->fcb->sd : NULL, as->SecurityDescriptor, (void**)&fcb->sd, NULL, fcb->type == BTRFS_TYPE_DIRECTORY, SEF_SACL_AUTO_INHERIT, &as->SubjectSecurityContext, IoGetFileObjectGenericMapping(), PagedPool); if (!NT_SUCCESS(Status)) { ERR("SeAssignSecurityEx returned %08lx\n", Status); return Status; } Status = RtlGetOwnerSecurityDescriptor(fcb->sd, &owner, &defaulted); if (!NT_SUCCESS(Status)) { ERR("RtlGetOwnerSecurityDescriptor returned %08lx\n", Status); fcb->inode_item.st_uid = UID_NOBODY; } else { fcb->inode_item.st_uid = sid_to_uid(owner); } find_gid(fcb, parfileref ? parfileref->fcb : NULL, &as->SubjectSecurityContext); return STATUS_SUCCESS; } ================================================ FILE: src/send.c ================================================ /* Copyright (c) Mark Harmstone 2017 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include "crc32c.h" typedef struct send_dir { LIST_ENTRY list_entry; uint64_t inode; bool dummy; BTRFS_TIME atime; BTRFS_TIME mtime; BTRFS_TIME ctime; struct send_dir* parent; uint16_t namelen; char* name; LIST_ENTRY deleted_children; } send_dir; typedef struct { LIST_ENTRY list_entry; uint64_t inode; bool dir; send_dir* sd; char tmpname[64]; } orphan; typedef struct { LIST_ENTRY list_entry; ULONG namelen; char name[1]; } deleted_child; typedef struct { LIST_ENTRY list_entry; send_dir* sd; uint16_t namelen; char name[1]; } ref; typedef struct { send_dir* sd; uint64_t last_child_inode; LIST_ENTRY list_entry; } pending_rmdir; typedef struct { uint64_t offset; LIST_ENTRY list_entry; ULONG datalen; EXTENT_DATA data; } send_ext; typedef struct { device_extension* Vcb; root* root; root* parent; uint8_t* data; ULONG datalen; ULONG num_clones; root** clones; LIST_ENTRY orphans; LIST_ENTRY dirs; LIST_ENTRY pending_rmdirs; KEVENT buffer_event; send_dir* root_dir; send_info* send; struct { uint64_t inode; bool deleting; bool new; uint64_t gen; uint64_t uid; uint64_t olduid; uint64_t gid; uint64_t oldgid; uint64_t mode; uint64_t oldmode; uint64_t size; uint64_t flags; BTRFS_TIME atime; BTRFS_TIME mtime; BTRFS_TIME ctime; bool file; char* path; orphan* o; send_dir* sd; LIST_ENTRY refs; LIST_ENTRY oldrefs; LIST_ENTRY exts; LIST_ENTRY oldexts; } lastinode; } send_context; #define MAX_SEND_WRITE 0xc000 // 48 KB #define SEND_BUFFER_LENGTH 0x100000 // 1 MB static NTSTATUS find_send_dir(send_context* context, uint64_t dir, uint64_t generation, send_dir** psd, bool* added_dummy); static NTSTATUS wait_for_flush(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2); static void send_command(send_context* context, uint16_t cmd) { btrfs_send_command* bsc = (btrfs_send_command*)&context->data[context->datalen]; bsc->cmd = cmd; bsc->csum = 0; context->datalen += sizeof(btrfs_send_command); } static void send_command_finish(send_context* context, ULONG pos) { btrfs_send_command* bsc = (btrfs_send_command*)&context->data[pos]; bsc->length = context->datalen - pos - sizeof(btrfs_send_command); bsc->csum = calc_crc32c(0, (uint8_t*)bsc, context->datalen - pos); } static void send_add_tlv(send_context* context, uint16_t type, void* data, uint16_t length) { btrfs_send_tlv* tlv = (btrfs_send_tlv*)&context->data[context->datalen]; tlv->type = type; tlv->length = length; if (length > 0 && data) RtlCopyMemory(&tlv[1], data, length); context->datalen += sizeof(btrfs_send_tlv) + length; } static char* uint64_to_char(uint64_t num, char* buf) { char *tmp, tmp2[20]; if (num == 0) { buf[0] = '0'; return buf + 1; } tmp = &tmp2[20]; while (num > 0) { tmp--; *tmp = (num % 10) + '0'; num /= 10; } RtlCopyMemory(buf, tmp, tmp2 + sizeof(tmp2) - tmp); return &buf[tmp2 + sizeof(tmp2) - tmp]; } static NTSTATUS get_orphan_name(send_context* context, uint64_t inode, uint64_t generation, char* name) { char *ptr, *ptr2; uint64_t index = 0; KEY searchkey; name[0] = 'o'; ptr = uint64_to_char(inode, &name[1]); *ptr = '-'; ptr++; ptr = uint64_to_char(generation, ptr); *ptr = '-'; ptr++; ptr2 = ptr; searchkey.obj_id = SUBVOL_ROOT_INODE; searchkey.obj_type = TYPE_DIR_ITEM; do { NTSTATUS Status; traverse_ptr tp; ptr = uint64_to_char(index, ptr); *ptr = 0; searchkey.offset = calc_crc32c(0xfffffffe, (uint8_t*)name, (ULONG)(ptr - name)); Status = find_item(context->Vcb, context->root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (!keycmp(searchkey, tp.item->key)) goto cont; if (context->parent) { Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (!keycmp(searchkey, tp.item->key)) goto cont; } return STATUS_SUCCESS; cont: index++; ptr = ptr2; } while (true); } static void add_orphan(send_context* context, orphan* o) { LIST_ENTRY* le; le = context->orphans.Flink; while (le != &context->orphans) { orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry); if (o2->inode > o->inode) { InsertHeadList(o2->list_entry.Blink, &o->list_entry); return; } le = le->Flink; } InsertTailList(&context->orphans, &o->list_entry); } static NTSTATUS send_read_symlink(send_context* context, uint64_t inode, char** link, uint16_t* linklen) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; EXTENT_DATA* ed; searchkey.obj_id = inode; searchkey.obj_type = TYPE_EXTENT_DATA; searchkey.offset = 0; Status = find_item(context->Vcb, context->root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (keycmp(tp.item->key, searchkey)) { ERR("could not find (%I64x,%x,%I64x)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); return STATUS_INTERNAL_ERROR; } if (tp.item->size < sizeof(EXTENT_DATA)) { 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_DATA)); return STATUS_INTERNAL_ERROR; } ed = (EXTENT_DATA*)tp.item->data; if (ed->type != EXTENT_TYPE_INLINE) { WARN("symlink data was not inline, returning blank string\n"); *link = NULL; *linklen = 0; return STATUS_SUCCESS; } if (tp.item->size < offsetof(EXTENT_DATA, data[0]) + ed->decoded_size) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %I64u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, offsetof(EXTENT_DATA, data[0]) + ed->decoded_size); return STATUS_INTERNAL_ERROR; } *link = (char*)ed->data; *linklen = (uint16_t)ed->decoded_size; return STATUS_SUCCESS; } static NTSTATUS send_inode(send_context* context, traverse_ptr* tp, traverse_ptr* tp2) { NTSTATUS Status; INODE_ITEM* ii; if (tp2 && !tp) { INODE_ITEM* ii2 = (INODE_ITEM*)tp2->item->data; if (tp2->item->size < sizeof(INODE_ITEM)) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %Iu\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset, tp2->item->size, sizeof(INODE_ITEM)); return STATUS_INTERNAL_ERROR; } context->lastinode.inode = tp2->item->key.obj_id; context->lastinode.deleting = true; context->lastinode.gen = ii2->generation; context->lastinode.mode = ii2->st_mode; context->lastinode.flags = ii2->flags; context->lastinode.o = NULL; context->lastinode.sd = NULL; return STATUS_SUCCESS; } ii = (INODE_ITEM*)tp->item->data; if (tp->item->size < sizeof(INODE_ITEM)) { 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)); return STATUS_INTERNAL_ERROR; } context->lastinode.inode = tp->item->key.obj_id; context->lastinode.deleting = false; context->lastinode.gen = ii->generation; context->lastinode.uid = ii->st_uid; context->lastinode.gid = ii->st_gid; context->lastinode.mode = ii->st_mode; context->lastinode.size = ii->st_size; context->lastinode.atime = ii->st_atime; context->lastinode.mtime = ii->st_mtime; context->lastinode.ctime = ii->st_ctime; context->lastinode.flags = ii->flags; context->lastinode.file = false; context->lastinode.o = NULL; context->lastinode.sd = NULL; if (context->lastinode.path) { ExFreePool(context->lastinode.path); context->lastinode.path = NULL; } if (tp2) { INODE_ITEM* ii2 = (INODE_ITEM*)tp2->item->data; LIST_ENTRY* le; if (tp2->item->size < sizeof(INODE_ITEM)) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %Iu\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset, tp2->item->size, sizeof(INODE_ITEM)); return STATUS_INTERNAL_ERROR; } context->lastinode.oldmode = ii2->st_mode; context->lastinode.olduid = ii2->st_uid; context->lastinode.oldgid = ii2->st_gid; if ((ii2->st_mode & __S_IFREG) == __S_IFREG && (ii2->st_mode & __S_IFLNK) != __S_IFLNK && (ii2->st_mode & __S_IFSOCK) != __S_IFSOCK) context->lastinode.file = true; context->lastinode.new = false; le = context->orphans.Flink; while (le != &context->orphans) { orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry); if (o2->inode == tp->item->key.obj_id) { context->lastinode.o = o2; break; } else if (o2->inode > tp->item->key.obj_id) break; le = le->Flink; } } else context->lastinode.new = true; if (tp->item->key.obj_id == SUBVOL_ROOT_INODE) { send_dir* sd; Status = find_send_dir(context, tp->item->key.obj_id, ii->generation, &sd, NULL); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; } sd->atime = ii->st_atime; sd->mtime = ii->st_mtime; sd->ctime = ii->st_ctime; context->root_dir = sd; } else if (!tp2) { ULONG pos = context->datalen; uint16_t cmd; send_dir* sd; char name[64]; orphan* o; // skip creating orphan directory if we've already done so if (ii->st_mode & __S_IFDIR) { LIST_ENTRY* le; le = context->orphans.Flink; while (le != &context->orphans) { orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry); if (o2->inode == tp->item->key.obj_id) { context->lastinode.o = o2; o2->sd->atime = ii->st_atime; o2->sd->mtime = ii->st_mtime; o2->sd->ctime = ii->st_ctime; o2->sd->dummy = false; return STATUS_SUCCESS; } else if (o2->inode > tp->item->key.obj_id) break; le = le->Flink; } } if ((ii->st_mode & __S_IFSOCK) == __S_IFSOCK) cmd = BTRFS_SEND_CMD_MKSOCK; else if ((ii->st_mode & __S_IFLNK) == __S_IFLNK) cmd = BTRFS_SEND_CMD_SYMLINK; else if ((ii->st_mode & __S_IFCHR) == __S_IFCHR || (ii->st_mode & __S_IFBLK) == __S_IFBLK) cmd = BTRFS_SEND_CMD_MKNOD; else if ((ii->st_mode & __S_IFDIR) == __S_IFDIR) cmd = BTRFS_SEND_CMD_MKDIR; else if ((ii->st_mode & __S_IFIFO) == __S_IFIFO) cmd = BTRFS_SEND_CMD_MKFIFO; else { cmd = BTRFS_SEND_CMD_MKFILE; context->lastinode.file = true; } send_command(context, cmd); Status = get_orphan_name(context, tp->item->key.obj_id, ii->generation, name); if (!NT_SUCCESS(Status)) { ERR("get_orphan_name returned %08lx\n", Status); return Status; } send_add_tlv(context, BTRFS_SEND_TLV_PATH, name, (uint16_t)strlen(name)); send_add_tlv(context, BTRFS_SEND_TLV_INODE, &tp->item->key.obj_id, sizeof(uint64_t)); if (cmd == BTRFS_SEND_CMD_MKNOD || cmd == BTRFS_SEND_CMD_MKFIFO || cmd == BTRFS_SEND_CMD_MKSOCK) { uint64_t rdev = makedev((ii->st_rdev & 0xFFFFFFFFFFF) >> 20, ii->st_rdev & 0xFFFFF), mode = ii->st_mode; send_add_tlv(context, BTRFS_SEND_TLV_RDEV, &rdev, sizeof(uint64_t)); send_add_tlv(context, BTRFS_SEND_TLV_MODE, &mode, sizeof(uint64_t)); } else if (cmd == BTRFS_SEND_CMD_SYMLINK && ii->st_size > 0) { char* link; uint16_t linklen; Status = send_read_symlink(context, tp->item->key.obj_id, &link, &linklen); if (!NT_SUCCESS(Status)) { ERR("send_read_symlink returned %08lx\n", Status); return Status; } send_add_tlv(context, BTRFS_SEND_TLV_PATH_LINK, link, linklen); } send_command_finish(context, pos); if (ii->st_mode & __S_IFDIR) { Status = find_send_dir(context, tp->item->key.obj_id, ii->generation, &sd, NULL); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; } sd->dummy = false; } else sd = NULL; context->lastinode.sd = sd; o = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG); if (!o) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } o->inode = tp->item->key.obj_id; o->dir = (ii->st_mode & __S_IFDIR && ii->st_size > 0) ? true : false; strcpy(o->tmpname, name); o->sd = sd; add_orphan(context, o); context->lastinode.path = ExAllocatePoolWithTag(PagedPool, strlen(o->tmpname) + 1, ALLOC_TAG); if (!context->lastinode.path) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } strcpy(context->lastinode.path, o->tmpname); context->lastinode.o = o; } return STATUS_SUCCESS; } static 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) { LIST_ENTRY* le; send_dir* sd = ExAllocatePoolWithTag(PagedPool, sizeof(send_dir), ALLOC_TAG); if (!sd) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } sd->inode = inode; sd->dummy = dummy; sd->parent = parent; if (!dummy) { sd->atime = context->lastinode.atime; sd->mtime = context->lastinode.mtime; sd->ctime = context->lastinode.ctime; } if (namelen > 0) { sd->name = ExAllocatePoolWithTag(PagedPool, namelen, ALLOC_TAG); if (!sd->name) { ERR("out of memory\n"); ExFreePool(sd); return STATUS_INSUFFICIENT_RESOURCES; } memcpy(sd->name, name, namelen); } else sd->name = NULL; sd->namelen = namelen; InitializeListHead(&sd->deleted_children); if (lastentry) InsertHeadList(lastentry, &sd->list_entry); else { le = context->dirs.Flink; while (le != &context->dirs) { send_dir* sd2 = CONTAINING_RECORD(le, send_dir, list_entry); if (sd2->inode > sd->inode) { InsertHeadList(sd2->list_entry.Blink, &sd->list_entry); if (psd) *psd = sd; return STATUS_SUCCESS; } le = le->Flink; } InsertTailList(&context->dirs, &sd->list_entry); } if (psd) *psd = sd; return STATUS_SUCCESS; } static __inline uint16_t find_path_len(send_dir* parent, uint16_t namelen) { uint16_t len = namelen; while (parent && parent->namelen > 0) { len += parent->namelen + 1; parent = parent->parent; } return len; } static void find_path(char* path, send_dir* parent, char* name, ULONG namelen) { ULONG len = namelen; RtlCopyMemory(path, name, namelen); while (parent && parent->namelen > 0) { RtlMoveMemory(path + parent->namelen + 1, path, len); RtlCopyMemory(path, parent->name, parent->namelen); path[parent->namelen] = '/'; len += parent->namelen + 1; parent = parent->parent; } } static void send_add_tlv_path(send_context* context, uint16_t type, send_dir* parent, char* name, uint16_t namelen) { uint16_t len = find_path_len(parent, namelen); send_add_tlv(context, type, NULL, len); if (len > 0) find_path((char*)&context->data[context->datalen - len], parent, name, namelen); } static NTSTATUS found_path(send_context* context, send_dir* parent, char* name, uint16_t namelen) { ULONG pos = context->datalen; if (context->lastinode.o) { send_command(context, BTRFS_SEND_CMD_RENAME); send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, context->root_dir, context->lastinode.o->tmpname, (uint16_t)strlen(context->lastinode.o->tmpname)); send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, parent, name, namelen); send_command_finish(context, pos); } else { send_command(context, BTRFS_SEND_CMD_LINK); send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, parent, name, namelen); send_add_tlv(context, BTRFS_SEND_TLV_PATH_LINK, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); send_command_finish(context, pos); } if (context->lastinode.o) { uint16_t pathlen; if (context->lastinode.o->sd) { if (context->lastinode.o->sd->name) ExFreePool(context->lastinode.o->sd->name); context->lastinode.o->sd->name = ExAllocatePoolWithTag(PagedPool, namelen, ALLOC_TAG); if (!context->lastinode.o->sd->name) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(context->lastinode.o->sd->name, name, namelen); context->lastinode.o->sd->namelen = namelen; context->lastinode.o->sd->parent = parent; } if (context->lastinode.path) ExFreePool(context->lastinode.path); pathlen = find_path_len(parent, namelen); context->lastinode.path = ExAllocatePoolWithTag(PagedPool, pathlen + 1, ALLOC_TAG); if (!context->lastinode.path) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } find_path(context->lastinode.path, parent, name, namelen); context->lastinode.path[pathlen] = 0; RemoveEntryList(&context->lastinode.o->list_entry); ExFreePool(context->lastinode.o); context->lastinode.o = NULL; } return STATUS_SUCCESS; } static void send_utimes_command_dir(send_context* context, send_dir* sd, BTRFS_TIME* atime, BTRFS_TIME* mtime, BTRFS_TIME* ctime) { ULONG pos = context->datalen; send_command(context, BTRFS_SEND_CMD_UTIMES); send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, sd->parent, sd->name, sd->namelen); send_add_tlv(context, BTRFS_SEND_TLV_ATIME, atime, sizeof(BTRFS_TIME)); send_add_tlv(context, BTRFS_SEND_TLV_MTIME, mtime, sizeof(BTRFS_TIME)); send_add_tlv(context, BTRFS_SEND_TLV_CTIME, ctime, sizeof(BTRFS_TIME)); send_command_finish(context, pos); } static NTSTATUS find_send_dir(send_context* context, uint64_t dir, uint64_t generation, send_dir** psd, bool* added_dummy) { NTSTATUS Status; LIST_ENTRY* le; char name[64]; le = context->dirs.Flink; while (le != &context->dirs) { send_dir* sd2 = CONTAINING_RECORD(le, send_dir, list_entry); if (sd2->inode > dir) break; else if (sd2->inode == dir) { *psd = sd2; if (added_dummy) *added_dummy = false; return STATUS_SUCCESS; } le = le->Flink; } if (dir == SUBVOL_ROOT_INODE) { Status = send_add_dir(context, dir, NULL, NULL, 0, false, le, psd); if (!NT_SUCCESS(Status)) { ERR("send_add_dir returned %08lx\n", Status); return Status; } if (added_dummy) *added_dummy = false; return STATUS_SUCCESS; } if (context->parent) { KEY searchkey; traverse_ptr tp; searchkey.obj_id = dir; searchkey.obj_type = TYPE_INODE_REF; // directories should never have an extiref searchkey.offset = 0xffffffffffffffff; Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { INODE_REF* ir = (INODE_REF*)tp.item->data; send_dir* parent; if (tp.item->size < sizeof(INODE_REF) || tp.item->size < offsetof(INODE_REF, name[0]) + ir->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); return STATUS_INTERNAL_ERROR; } if (tp.item->key.offset == SUBVOL_ROOT_INODE) parent = context->root_dir; else { Status = find_send_dir(context, tp.item->key.offset, generation, &parent, NULL); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; } } Status = send_add_dir(context, dir, parent, ir->name, ir->n, true, NULL, psd); if (!NT_SUCCESS(Status)) { ERR("send_add_dir returned %08lx\n", Status); return Status; } if (added_dummy) *added_dummy = false; return STATUS_SUCCESS; } } Status = get_orphan_name(context, dir, generation, name); if (!NT_SUCCESS(Status)) { ERR("get_orphan_name returned %08lx\n", Status); return Status; } Status = send_add_dir(context, dir, NULL, name, (uint16_t)strlen(name), true, le, psd); if (!NT_SUCCESS(Status)) { ERR("send_add_dir returned %08lx\n", Status); return Status; } if (added_dummy) *added_dummy = true; return STATUS_SUCCESS; } static NTSTATUS send_inode_ref(send_context* context, traverse_ptr* tp, bool tree2) { NTSTATUS Status; uint64_t inode = tp ? tp->item->key.obj_id : 0, dir = tp ? tp->item->key.offset : 0; LIST_ENTRY* le; INODE_REF* ir; uint16_t len; send_dir* sd = NULL; orphan* o2 = NULL; if (inode == dir) // root return STATUS_SUCCESS; if (tp->item->size < sizeof(INODE_REF)) { 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)); return STATUS_INTERNAL_ERROR; } if (dir != SUBVOL_ROOT_INODE) { bool added_dummy; Status = find_send_dir(context, dir, context->root->root_item.ctransid, &sd, &added_dummy); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; } // directory has higher inode number than file, so might need to be created if (added_dummy) { bool found = false; le = context->orphans.Flink; while (le != &context->orphans) { o2 = CONTAINING_RECORD(le, orphan, list_entry); if (o2->inode == dir) { found = true; break; } else if (o2->inode > dir) break; le = le->Flink; } if (!found) { ULONG pos = context->datalen; send_command(context, BTRFS_SEND_CMD_MKDIR); send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, NULL, sd->name, sd->namelen); send_add_tlv(context, BTRFS_SEND_TLV_INODE, &dir, sizeof(uint64_t)); send_command_finish(context, pos); o2 = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG); if (!o2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } o2->inode = dir; o2->dir = true; memcpy(o2->tmpname, sd->name, sd->namelen); o2->tmpname[sd->namelen] = 0; o2->sd = sd; add_orphan(context, o2); } } } else sd = context->root_dir; len = tp->item->size; ir = (INODE_REF*)tp->item->data; while (len > 0) { ref* r; if (len < sizeof(INODE_REF) || len < offsetof(INODE_REF, name[0]) + ir->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset); return STATUS_INTERNAL_ERROR; } r = ExAllocatePoolWithTag(PagedPool, offsetof(ref, name[0]) + ir->n, ALLOC_TAG); if (!r) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } r->sd = sd; r->namelen = ir->n; RtlCopyMemory(r->name, ir->name, ir->n); InsertTailList(tree2 ? &context->lastinode.oldrefs : &context->lastinode.refs, &r->list_entry); len -= (uint16_t)offsetof(INODE_REF, name[0]) + ir->n; ir = (INODE_REF*)&ir->name[ir->n]; } return STATUS_SUCCESS; } static NTSTATUS send_inode_extref(send_context* context, traverse_ptr* tp, bool tree2) { INODE_EXTREF* ier; uint16_t len; if (tp->item->size < sizeof(INODE_EXTREF)) { 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_EXTREF)); return STATUS_INTERNAL_ERROR; } len = tp->item->size; ier = (INODE_EXTREF*)tp->item->data; while (len > 0) { NTSTATUS Status; send_dir* sd = NULL; orphan* o2 = NULL; ref* r; if (len < sizeof(INODE_EXTREF) || len < offsetof(INODE_EXTREF, name[0]) + ier->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset); return STATUS_INTERNAL_ERROR; } if (ier->dir != SUBVOL_ROOT_INODE) { LIST_ENTRY* le; bool added_dummy; Status = find_send_dir(context, ier->dir, context->root->root_item.ctransid, &sd, &added_dummy); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; } // directory has higher inode number than file, so might need to be created if (added_dummy) { bool found = false; le = context->orphans.Flink; while (le != &context->orphans) { o2 = CONTAINING_RECORD(le, orphan, list_entry); if (o2->inode == ier->dir) { found = true; break; } else if (o2->inode > ier->dir) break; le = le->Flink; } if (!found) { ULONG pos = context->datalen; send_command(context, BTRFS_SEND_CMD_MKDIR); send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, NULL, sd->name, sd->namelen); send_add_tlv(context, BTRFS_SEND_TLV_INODE, &ier->dir, sizeof(uint64_t)); send_command_finish(context, pos); o2 = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG); if (!o2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } o2->inode = ier->dir; o2->dir = true; memcpy(o2->tmpname, sd->name, sd->namelen); o2->tmpname[sd->namelen] = 0; o2->sd = sd; add_orphan(context, o2); } } } else sd = context->root_dir; r = ExAllocatePoolWithTag(PagedPool, offsetof(ref, name[0]) + ier->n, ALLOC_TAG); if (!r) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } r->sd = sd; r->namelen = ier->n; RtlCopyMemory(r->name, ier->name, ier->n); InsertTailList(tree2 ? &context->lastinode.oldrefs : &context->lastinode.refs, &r->list_entry); len -= (uint16_t)offsetof(INODE_EXTREF, name[0]) + ier->n; ier = (INODE_EXTREF*)&ier->name[ier->n]; } return STATUS_SUCCESS; } static void send_subvol_header(send_context* context, root* r, file_ref* fr) { ULONG pos = context->datalen; send_command(context, context->parent ? BTRFS_SEND_CMD_SNAPSHOT : BTRFS_SEND_CMD_SUBVOL); send_add_tlv(context, BTRFS_SEND_TLV_PATH, fr->dc->utf8.Buffer, fr->dc->utf8.Length); 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)); send_add_tlv(context, BTRFS_SEND_TLV_TRANSID, &r->root_item.ctransid, sizeof(uint64_t)); if (context->parent) { send_add_tlv(context, BTRFS_SEND_TLV_CLONE_UUID, context->parent->root_item.rtransid == 0 ? &context->parent->root_item.uuid : &context->parent->root_item.received_uuid, sizeof(BTRFS_UUID)); send_add_tlv(context, BTRFS_SEND_TLV_CLONE_CTRANSID, &context->parent->root_item.ctransid, sizeof(uint64_t)); } send_command_finish(context, pos); } static void send_chown_command(send_context* context, char* path, uint64_t uid, uint64_t gid) { ULONG pos = context->datalen; send_command(context, BTRFS_SEND_CMD_CHOWN); send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_UID, &uid, sizeof(uint64_t)); send_add_tlv(context, BTRFS_SEND_TLV_GID, &gid, sizeof(uint64_t)); send_command_finish(context, pos); } static void send_chmod_command(send_context* context, char* path, uint64_t mode) { ULONG pos = context->datalen; send_command(context, BTRFS_SEND_CMD_CHMOD); mode &= 07777; send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_MODE, &mode, sizeof(uint64_t)); send_command_finish(context, pos); } static void send_utimes_command(send_context* context, char* path, BTRFS_TIME* atime, BTRFS_TIME* mtime, BTRFS_TIME* ctime) { ULONG pos = context->datalen; send_command(context, BTRFS_SEND_CMD_UTIMES); send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_ATIME, atime, sizeof(BTRFS_TIME)); send_add_tlv(context, BTRFS_SEND_TLV_MTIME, mtime, sizeof(BTRFS_TIME)); send_add_tlv(context, BTRFS_SEND_TLV_CTIME, ctime, sizeof(BTRFS_TIME)); send_command_finish(context, pos); } static void send_truncate_command(send_context* context, char* path, uint64_t size) { ULONG pos = context->datalen; send_command(context, BTRFS_SEND_CMD_TRUNCATE); send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_SIZE, &size, sizeof(uint64_t)); send_command_finish(context, pos); } static NTSTATUS send_unlink_command(send_context* context, send_dir* parent, uint16_t namelen, char* name) { ULONG pos = context->datalen; uint16_t pathlen; send_command(context, BTRFS_SEND_CMD_UNLINK); pathlen = find_path_len(parent, namelen); send_add_tlv(context, BTRFS_SEND_TLV_PATH, NULL, pathlen); find_path((char*)&context->data[context->datalen - pathlen], parent, name, namelen); send_command_finish(context, pos); return STATUS_SUCCESS; } static void send_rmdir_command(send_context* context, uint16_t pathlen, char* path) { ULONG pos = context->datalen; send_command(context, BTRFS_SEND_CMD_RMDIR); send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, pathlen); send_command_finish(context, pos); } static NTSTATUS get_dir_last_child(send_context* context, uint64_t* last_inode) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; *last_inode = 0; searchkey.obj_id = context->lastinode.inode; searchkey.obj_type = TYPE_DIR_INDEX; searchkey.offset = 2; Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } do { traverse_ptr next_tp; if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { DIR_ITEM* di = (DIR_ITEM*)tp.item->data; if (tp.item->size < sizeof(DIR_ITEM) || tp.item->size < offsetof(DIR_ITEM, name[0]) + di->m + di->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); return STATUS_INTERNAL_ERROR; } if (di->key.obj_type == TYPE_INODE_ITEM) *last_inode = max(*last_inode, di->key.obj_id); } else break; if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else break; } while (true); return STATUS_SUCCESS; } static NTSTATUS add_pending_rmdir(send_context* context, uint64_t last_inode) { pending_rmdir* pr; LIST_ENTRY* le; pr = ExAllocatePoolWithTag(PagedPool, sizeof(pending_rmdir), ALLOC_TAG); if (!pr) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } pr->sd = context->lastinode.sd; pr->last_child_inode = last_inode; le = context->pending_rmdirs.Flink; while (le != &context->pending_rmdirs) { pending_rmdir* pr2 = CONTAINING_RECORD(le, pending_rmdir, list_entry); if (pr2->last_child_inode > pr->last_child_inode) { InsertHeadList(pr2->list_entry.Blink, &pr->list_entry); return STATUS_SUCCESS; } le = le->Flink; } InsertTailList(&context->pending_rmdirs, &pr->list_entry); return STATUS_SUCCESS; } static NTSTATUS look_for_collision(send_context* context, send_dir* sd, char* name, ULONG namelen, uint64_t* inode, bool* dir) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; DIR_ITEM* di; uint16_t len; searchkey.obj_id = sd->inode; searchkey.obj_type = TYPE_DIR_ITEM; searchkey.offset = calc_crc32c(0xfffffffe, (uint8_t*)name, namelen); Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (keycmp(tp.item->key, searchkey)) return STATUS_SUCCESS; di = (DIR_ITEM*)tp.item->data; len = tp.item->size; do { if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); return STATUS_INTERNAL_ERROR; } if (di->n == namelen && RtlCompareMemory(di->name, name, namelen) == namelen) { *inode = di->key.obj_type == TYPE_INODE_ITEM ? di->key.obj_id : 0; *dir = di->type == BTRFS_TYPE_DIRECTORY ? true: false; return STATUS_OBJECT_NAME_COLLISION; } di = (DIR_ITEM*)&di->name[di->m + di->n]; len -= (uint16_t)offsetof(DIR_ITEM, name[0]) + di->m + di->n; } while (len > 0); return STATUS_SUCCESS; } static NTSTATUS make_file_orphan(send_context* context, uint64_t inode, bool dir, uint64_t generation, ref* r) { NTSTATUS Status; ULONG pos = context->datalen; send_dir* sd = NULL; orphan* o; LIST_ENTRY* le; char name[64]; if (!dir) { deleted_child* dc; dc = ExAllocatePoolWithTag(PagedPool, offsetof(deleted_child, name[0]) + r->namelen, ALLOC_TAG); if (!dc) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } dc->namelen = r->namelen; RtlCopyMemory(dc->name, r->name, r->namelen); InsertTailList(&r->sd->deleted_children, &dc->list_entry); } le = context->orphans.Flink; while (le != &context->orphans) { orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry); if (o2->inode == inode) { send_command(context, BTRFS_SEND_CMD_UNLINK); send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, r->sd, r->name, r->namelen); send_command_finish(context, pos); return STATUS_SUCCESS; } else if (o2->inode > inode) break; le = le->Flink; } Status = get_orphan_name(context, inode, generation, name); if (!NT_SUCCESS(Status)) { ERR("get_orphan_name returned %08lx\n", Status); return Status; } if (dir) { Status = find_send_dir(context, inode, generation, &sd, NULL); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; } sd->dummy = true; send_command(context, BTRFS_SEND_CMD_RENAME); send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, r->sd, r->name, r->namelen); send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, context->root_dir, name, (uint16_t)strlen(name)); send_command_finish(context, pos); if (sd->name) ExFreePool(sd->name); sd->namelen = (uint16_t)strlen(name); sd->name = ExAllocatePoolWithTag(PagedPool, sd->namelen, ALLOC_TAG); if (!sd->name) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(sd->name, name, sd->namelen); sd->parent = context->root_dir; } else { send_command(context, BTRFS_SEND_CMD_RENAME); send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, r->sd, r->name, r->namelen); send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, context->root_dir, name, (uint16_t)strlen(name)); send_command_finish(context, pos); } o = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG); if (!o) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } o->inode = inode; o->dir = true; strcpy(o->tmpname, name); o->sd = sd; add_orphan(context, o); return STATUS_SUCCESS; } static NTSTATUS flush_refs(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) { NTSTATUS Status; LIST_ENTRY* le; ref *nameref = NULL, *nameref2 = NULL; if (context->lastinode.mode & __S_IFDIR) { // directory ref* r = IsListEmpty(&context->lastinode.refs) ? NULL : CONTAINING_RECORD(context->lastinode.refs.Flink, ref, list_entry); ref* or = IsListEmpty(&context->lastinode.oldrefs) ? NULL : CONTAINING_RECORD(context->lastinode.oldrefs.Flink, ref, list_entry); if (or && !context->lastinode.o) { ULONG len = find_path_len(or->sd, or->namelen); context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG); if (!context->lastinode.path) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } find_path(context->lastinode.path, or->sd, or->name, or->namelen); context->lastinode.path[len] = 0; if (!context->lastinode.sd) { Status = find_send_dir(context, context->lastinode.inode, context->lastinode.gen, &context->lastinode.sd, false); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; } } } if (r && or) { uint64_t inode; bool dir; Status = look_for_collision(context, r->sd, r->name, r->namelen, &inode, &dir); if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_COLLISION) { ERR("look_for_collision returned %08lx\n", Status); return Status; } if (Status == STATUS_OBJECT_NAME_COLLISION && inode > context->lastinode.inode) { Status = make_file_orphan(context, inode, dir, context->parent->root_item.ctransid, r); if (!NT_SUCCESS(Status)) { ERR("make_file_orphan returned %08lx\n", Status); return Status; } } if (context->lastinode.o) { Status = found_path(context, r->sd, r->name, r->namelen); if (!NT_SUCCESS(Status)) { ERR("found_path returned %08lx\n", Status); return Status; } if (!r->sd->dummy) send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime); } else if (r->sd != or->sd || r->namelen != or->namelen || RtlCompareMemory(r->name, or->name, r->namelen) != r->namelen) { // moved or renamed ULONG pos = context->datalen, len; send_command(context, BTRFS_SEND_CMD_RENAME); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, (uint16_t)strlen(context->lastinode.path)); send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, r->sd, r->name, r->namelen); send_command_finish(context, pos); if (!r->sd->dummy) send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime); if (context->lastinode.sd->name) ExFreePool(context->lastinode.sd->name); context->lastinode.sd->name = ExAllocatePoolWithTag(PagedPool, r->namelen, ALLOC_TAG); if (!context->lastinode.sd->name) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(context->lastinode.sd->name, r->name, r->namelen); context->lastinode.sd->parent = r->sd; if (context->lastinode.path) ExFreePool(context->lastinode.path); len = find_path_len(r->sd, r->namelen); context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG); if (!context->lastinode.path) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } find_path(context->lastinode.path, r->sd, r->name, r->namelen); context->lastinode.path[len] = 0; } } else if (r && !or) { // new Status = found_path(context, r->sd, r->name, r->namelen); if (!NT_SUCCESS(Status)) { ERR("found_path returned %08lx\n", Status); return Status; } if (!r->sd->dummy) send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime); } else { // deleted uint64_t last_inode; Status = get_dir_last_child(context, &last_inode); if (!NT_SUCCESS(Status)) { ERR("get_dir_last_child returned %08lx\n", Status); return Status; } if (last_inode <= context->lastinode.inode) { send_rmdir_command(context, (uint16_t)strlen(context->lastinode.path), context->lastinode.path); if (!or->sd->dummy) send_utimes_command_dir(context, or->sd, &or->sd->atime, &or->sd->mtime, &or->sd->ctime); } else { char name[64]; ULONG pos = context->datalen; Status = get_orphan_name(context, context->lastinode.inode, context->lastinode.gen, name); if (!NT_SUCCESS(Status)) { ERR("get_orphan_name returned %08lx\n", Status); return Status; } send_command(context, BTRFS_SEND_CMD_RENAME); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, (uint16_t)strlen(context->lastinode.path)); send_add_tlv(context, BTRFS_SEND_TLV_PATH_TO, name, (uint16_t)strlen(name)); send_command_finish(context, pos); if (context->lastinode.sd->name) ExFreePool(context->lastinode.sd->name); context->lastinode.sd->name = ExAllocatePoolWithTag(PagedPool, strlen(name), ALLOC_TAG); if (!context->lastinode.sd->name) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(context->lastinode.sd->name, name, strlen(name)); context->lastinode.sd->namelen = (uint16_t)strlen(name); context->lastinode.sd->dummy = true; context->lastinode.sd->parent = NULL; send_utimes_command(context, NULL, &context->root_dir->atime, &context->root_dir->mtime, &context->root_dir->ctime); Status = add_pending_rmdir(context, last_inode); if (!NT_SUCCESS(Status)) { ERR("add_pending_rmdir returned %08lx\n", Status); return Status; } } } while (!IsListEmpty(&context->lastinode.refs)) { r = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.refs), ref, list_entry); ExFreePool(r); } while (!IsListEmpty(&context->lastinode.oldrefs)) { or = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldrefs), ref, list_entry); ExFreePool(or); } return STATUS_SUCCESS; } else { if (!IsListEmpty(&context->lastinode.oldrefs)) { ref* or = CONTAINING_RECORD(context->lastinode.oldrefs.Flink, ref, list_entry); ULONG len = find_path_len(or->sd, or->namelen); context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG); if (!context->lastinode.path) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } find_path(context->lastinode.path, or->sd, or->name, or->namelen); context->lastinode.path[len] = 0; nameref = or; } // remove unchanged refs le = context->lastinode.oldrefs.Flink; while (le != &context->lastinode.oldrefs) { ref* or = CONTAINING_RECORD(le, ref, list_entry); LIST_ENTRY* le2; bool matched = false; le2 = context->lastinode.refs.Flink; while (le2 != &context->lastinode.refs) { ref* r = CONTAINING_RECORD(le2, ref, list_entry); if (r->sd == or->sd && r->namelen == or->namelen && RtlCompareMemory(r->name, or->name, r->namelen) == r->namelen) { RemoveEntryList(&r->list_entry); ExFreePool(r); matched = true; break; } le2 = le2->Flink; } if (matched) { le = le->Flink; RemoveEntryList(&or->list_entry); ExFreePool(or); continue; } le = le->Flink; } while (!IsListEmpty(&context->lastinode.refs)) { ref* r = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.refs), ref, list_entry); uint64_t inode; bool dir; if (context->parent) { Status = look_for_collision(context, r->sd, r->name, r->namelen, &inode, &dir); if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_COLLISION) { ERR("look_for_collision returned %08lx\n", Status); return Status; } if (Status == STATUS_OBJECT_NAME_COLLISION && inode > context->lastinode.inode) { Status = make_file_orphan(context, inode, dir, context->lastinode.gen, r); if (!NT_SUCCESS(Status)) { ERR("make_file_orphan returned %08lx\n", Status); return Status; } } } if (context->datalen > SEND_BUFFER_LENGTH) { Status = wait_for_flush(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("wait_for_flush returned %08lx\n", Status); return Status; } if (context->send->cancelling) return STATUS_SUCCESS; } Status = found_path(context, r->sd, r->name, r->namelen); if (!NT_SUCCESS(Status)) { ERR("found_path returned %08lx\n", Status); return Status; } if (!r->sd->dummy) send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime); if (nameref && !nameref2) nameref2 = r; else ExFreePool(r); } while (!IsListEmpty(&context->lastinode.oldrefs)) { ref* or = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldrefs), ref, list_entry); bool deleted = false; le = or->sd->deleted_children.Flink; while (le != &or->sd->deleted_children) { deleted_child* dc = CONTAINING_RECORD(le, deleted_child, list_entry); if (dc->namelen == or->namelen && RtlCompareMemory(dc->name, or->name, or->namelen) == or->namelen) { RemoveEntryList(&dc->list_entry); ExFreePool(dc); deleted = true; break; } le = le->Flink; } if (!deleted) { if (context->datalen > SEND_BUFFER_LENGTH) { Status = wait_for_flush(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("wait_for_flush returned %08lx\n", Status); return Status; } if (context->send->cancelling) return STATUS_SUCCESS; } Status = send_unlink_command(context, or->sd, or->namelen, or->name); if (!NT_SUCCESS(Status)) { ERR("send_unlink_command returned %08lx\n", Status); return Status; } if (!or->sd->dummy) send_utimes_command_dir(context, or->sd, &or->sd->atime, &or->sd->mtime, &or->sd->ctime); } if (or == nameref && nameref2) { uint16_t len = find_path_len(nameref2->sd, nameref2->namelen); if (context->lastinode.path) ExFreePool(context->lastinode.path); context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG); if (!context->lastinode.path) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } find_path(context->lastinode.path, nameref2->sd, nameref2->name, nameref2->namelen); context->lastinode.path[len] = 0; ExFreePool(nameref2); } ExFreePool(or); } } return STATUS_SUCCESS; } static NTSTATUS wait_for_flush(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) { NTSTATUS Status; KEY key1, key2; if (tp1) key1 = tp1->item->key; if (tp2) key2 = tp2->item->key; ExReleaseResourceLite(&context->Vcb->tree_lock); KeClearEvent(&context->send->cleared_event); KeSetEvent(&context->buffer_event, 0, true); KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL); ExAcquireResourceSharedLite(&context->Vcb->tree_lock, true); if (context->send->cancelling) return STATUS_SUCCESS; if (tp1) { Status = find_item(context->Vcb, context->root, tp1, &key1, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (keycmp(tp1->item->key, key1)) { ERR("readonly subvolume changed\n"); return STATUS_INTERNAL_ERROR; } } if (tp2) { Status = find_item(context->Vcb, context->parent, tp2, &key2, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } if (keycmp(tp2->item->key, key2)) { ERR("readonly subvolume changed\n"); return STATUS_INTERNAL_ERROR; } } return STATUS_SUCCESS; } static NTSTATUS add_ext_holes(device_extension* Vcb, LIST_ENTRY* exts, uint64_t size) { uint64_t lastoff = 0; LIST_ENTRY* le; le = exts->Flink; while (le != exts) { send_ext* ext = CONTAINING_RECORD(le, send_ext, list_entry); if (ext->offset > lastoff) { send_ext* ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data.data) + sizeof(EXTENT_DATA2), ALLOC_TAG); EXTENT_DATA2* ed2; if (!ext2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ed2 = (EXTENT_DATA2*)ext2->data.data; ext2->offset = lastoff; ext2->datalen = offsetof(EXTENT_DATA, data) + sizeof(EXTENT_DATA2); ext2->data.decoded_size = ed2->num_bytes = ext->offset - lastoff; ext2->data.type = EXTENT_TYPE_REGULAR; ed2->address = ed2->size = ed2->offset = 0; InsertHeadList(le->Blink, &ext2->list_entry); } if (ext->data.type == EXTENT_TYPE_INLINE) lastoff = ext->offset + ext->data.decoded_size; else { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->data.data; lastoff = ext->offset + ed2->num_bytes; } le = le->Flink; } if (size > lastoff) { send_ext* ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data.data) + sizeof(EXTENT_DATA2), ALLOC_TAG); EXTENT_DATA2* ed2; if (!ext2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ed2 = (EXTENT_DATA2*)ext2->data.data; ext2->offset = lastoff; ext2->datalen = offsetof(EXTENT_DATA, data) + sizeof(EXTENT_DATA2); ext2->data.decoded_size = ed2->num_bytes = sector_align(size - lastoff, Vcb->superblock.sector_size); ext2->data.type = EXTENT_TYPE_REGULAR; ed2->address = ed2->size = ed2->offset = 0; InsertTailList(exts, &ext2->list_entry); } return STATUS_SUCCESS; } static NTSTATUS divide_ext(send_ext* ext, uint64_t len, bool trunc) { send_ext* ext2; EXTENT_DATA2 *ed2a, *ed2b; if (ext->data.type == EXTENT_TYPE_INLINE) { if (!trunc) { ext2 = ExAllocatePoolWithTag(PagedPool, (ULONG)(offsetof(send_ext, data.data) + ext->data.decoded_size - len), ALLOC_TAG); if (!ext2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ext2->offset = ext->offset + len; ext2->datalen = (ULONG)(ext->data.decoded_size - len); ext2->data.decoded_size = ext->data.decoded_size - len; ext2->data.compression = ext->data.compression; ext2->data.encryption = ext->data.encryption; ext2->data.encoding = ext->data.encoding; ext2->data.type = ext->data.type; RtlCopyMemory(ext2->data.data, ext->data.data + len, (ULONG)(ext->data.decoded_size - len)); InsertHeadList(&ext->list_entry, &ext2->list_entry); } ext->data.decoded_size = len; return STATUS_SUCCESS; } ed2a = (EXTENT_DATA2*)ext->data.data; if (!trunc) { ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data.data) + sizeof(EXTENT_DATA2), ALLOC_TAG); if (!ext2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ed2b = (EXTENT_DATA2*)ext2->data.data; ext2->offset = ext->offset + len; ext2->datalen = offsetof(EXTENT_DATA, data) + sizeof(EXTENT_DATA2); ext2->data.compression = ext->data.compression; ext2->data.encryption = ext->data.encryption; ext2->data.encoding = ext->data.encoding; ext2->data.type = ext->data.type; ed2b->num_bytes = ed2a->num_bytes - len; if (ed2a->size == 0) { ext2->data.decoded_size = ed2b->num_bytes; ext->data.decoded_size = len; ed2b->address = ed2b->size = ed2b->offset = 0; } else { ext2->data.decoded_size = ext->data.decoded_size; ed2b->address = ed2a->address; ed2b->size = ed2a->size; ed2b->offset = ed2a->offset + len; } InsertHeadList(&ext->list_entry, &ext2->list_entry); } ed2a->num_bytes = len; return STATUS_SUCCESS; } static NTSTATUS sync_ext_cutoff_points(send_context* context) { NTSTATUS Status; send_ext *ext1, *ext2; ext1 = CONTAINING_RECORD(context->lastinode.exts.Flink, send_ext, list_entry); ext2 = CONTAINING_RECORD(context->lastinode.oldexts.Flink, send_ext, list_entry); do { uint64_t len1, len2; EXTENT_DATA2 *ed2a, *ed2b; ed2a = ext1->data.type == EXTENT_TYPE_INLINE ? NULL : (EXTENT_DATA2*)ext1->data.data; ed2b = ext2->data.type == EXTENT_TYPE_INLINE ? NULL : (EXTENT_DATA2*)ext2->data.data; len1 = ed2a ? ed2a->num_bytes : ext1->data.decoded_size; len2 = ed2b ? ed2b->num_bytes : ext2->data.decoded_size; if (len1 < len2) { Status = divide_ext(ext2, len1, false); if (!NT_SUCCESS(Status)) { ERR("divide_ext returned %08lx\n", Status); return Status; } } else if (len2 < len1) { Status = divide_ext(ext1, len2, false); if (!NT_SUCCESS(Status)) { ERR("divide_ext returned %08lx\n", Status); return Status; } } if (ext1->list_entry.Flink == &context->lastinode.exts || ext2->list_entry.Flink == &context->lastinode.oldexts) break; ext1 = CONTAINING_RECORD(ext1->list_entry.Flink, send_ext, list_entry); ext2 = CONTAINING_RECORD(ext2->list_entry.Flink, send_ext, list_entry); } while (true); ext1 = CONTAINING_RECORD(context->lastinode.exts.Blink, send_ext, list_entry); ext2 = CONTAINING_RECORD(context->lastinode.oldexts.Blink, send_ext, list_entry); Status = divide_ext(ext1, context->lastinode.size - ext1->offset, true); if (!NT_SUCCESS(Status)) { ERR("divide_ext returned %08lx\n", Status); return Status; } Status = divide_ext(ext2, context->lastinode.size - ext2->offset, true); if (!NT_SUCCESS(Status)) { ERR("divide_ext returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } static bool send_add_tlv_clone_path(send_context* context, root* r, uint64_t inode) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; uint16_t len = 0; uint64_t num; uint8_t* ptr; num = inode; while (num != SUBVOL_ROOT_INODE) { searchkey.obj_id = num; searchkey.obj_type = TYPE_INODE_EXTREF; searchkey.offset = 0xffffffffffffffff; Status = find_item(context->Vcb, r, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return false; } 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)) { ERR("could not find INODE_REF for inode %I64x\n", searchkey.obj_id); return false; } if (len > 0) len++; if (tp.item->key.obj_type == TYPE_INODE_REF) { INODE_REF* ir = (INODE_REF*)tp.item->data; if (tp.item->size < sizeof(INODE_REF) || tp.item->size < offsetof(INODE_REF, name[0]) + ir->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); return false; } len += ir->n; num = tp.item->key.offset; } else { INODE_EXTREF* ier = (INODE_EXTREF*)tp.item->data; if (tp.item->size < sizeof(INODE_EXTREF) || tp.item->size < offsetof(INODE_EXTREF, name[0]) + ier->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); return false; } len += ier->n; num = ier->dir; } } send_add_tlv(context, BTRFS_SEND_TLV_CLONE_PATH, NULL, len); ptr = &context->data[context->datalen]; num = inode; while (num != SUBVOL_ROOT_INODE) { searchkey.obj_id = num; searchkey.obj_type = TYPE_INODE_EXTREF; searchkey.offset = 0xffffffffffffffff; Status = find_item(context->Vcb, r, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return false; } 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)) { ERR("could not find INODE_REF for inode %I64x\n", searchkey.obj_id); return false; } if (num != inode) { ptr--; *ptr = '/'; } if (tp.item->key.obj_type == TYPE_INODE_REF) { INODE_REF* ir = (INODE_REF*)tp.item->data; RtlCopyMemory(ptr - ir->n, ir->name, ir->n); ptr -= ir->n; num = tp.item->key.offset; } else { INODE_EXTREF* ier = (INODE_EXTREF*)tp.item->data; RtlCopyMemory(ptr - ier->n, ier->name, ier->n); ptr -= ier->n; num = ier->dir; } } return true; } static bool try_clone_edr(send_context* context, send_ext* se, EXTENT_DATA_REF* edr) { NTSTATUS Status; root* r = NULL; KEY searchkey; traverse_ptr tp; EXTENT_DATA2* seed2 = (EXTENT_DATA2*)se->data.data; if (context->parent && edr->root == context->parent->id) r = context->parent; if (!r && context->num_clones > 0) { ULONG i; for (i = 0; i < context->num_clones; i++) { if (context->clones[i]->id == edr->root && context->clones[i] != context->root) { r = context->clones[i]; break; } } } if (!r) return false; searchkey.obj_id = edr->objid; searchkey.obj_type = TYPE_EXTENT_DATA; searchkey.offset = 0; Status = find_item(context->Vcb, r, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return false; } while (true) { traverse_ptr next_tp; if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { if (tp.item->size < sizeof(EXTENT_DATA)) 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)); else { EXTENT_DATA* ed = (EXTENT_DATA*)tp.item->data; if (ed->type == EXTENT_TYPE_REGULAR) { if (tp.item->size < offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)) 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, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)); else { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; if (ed2->address == seed2->address && ed2->size == seed2->size && seed2->offset <= ed2->offset && seed2->offset + seed2->num_bytes >= ed2->offset + ed2->num_bytes) { uint64_t clone_offset = tp.item->key.offset + ed2->offset - seed2->offset; uint64_t clone_len = min(context->lastinode.size - se->offset, ed2->num_bytes); if ((clone_offset & (context->Vcb->superblock.sector_size - 1)) == 0 && (clone_len & (context->Vcb->superblock.sector_size - 1)) == 0) { ULONG pos = context->datalen; send_command(context, BTRFS_SEND_CMD_CLONE); send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &se->offset, sizeof(uint64_t)); send_add_tlv(context, BTRFS_SEND_TLV_CLONE_LENGTH, &clone_len, sizeof(uint64_t)); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); 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)); send_add_tlv(context, BTRFS_SEND_TLV_CLONE_CTRANSID, &r->root_item.ctransid, sizeof(uint64_t)); if (!send_add_tlv_clone_path(context, r, tp.item->key.obj_id)) context->datalen = pos; else { send_add_tlv(context, BTRFS_SEND_TLV_CLONE_OFFSET, &clone_offset, sizeof(uint64_t)); send_command_finish(context, pos); return true; } } } } } } } 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)) break; if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else break; } return false; } static bool try_clone(send_context* context, send_ext* se) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; EXTENT_DATA2* ed2 = (EXTENT_DATA2*)se->data.data; EXTENT_ITEM* ei; uint64_t rc = 0; searchkey.obj_id = ed2->address; searchkey.obj_type = TYPE_EXTENT_ITEM; searchkey.offset = ed2->size; Status = find_item(context->Vcb, context->Vcb->extent_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return false; } if (keycmp(tp.item->key, searchkey)) { ERR("(%I64x,%x,%I64x) not found\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); return false; } if (tp.item->size < sizeof(EXTENT_ITEM)) { 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)); return false; } ei = (EXTENT_ITEM*)tp.item->data; if (tp.item->size > sizeof(EXTENT_ITEM)) { uint32_t len = tp.item->size - sizeof(EXTENT_ITEM); uint8_t* ptr = (uint8_t*)&ei[1]; while (len > 0) { uint8_t secttype = *ptr; ULONG sectlen = get_extent_data_len(secttype); uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t)); len--; if (sectlen > len) { 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); return false; } if (sectlen == 0) { ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype); return false; } rc += sectcount; if (secttype == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t)); if (try_clone_edr(context, se, sectedr)) return true; } len -= sectlen; ptr += sizeof(uint8_t) + sectlen; } } if (rc >= ei->refcount) return false; searchkey.obj_type = TYPE_EXTENT_DATA_REF; searchkey.offset = 0; Status = find_item(context->Vcb, context->Vcb->extent_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return false; } while (true) { traverse_ptr next_tp; if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { if (tp.item->size < sizeof(EXTENT_DATA_REF)) 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)); else { if (try_clone_edr(context, se, (EXTENT_DATA_REF*)tp.item->data)) return true; } } 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)) break; if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else break; } return false; } static NTSTATUS flush_extents(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) { NTSTATUS Status; if ((IsListEmpty(&context->lastinode.exts) && IsListEmpty(&context->lastinode.oldexts)) || context->lastinode.size == 0) return STATUS_SUCCESS; if (context->parent) { Status = add_ext_holes(context->Vcb, &context->lastinode.exts, context->lastinode.size); if (!NT_SUCCESS(Status)) { ERR("add_ext_holes returned %08lx\n", Status); return Status; } Status = add_ext_holes(context->Vcb, &context->lastinode.oldexts, context->lastinode.size); if (!NT_SUCCESS(Status)) { ERR("add_ext_holes returned %08lx\n", Status); return Status; } Status = sync_ext_cutoff_points(context); if (!NT_SUCCESS(Status)) { ERR("sync_ext_cutoff_points returned %08lx\n", Status); return Status; } } while (!IsListEmpty(&context->lastinode.exts)) { send_ext* se = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.exts), send_ext, list_entry); send_ext* se2 = context->parent ? CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldexts), send_ext, list_entry) : NULL; ULONG pos; EXTENT_DATA2* ed2; if (se2) { if (se->data.type == EXTENT_TYPE_INLINE && se2->data.type == EXTENT_TYPE_INLINE && RtlCompareMemory(se->data.data, se2->data.data, (ULONG)se->data.decoded_size) == (ULONG)se->data.decoded_size) { ExFreePool(se); ExFreePool(se2); continue; } if (se->data.type == EXTENT_TYPE_REGULAR && se2->data.type == EXTENT_TYPE_REGULAR) { EXTENT_DATA2 *ed2a, *ed2b; ed2a = (EXTENT_DATA2*)se->data.data; ed2b = (EXTENT_DATA2*)se2->data.data; if (RtlCompareMemory(ed2a, ed2b, sizeof(EXTENT_DATA2)) == sizeof(EXTENT_DATA2)) { ExFreePool(se); ExFreePool(se2); continue; } } } if (se->data.type == EXTENT_TYPE_INLINE) { pos = context->datalen; send_command(context, BTRFS_SEND_CMD_WRITE); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &se->offset, sizeof(uint64_t)); if (se->data.compression == BTRFS_COMPRESSION_NONE) send_add_tlv(context, BTRFS_SEND_TLV_DATA, se->data.data, (uint16_t)se->data.decoded_size); else if (se->data.compression == BTRFS_COMPRESSION_ZLIB || se->data.compression == BTRFS_COMPRESSION_LZO || se->data.compression == BTRFS_COMPRESSION_ZSTD) { ULONG inlen = se->datalen - (ULONG)offsetof(EXTENT_DATA, data[0]); send_add_tlv(context, BTRFS_SEND_TLV_DATA, NULL, (uint16_t)se->data.decoded_size); RtlZeroMemory(&context->data[context->datalen - se->data.decoded_size], (ULONG)se->data.decoded_size); if (se->data.compression == BTRFS_COMPRESSION_ZLIB) { Status = zlib_decompress(se->data.data, inlen, &context->data[context->datalen - se->data.decoded_size], (uint32_t)se->data.decoded_size); if (!NT_SUCCESS(Status)) { ERR("zlib_decompress returned %08lx\n", Status); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } } else if (se->data.compression == BTRFS_COMPRESSION_LZO) { if (inlen < sizeof(uint32_t)) { ERR("extent data was truncated\n"); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INTERNAL_ERROR; } else inlen -= sizeof(uint32_t); 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)); if (!NT_SUCCESS(Status)) { ERR("lzo_decompress returned %08lx\n", Status); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } } else if (se->data.compression == BTRFS_COMPRESSION_ZSTD) { Status = zstd_decompress(se->data.data, inlen, &context->data[context->datalen - se->data.decoded_size], (uint32_t)se->data.decoded_size); if (!NT_SUCCESS(Status)) { ERR("zlib_decompress returned %08lx\n", Status); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } } } else { ERR("unhandled compression type %x\n", se->data.compression); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_NOT_IMPLEMENTED; } send_command_finish(context, pos); ExFreePool(se); if (se2) ExFreePool(se2); continue; } ed2 = (EXTENT_DATA2*)se->data.data; if (ed2->size != 0 && (context->parent || context->num_clones > 0)) { if (try_clone(context, se)) { ExFreePool(se); if (se2) ExFreePool(se2); continue; } } if (ed2->size == 0) { // write sparse uint64_t off, offset; for (off = ed2->offset; off < ed2->offset + ed2->num_bytes; off += MAX_SEND_WRITE) { uint16_t length = (uint16_t)min(min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE), context->lastinode.size - se->offset - off); if (context->datalen > SEND_BUFFER_LENGTH) { Status = wait_for_flush(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("wait_for_flush returned %08lx\n", Status); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } if (context->send->cancelling) { ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_SUCCESS; } } pos = context->datalen; send_command(context, BTRFS_SEND_CMD_WRITE); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); offset = se->offset + off; send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(uint64_t)); send_add_tlv(context, BTRFS_SEND_TLV_DATA, NULL, length); RtlZeroMemory(&context->data[context->datalen - length], length); send_command_finish(context, pos); } } else if (se->data.compression == BTRFS_COMPRESSION_NONE) { uint64_t off, offset; uint8_t* buf; buf = ExAllocatePoolWithTag(NonPagedPool, MAX_SEND_WRITE + (2 * context->Vcb->superblock.sector_size), ALLOC_TAG); if (!buf) { ERR("out of memory\n"); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INSUFFICIENT_RESOURCES; } for (off = ed2->offset; off < ed2->offset + ed2->num_bytes; off += MAX_SEND_WRITE) { uint16_t length = (uint16_t)min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE); ULONG skip_start; uint64_t addr = ed2->address + off; void* csum; if (context->datalen > SEND_BUFFER_LENGTH) { Status = wait_for_flush(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("wait_for_flush returned %08lx\n", Status); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } if (context->send->cancelling) { ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_SUCCESS; } } skip_start = addr & (context->Vcb->superblock.sector_size - 1); addr -= skip_start; if (context->lastinode.flags & BTRFS_INODE_NODATASUM) csum = NULL; else { uint32_t len; len = (uint32_t)sector_align(length + skip_start, context->Vcb->superblock.sector_size) >> context->Vcb->sector_shift; csum = ExAllocatePoolWithTag(PagedPool, len * context->Vcb->csum_size, ALLOC_TAG); if (!csum) { ERR("out of memory\n"); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INSUFFICIENT_RESOURCES; } Status = load_csum(context->Vcb, csum, addr, len, NULL); if (!NT_SUCCESS(Status)) { ERR("load_csum returned %08lx\n", Status); ExFreePool(csum); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INSUFFICIENT_RESOURCES; } } Status = read_data(context->Vcb, addr, (uint32_t)sector_align(length + skip_start, context->Vcb->superblock.sector_size), csum, false, buf, NULL, NULL, NULL, 0, false, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned %08lx\n", Status); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); if (csum) ExFreePool(csum); return Status; } if (csum) ExFreePool(csum); pos = context->datalen; send_command(context, BTRFS_SEND_CMD_WRITE); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); offset = se->offset + off; send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(uint64_t)); length = (uint16_t)min(context->lastinode.size - se->offset - off, length); send_add_tlv(context, BTRFS_SEND_TLV_DATA, buf + skip_start, length); send_command_finish(context, pos); } ExFreePool(buf); } else { uint8_t *buf, *compbuf; uint64_t off; void* csum; buf = ExAllocatePoolWithTag(PagedPool, (ULONG)se->data.decoded_size, ALLOC_TAG); if (!buf) { ERR("out of memory\n"); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INSUFFICIENT_RESOURCES; } compbuf = ExAllocatePoolWithTag(PagedPool, (ULONG)ed2->size, ALLOC_TAG); if (!compbuf) { ERR("out of memory\n"); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INSUFFICIENT_RESOURCES; } if (context->lastinode.flags & BTRFS_INODE_NODATASUM) csum = NULL; else { uint32_t len; len = (uint32_t)(ed2->size >> context->Vcb->sector_shift); csum = ExAllocatePoolWithTag(PagedPool, len * context->Vcb->csum_size, ALLOC_TAG); if (!csum) { ERR("out of memory\n"); ExFreePool(compbuf); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INSUFFICIENT_RESOURCES; } Status = load_csum(context->Vcb, csum, ed2->address, len, NULL); if (!NT_SUCCESS(Status)) { ERR("load_csum returned %08lx\n", Status); ExFreePool(csum); ExFreePool(compbuf); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } } Status = read_data(context->Vcb, ed2->address, (uint32_t)ed2->size, csum, false, compbuf, NULL, NULL, NULL, 0, false, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned %08lx\n", Status); ExFreePool(compbuf); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); if (csum) ExFreePool(csum); return Status; } if (csum) ExFreePool(csum); if (se->data.compression == BTRFS_COMPRESSION_ZLIB) { Status = zlib_decompress(compbuf, (uint32_t)ed2->size, buf, (uint32_t)se->data.decoded_size); if (!NT_SUCCESS(Status)) { ERR("zlib_decompress returned %08lx\n", Status); ExFreePool(compbuf); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } } else if (se->data.compression == BTRFS_COMPRESSION_LZO) { Status = lzo_decompress(&compbuf[sizeof(uint32_t)], (uint32_t)ed2->size, buf, (uint32_t)se->data.decoded_size, sizeof(uint32_t)); if (!NT_SUCCESS(Status)) { ERR("lzo_decompress returned %08lx\n", Status); ExFreePool(compbuf); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } } else if (se->data.compression == BTRFS_COMPRESSION_ZSTD) { Status = zstd_decompress(compbuf, (uint32_t)ed2->size, buf, (uint32_t)se->data.decoded_size); if (!NT_SUCCESS(Status)) { ERR("zstd_decompress returned %08lx\n", Status); ExFreePool(compbuf); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } } ExFreePool(compbuf); for (off = ed2->offset; off < ed2->offset + ed2->num_bytes; off += MAX_SEND_WRITE) { uint16_t length = (uint16_t)min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE); uint64_t offset; if (context->datalen > SEND_BUFFER_LENGTH) { Status = wait_for_flush(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("wait_for_flush returned %08lx\n", Status); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } if (context->send->cancelling) { ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_SUCCESS; } } pos = context->datalen; send_command(context, BTRFS_SEND_CMD_WRITE); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); offset = se->offset + off; send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(uint64_t)); length = (uint16_t)min(context->lastinode.size - se->offset - off, length); send_add_tlv(context, BTRFS_SEND_TLV_DATA, &buf[off], length); send_command_finish(context, pos); } ExFreePool(buf); } ExFreePool(se); if (se2) ExFreePool(se2); } return STATUS_SUCCESS; } static NTSTATUS finish_inode(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) { LIST_ENTRY* le; if (!IsListEmpty(&context->lastinode.refs) || !IsListEmpty(&context->lastinode.oldrefs)) { NTSTATUS Status = flush_refs(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("flush_refs returned %08lx\n", Status); return Status; } if (context->send->cancelling) return STATUS_SUCCESS; } if (!context->lastinode.deleting) { if (context->lastinode.file) { NTSTATUS Status = flush_extents(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("flush_extents returned %08lx\n", Status); return Status; } if (context->send->cancelling) return STATUS_SUCCESS; send_truncate_command(context, context->lastinode.path, context->lastinode.size); } if (context->lastinode.new || context->lastinode.uid != context->lastinode.olduid || context->lastinode.gid != context->lastinode.oldgid) send_chown_command(context, context->lastinode.path, context->lastinode.uid, context->lastinode.gid); if (((context->lastinode.mode & __S_IFLNK) != __S_IFLNK || ((context->lastinode.mode & 07777) != 0777)) && (context->lastinode.new || context->lastinode.mode != context->lastinode.oldmode)) send_chmod_command(context, context->lastinode.path, context->lastinode.mode); send_utimes_command(context, context->lastinode.path, &context->lastinode.atime, &context->lastinode.mtime, &context->lastinode.ctime); } while (!IsListEmpty(&context->lastinode.exts)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&context->lastinode.exts), send_ext, list_entry)); } while (!IsListEmpty(&context->lastinode.oldexts)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldexts), send_ext, list_entry)); } if (context->parent) { le = context->pending_rmdirs.Flink; while (le != &context->pending_rmdirs) { pending_rmdir* pr = CONTAINING_RECORD(le, pending_rmdir, list_entry); if (pr->last_child_inode <= context->lastinode.inode) { le = le->Flink; send_rmdir_command(context, pr->sd->namelen, pr->sd->name); RemoveEntryList(&pr->sd->list_entry); if (pr->sd->name) ExFreePool(pr->sd->name); while (!IsListEmpty(&pr->sd->deleted_children)) { deleted_child* dc = CONTAINING_RECORD(RemoveHeadList(&pr->sd->deleted_children), deleted_child, list_entry); ExFreePool(dc); } ExFreePool(pr->sd); RemoveEntryList(&pr->list_entry); ExFreePool(pr); } else break; } } context->lastinode.inode = 0; context->lastinode.o = NULL; if (context->lastinode.path) { ExFreePool(context->lastinode.path); context->lastinode.path = NULL; } return STATUS_SUCCESS; } static NTSTATUS send_extent_data(send_context* context, traverse_ptr* tp, traverse_ptr* tp2) { NTSTATUS Status; if (tp && tp2 && tp->item->size == tp2->item->size && RtlCompareMemory(tp->item->data, tp2->item->data, tp->item->size) == tp->item->size) return STATUS_SUCCESS; if (!IsListEmpty(&context->lastinode.refs) || !IsListEmpty(&context->lastinode.oldrefs)) { Status = flush_refs(context, tp, tp2); if (!NT_SUCCESS(Status)) { ERR("flush_refs returned %08lx\n", Status); return Status; } if (context->send->cancelling) return STATUS_SUCCESS; } if ((context->lastinode.mode & __S_IFLNK) == __S_IFLNK) return STATUS_SUCCESS; if (tp) { EXTENT_DATA* ed; EXTENT_DATA2* ed2 = NULL; if (tp->item->size < sizeof(EXTENT_DATA)) { 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_DATA)); return STATUS_INTERNAL_ERROR; } ed = (EXTENT_DATA*)tp->item->data; if (ed->encryption != BTRFS_ENCRYPTION_NONE) { ERR("unknown encryption type %u\n", ed->encryption); return STATUS_INTERNAL_ERROR; } if (ed->encoding != BTRFS_ENCODING_NONE) { ERR("unknown encoding type %u\n", ed->encoding); return STATUS_INTERNAL_ERROR; } if (ed->compression != BTRFS_COMPRESSION_NONE && ed->compression != BTRFS_COMPRESSION_ZLIB && ed->compression != BTRFS_COMPRESSION_LZO && ed->compression != BTRFS_COMPRESSION_ZSTD) { ERR("unknown compression type %u\n", ed->compression); return STATUS_INTERNAL_ERROR; } if (ed->type == EXTENT_TYPE_REGULAR) { if (tp->item->size < offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)) { 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(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)); return STATUS_INTERNAL_ERROR; } ed2 = (EXTENT_DATA2*)ed->data; } else if (ed->type == EXTENT_TYPE_INLINE) { if (tp->item->size < offsetof(EXTENT_DATA, data[0]) + ed->decoded_size && ed->compression == BTRFS_COMPRESSION_NONE) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %I64u\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, tp->item->size, offsetof(EXTENT_DATA, data[0]) + ed->decoded_size); return STATUS_INTERNAL_ERROR; } } if ((ed->type == EXTENT_TYPE_INLINE || (ed->type == EXTENT_TYPE_REGULAR && ed2->size != 0)) && ed->decoded_size != 0) { send_ext* se = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data) + tp->item->size, ALLOC_TAG); if (!se) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } se->offset = tp->item->key.offset; se->datalen = tp->item->size; RtlCopyMemory(&se->data, tp->item->data, tp->item->size); InsertTailList(&context->lastinode.exts, &se->list_entry); } } if (tp2) { EXTENT_DATA* ed; EXTENT_DATA2* ed2 = NULL; if (tp2->item->size < sizeof(EXTENT_DATA)) { 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, tp2->item->size, sizeof(EXTENT_DATA)); return STATUS_INTERNAL_ERROR; } ed = (EXTENT_DATA*)tp2->item->data; if (ed->encryption != BTRFS_ENCRYPTION_NONE) { ERR("unknown encryption type %u\n", ed->encryption); return STATUS_INTERNAL_ERROR; } if (ed->encoding != BTRFS_ENCODING_NONE) { ERR("unknown encoding type %u\n", ed->encoding); return STATUS_INTERNAL_ERROR; } if (ed->compression != BTRFS_COMPRESSION_NONE && ed->compression != BTRFS_COMPRESSION_ZLIB && ed->compression != BTRFS_COMPRESSION_LZO && ed->compression != BTRFS_COMPRESSION_ZSTD) { ERR("unknown compression type %u\n", ed->compression); return STATUS_INTERNAL_ERROR; } if (ed->type == EXTENT_TYPE_REGULAR) { if (tp2->item->size < offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %Iu\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset, tp2->item->size, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)); return STATUS_INTERNAL_ERROR; } ed2 = (EXTENT_DATA2*)ed->data; } else if (ed->type == EXTENT_TYPE_INLINE) { if (tp2->item->size < offsetof(EXTENT_DATA, data[0]) + ed->decoded_size) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %I64u\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset, tp2->item->size, offsetof(EXTENT_DATA, data[0]) + ed->decoded_size); return STATUS_INTERNAL_ERROR; } } if ((ed->type == EXTENT_TYPE_INLINE || (ed->type == EXTENT_TYPE_REGULAR && ed2->size != 0)) && ed->decoded_size != 0) { send_ext* se = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data) + tp2->item->size, ALLOC_TAG); if (!se) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } se->offset = tp2->item->key.offset; se->datalen = tp2->item->size; RtlCopyMemory(&se->data, tp2->item->data, tp2->item->size); InsertTailList(&context->lastinode.oldexts, &se->list_entry); } } return STATUS_SUCCESS; } typedef struct { uint16_t namelen; char* name; uint16_t value1len; char* value1; uint16_t value2len; char* value2; LIST_ENTRY list_entry; } xattr_cmp; static NTSTATUS send_xattr(send_context* context, traverse_ptr* tp, traverse_ptr* tp2) { if (tp && tp2 && tp->item->size == tp2->item->size && RtlCompareMemory(tp->item->data, tp2->item->data, tp->item->size) == tp->item->size) return STATUS_SUCCESS; if (!IsListEmpty(&context->lastinode.refs) || !IsListEmpty(&context->lastinode.oldrefs)) { NTSTATUS Status = flush_refs(context, tp, tp2); if (!NT_SUCCESS(Status)) { ERR("flush_refs returned %08lx\n", Status); return Status; } if (context->send->cancelling) return STATUS_SUCCESS; } if (tp && tp->item->size < sizeof(DIR_ITEM)) { 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)); return STATUS_INTERNAL_ERROR; } if (tp2 && tp2->item->size < sizeof(DIR_ITEM)) { 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, tp2->item->size, sizeof(DIR_ITEM)); return STATUS_INTERNAL_ERROR; } if (tp && !tp2) { ULONG len; DIR_ITEM* di; len = tp->item->size; di = (DIR_ITEM*)tp->item->data; do { ULONG pos; if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset); return STATUS_INTERNAL_ERROR; } pos = context->datalen; send_command(context, BTRFS_SEND_CMD_SET_XATTR); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, di->name, di->n); send_add_tlv(context, BTRFS_SEND_TLV_XATTR_DATA, &di->name[di->n], di->m); send_command_finish(context, pos); len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n; di = (DIR_ITEM*)&di->name[di->m + di->n]; } while (len > 0); } else if (!tp && tp2) { ULONG len; DIR_ITEM* di; len = tp2->item->size; di = (DIR_ITEM*)tp2->item->data; do { ULONG pos; if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset); return STATUS_INTERNAL_ERROR; } pos = context->datalen; send_command(context, BTRFS_SEND_CMD_REMOVE_XATTR); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, di->name, di->n); send_command_finish(context, pos); len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n; di = (DIR_ITEM*)&di->name[di->m + di->n]; } while (len > 0); } else { ULONG len; DIR_ITEM* di; LIST_ENTRY xattrs; InitializeListHead(&xattrs); len = tp->item->size; di = (DIR_ITEM*)tp->item->data; do { xattr_cmp* xa; if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset); return STATUS_INTERNAL_ERROR; } xa = ExAllocatePoolWithTag(PagedPool, sizeof(xattr_cmp), ALLOC_TAG); if (!xa) { ERR("out of memory\n"); while (!IsListEmpty(&xattrs)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&xattrs), xattr_cmp, list_entry)); } return STATUS_INSUFFICIENT_RESOURCES; } xa->namelen = di->n; xa->name = di->name; xa->value1len = di->m; xa->value1 = di->name + di->n; xa->value2len = 0; xa->value2 = NULL; InsertTailList(&xattrs, &xa->list_entry); len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n; di = (DIR_ITEM*)&di->name[di->m + di->n]; } while (len > 0); len = tp2->item->size; di = (DIR_ITEM*)tp2->item->data; do { xattr_cmp* xa; LIST_ENTRY* le; bool found = false; if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset); return STATUS_INTERNAL_ERROR; } le = xattrs.Flink; while (le != &xattrs) { xa = CONTAINING_RECORD(le, xattr_cmp, list_entry); if (xa->namelen == di->n && RtlCompareMemory(xa->name, di->name, di->n) == di->n) { xa->value2len = di->m; xa->value2 = di->name + di->n; found = true; break; } le = le->Flink; } if (!found) { xa = ExAllocatePoolWithTag(PagedPool, sizeof(xattr_cmp), ALLOC_TAG); if (!xa) { ERR("out of memory\n"); while (!IsListEmpty(&xattrs)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&xattrs), xattr_cmp, list_entry)); } return STATUS_INSUFFICIENT_RESOURCES; } xa->namelen = di->n; xa->name = di->name; xa->value1len = 0; xa->value1 = NULL; xa->value2len = di->m; xa->value2 = di->name + di->n; InsertTailList(&xattrs, &xa->list_entry); } len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n; di = (DIR_ITEM*)&di->name[di->m + di->n]; } while (len > 0); while (!IsListEmpty(&xattrs)) { xattr_cmp* xa = CONTAINING_RECORD(RemoveHeadList(&xattrs), xattr_cmp, list_entry); if (xa->value1len != xa->value2len || !xa->value1 || !xa->value2 || RtlCompareMemory(xa->value1, xa->value2, xa->value1len) != xa->value1len) { ULONG pos; if (!xa->value1) { pos = context->datalen; send_command(context, BTRFS_SEND_CMD_REMOVE_XATTR); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, xa->name, xa->namelen); send_command_finish(context, pos); } else { pos = context->datalen; send_command(context, BTRFS_SEND_CMD_SET_XATTR); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, xa->name, xa->namelen); send_add_tlv(context, BTRFS_SEND_TLV_XATTR_DATA, xa->value1, xa->value1len); send_command_finish(context, pos); } } ExFreePool(xa); } } return STATUS_SUCCESS; } _Function_class_(KSTART_ROUTINE) static void __stdcall send_thread(void* ctx) { send_context* context = (send_context*)ctx; NTSTATUS Status; KEY searchkey; traverse_ptr tp, tp2; InterlockedIncrement(&context->root->send_ops); if (context->parent) InterlockedIncrement(&context->parent->send_ops); if (context->clones) { ULONG i; for (i = 0; i < context->num_clones; i++) { InterlockedIncrement(&context->clones[i]->send_ops); } } ExAcquireResourceExclusiveLite(&context->Vcb->tree_lock, true); flush_subvol_fcbs(context->root); if (context->parent) flush_subvol_fcbs(context->parent); if (context->Vcb->need_write) Status = do_write(context->Vcb, NULL); else Status = STATUS_SUCCESS; free_trees(context->Vcb); if (!NT_SUCCESS(Status)) { ERR("do_write returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } ExConvertExclusiveToSharedLite(&context->Vcb->tree_lock); searchkey.obj_id = searchkey.offset = 0; searchkey.obj_type = 0; Status = find_item(context->Vcb, context->root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->parent) { bool ended1 = false, ended2 = false; Status = find_item(context->Vcb, context->parent, &tp2, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } do { traverse_ptr next_tp; if (context->datalen > SEND_BUFFER_LENGTH) { KEY key1 = tp.item->key, key2 = tp2.item->key; ExReleaseResourceLite(&context->Vcb->tree_lock); KeClearEvent(&context->send->cleared_event); KeSetEvent(&context->buffer_event, 0, true); KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL); if (context->send->cancelling) goto end; ExAcquireResourceSharedLite(&context->Vcb->tree_lock, true); if (!ended1) { Status = find_item(context->Vcb, context->root, &tp, &key1, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (keycmp(tp.item->key, key1)) { ERR("readonly subvolume changed\n"); ExReleaseResourceLite(&context->Vcb->tree_lock); Status = STATUS_INTERNAL_ERROR; goto end; } } if (!ended2) { Status = find_item(context->Vcb, context->parent, &tp2, &key2, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (keycmp(tp2.item->key, key2)) { ERR("readonly subvolume changed\n"); ExReleaseResourceLite(&context->Vcb->tree_lock); Status = STATUS_INTERNAL_ERROR; goto end; } } } while (!ended1 && !ended2 && tp.tree->header.address == tp2.tree->header.address) { Status = skip_to_difference(context->Vcb, &tp, &tp2, &ended1, &ended2); if (!NT_SUCCESS(Status)) { ERR("skip_to_difference returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } if (!ended1 && !ended2 && !keycmp(tp.item->key, tp2.item->key)) { bool no_next = false, no_next2 = false; TRACE("~ %I64x,%x,%I64x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) { Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2); if (!NT_SUCCESS(Status)) { ERR("finish_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } if (tp.item->key.obj_type == TYPE_INODE_ITEM) { if (tp.item->size == tp2.item->size && tp.item->size > 0 && RtlCompareMemory(tp.item->data, tp2.item->data, tp.item->size) == tp.item->size) { uint64_t inode = tp.item->key.obj_id; while (true) { if (!find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) { ended1 = true; break; } tp = next_tp; if (tp.item->key.obj_id != inode) break; } while (true) { if (!find_next_item(context->Vcb, &tp2, &next_tp, false, NULL)) { ended2 = true; break; } tp2 = next_tp; if (tp2.item->key.obj_id != inode) break; } no_next = true; } else if (tp.item->size > sizeof(uint64_t) && tp2.item->size > sizeof(uint64_t) && *(uint64_t*)tp.item->data != *(uint64_t*)tp2.item->data) { uint64_t inode = tp.item->key.obj_id; Status = send_inode(context, NULL, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } while (true) { if (!find_next_item(context->Vcb, &tp2, &next_tp, false, NULL)) { ended2 = true; break; } tp2 = next_tp; if (tp2.item->key.obj_id != inode) break; if (tp2.item->key.obj_type == TYPE_INODE_REF) { Status = send_inode_ref(context, &tp2, true); if (!NT_SUCCESS(Status)) { ERR("send_inode_ref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp2.item->key.obj_type == TYPE_INODE_EXTREF) { Status = send_inode_extref(context, &tp2, true); if (!NT_SUCCESS(Status)) { ERR("send_inode_extref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } } Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2); if (!NT_SUCCESS(Status)) { ERR("finish_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } no_next2 = true; Status = send_inode(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else { Status = send_inode(context, &tp, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } } else if (tp.item->key.obj_type == TYPE_INODE_REF) { Status = send_inode_ref(context, &tp, false); if (!NT_SUCCESS(Status)) { ERR("send_inode_ref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } Status = send_inode_ref(context, &tp2, true); if (!NT_SUCCESS(Status)) { ERR("send_inode_ref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) { Status = send_inode_extref(context, &tp, false); if (!NT_SUCCESS(Status)) { ERR("send_inode_extref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } Status = send_inode_extref(context, &tp2, true); if (!NT_SUCCESS(Status)) { ERR("send_inode_extref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) { Status = send_extent_data(context, &tp, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_extent_data returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) { Status = send_xattr(context, &tp, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_xattr returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } if (!no_next) { if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else ended1 = true; if (!no_next2) { if (find_next_item(context->Vcb, &tp2, &next_tp, false, NULL)) tp2 = next_tp; else ended2 = true; } } } else if (ended2 || (!ended1 && !ended2 && keycmp(tp.item->key, tp2.item->key) == -1)) { TRACE("A %I64x,%x,%I64x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) { Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2); if (!NT_SUCCESS(Status)) { ERR("finish_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } if (tp.item->key.obj_type == TYPE_INODE_ITEM) { Status = send_inode(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_INODE_REF) { Status = send_inode_ref(context, &tp, false); if (!NT_SUCCESS(Status)) { ERR("send_inode_ref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) { Status = send_inode_extref(context, &tp, false); if (!NT_SUCCESS(Status)) { ERR("send_inode_extref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) { Status = send_extent_data(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_extent_data returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) { Status = send_xattr(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_xattr returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else ended1 = true; } else if (ended1 || (!ended1 && !ended2 && keycmp(tp.item->key, tp2.item->key) == 1)) { TRACE("B %I64x,%x,%I64x\n", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset); if (context->lastinode.inode != 0 && tp2.item->key.obj_id > context->lastinode.inode) { Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2); if (!NT_SUCCESS(Status)) { ERR("finish_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } if (tp2.item->key.obj_type == TYPE_INODE_ITEM) { Status = send_inode(context, NULL, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp2.item->key.obj_type == TYPE_INODE_REF) { Status = send_inode_ref(context, &tp2, true); if (!NT_SUCCESS(Status)) { ERR("send_inode_ref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp2.item->key.obj_type == TYPE_INODE_EXTREF) { Status = send_inode_extref(context, &tp2, true); if (!NT_SUCCESS(Status)) { ERR("send_inode_extref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp2.item->key.obj_type == TYPE_EXTENT_DATA && !context->lastinode.deleting) { Status = send_extent_data(context, NULL, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_extent_data returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp2.item->key.obj_type == TYPE_XATTR_ITEM && !context->lastinode.deleting) { Status = send_xattr(context, NULL, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_xattr returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } if (find_next_item(context->Vcb, &tp2, &next_tp, false, NULL)) tp2 = next_tp; else ended2 = true; } } while (!ended1 || !ended2); } else { do { traverse_ptr next_tp; if (context->datalen > SEND_BUFFER_LENGTH) { KEY key = tp.item->key; ExReleaseResourceLite(&context->Vcb->tree_lock); KeClearEvent(&context->send->cleared_event); KeSetEvent(&context->buffer_event, 0, true); KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL); if (context->send->cancelling) goto end; ExAcquireResourceSharedLite(&context->Vcb->tree_lock, true); Status = find_item(context->Vcb, context->root, &tp, &key, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (keycmp(tp.item->key, key)) { ERR("readonly subvolume changed\n"); ExReleaseResourceLite(&context->Vcb->tree_lock); Status = STATUS_INTERNAL_ERROR; goto end; } } if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) { Status = finish_inode(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("finish_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } if (tp.item->key.obj_type == TYPE_INODE_ITEM) { Status = send_inode(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_INODE_REF) { Status = send_inode_ref(context, &tp, false); if (!NT_SUCCESS(Status)) { ERR("send_inode_ref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) { Status = send_inode_extref(context, &tp, false); if (!NT_SUCCESS(Status)) { ERR("send_inode_extref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) { Status = send_extent_data(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_extent_data returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) { Status = send_xattr(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_xattr returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else break; } while (true); } if (context->lastinode.inode != 0) { Status = finish_inode(context, NULL, NULL); if (!NT_SUCCESS(Status)) { ERR("finish_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } ExReleaseResourceLite(&context->Vcb->tree_lock); if (context->send->cancelling) goto end; } else ExReleaseResourceLite(&context->Vcb->tree_lock); KeClearEvent(&context->send->cleared_event); KeSetEvent(&context->buffer_event, 0, true); KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL); Status = STATUS_SUCCESS; end: if (!NT_SUCCESS(Status)) { KeSetEvent(&context->buffer_event, 0, false); if (context->send->ccb) context->send->ccb->send_status = Status; } ExAcquireResourceExclusiveLite(&context->Vcb->send_load_lock, true); while (!IsListEmpty(&context->orphans)) { orphan* o = CONTAINING_RECORD(RemoveHeadList(&context->orphans), orphan, list_entry); ExFreePool(o); } while (!IsListEmpty(&context->dirs)) { send_dir* sd = CONTAINING_RECORD(RemoveHeadList(&context->dirs), send_dir, list_entry); if (sd->name) ExFreePool(sd->name); while (!IsListEmpty(&sd->deleted_children)) { deleted_child* dc = CONTAINING_RECORD(RemoveHeadList(&sd->deleted_children), deleted_child, list_entry); ExFreePool(dc); } ExFreePool(sd); } ZwClose(context->send->thread); context->send->thread = NULL; if (context->send->ccb) context->send->ccb->send = NULL; RemoveEntryList(&context->send->list_entry); ExFreePool(context->send); ExFreePool(context->data); InterlockedDecrement(&context->Vcb->running_sends); InterlockedDecrement(&context->root->send_ops); if (context->parent) InterlockedDecrement(&context->parent->send_ops); ExReleaseResourceLite(&context->Vcb->send_load_lock); if (context->clones) { ULONG i; for (i = 0; i < context->num_clones; i++) { InterlockedDecrement(&context->clones[i]->send_ops); } ExFreePool(context->clones); } ExFreePool(context); PsTerminateSystemThread(STATUS_SUCCESS); } NTSTATUS send_subvol(device_extension* Vcb, void* data, ULONG datalen, PFILE_OBJECT FileObject, PIRP Irp) { NTSTATUS Status; fcb* fcb; ccb* ccb; root* parsubvol = NULL; send_context* context; send_info* send; ULONG num_clones = 0; root** clones = NULL; OBJECT_ATTRIBUTES oa; if (!FileObject || !FileObject->FsContext || !FileObject->FsContext2 || FileObject->FsContext == Vcb->volume_fcb) return STATUS_INVALID_PARAMETER; if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode)) return STATUS_PRIVILEGE_NOT_HELD; fcb = FileObject->FsContext; ccb = FileObject->FsContext2; if (fcb->inode != SUBVOL_ROOT_INODE || fcb == Vcb->root_fileref->fcb) return STATUS_INVALID_PARAMETER; if (!Vcb->readonly && !(fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY)) return STATUS_INVALID_PARAMETER; if (data) { btrfs_send_subvol* bss = (btrfs_send_subvol*)data; HANDLE parent; #if defined(_WIN64) if (IoIs32bitProcess(Irp)) { btrfs_send_subvol32* bss32 = (btrfs_send_subvol32*)data; if (datalen < offsetof(btrfs_send_subvol32, num_clones)) return STATUS_INVALID_PARAMETER; parent = Handle32ToHandle(bss32->parent); if (datalen >= offsetof(btrfs_send_subvol32, clones[0])) num_clones = bss32->num_clones; if (datalen < offsetof(btrfs_send_subvol32, clones[0]) + (num_clones * sizeof(uint32_t))) return STATUS_INVALID_PARAMETER; } else { #endif if (datalen < offsetof(btrfs_send_subvol, num_clones)) return STATUS_INVALID_PARAMETER; parent = bss->parent; if (datalen >= offsetof(btrfs_send_subvol, clones[0])) num_clones = bss->num_clones; if (datalen < offsetof(btrfs_send_subvol, clones[0]) + (num_clones * sizeof(HANDLE))) return STATUS_INVALID_PARAMETER; #if defined(_WIN64) } #endif if (parent) { PFILE_OBJECT fileobj; struct _fcb* parfcb; Status = ObReferenceObjectByHandle(parent, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL); if (!NT_SUCCESS(Status)) { ERR("ObReferenceObjectByHandle returned %08lx\n", Status); return Status; } if (fileobj->DeviceObject != FileObject->DeviceObject) { ObDereferenceObject(fileobj); return STATUS_INVALID_PARAMETER; } parfcb = fileobj->FsContext; if (!parfcb || parfcb == Vcb->root_fileref->fcb || parfcb == Vcb->volume_fcb || parfcb->inode != SUBVOL_ROOT_INODE) { ObDereferenceObject(fileobj); return STATUS_INVALID_PARAMETER; } parsubvol = parfcb->subvol; ObDereferenceObject(fileobj); if (!Vcb->readonly && !(parsubvol->root_item.flags & BTRFS_SUBVOL_READONLY)) return STATUS_INVALID_PARAMETER; if (parsubvol == fcb->subvol) return STATUS_INVALID_PARAMETER; } if (num_clones > 0) { ULONG i; clones = ExAllocatePoolWithTag(PagedPool, sizeof(root*) * num_clones, ALLOC_TAG); if (!clones) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } for (i = 0; i < num_clones; i++) { HANDLE h; PFILE_OBJECT fileobj; struct _fcb* clonefcb; #if defined(_WIN64) if (IoIs32bitProcess(Irp)) { btrfs_send_subvol32* bss32 = (btrfs_send_subvol32*)data; h = Handle32ToHandle(bss32->clones[i]); } else #endif h = bss->clones[i]; Status = ObReferenceObjectByHandle(h, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL); if (!NT_SUCCESS(Status)) { ERR("ObReferenceObjectByHandle returned %08lx\n", Status); ExFreePool(clones); return Status; } if (fileobj->DeviceObject != FileObject->DeviceObject) { ObDereferenceObject(fileobj); ExFreePool(clones); return STATUS_INVALID_PARAMETER; } clonefcb = fileobj->FsContext; if (!clonefcb || clonefcb == Vcb->root_fileref->fcb || clonefcb == Vcb->volume_fcb || clonefcb->inode != SUBVOL_ROOT_INODE) { ObDereferenceObject(fileobj); ExFreePool(clones); return STATUS_INVALID_PARAMETER; } clones[i] = clonefcb->subvol; ObDereferenceObject(fileobj); if (!Vcb->readonly && !(clones[i]->root_item.flags & BTRFS_SUBVOL_READONLY)) { ExFreePool(clones); return STATUS_INVALID_PARAMETER; } } } } ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, true); if (ccb->send) { WARN("send operation already running\n"); ExReleaseResourceLite(&Vcb->send_load_lock); return STATUS_DEVICE_NOT_READY; } context = ExAllocatePoolWithTag(NonPagedPool, sizeof(send_context), ALLOC_TAG); if (!context) { ERR("out of memory\n"); if (clones) ExFreePool(clones); ExReleaseResourceLite(&Vcb->send_load_lock); return STATUS_INSUFFICIENT_RESOURCES; } context->Vcb = Vcb; context->root = fcb->subvol; context->parent = parsubvol; InitializeListHead(&context->orphans); InitializeListHead(&context->dirs); InitializeListHead(&context->pending_rmdirs); context->lastinode.inode = 0; context->lastinode.path = NULL; context->lastinode.sd = NULL; context->root_dir = NULL; context->num_clones = num_clones; context->clones = clones; InitializeListHead(&context->lastinode.refs); InitializeListHead(&context->lastinode.oldrefs); InitializeListHead(&context->lastinode.exts); InitializeListHead(&context->lastinode.oldexts); context->data = ExAllocatePoolWithTag(PagedPool, SEND_BUFFER_LENGTH + (2 * MAX_SEND_WRITE), ALLOC_TAG); // give ourselves some wiggle room if (!context->data) { ExFreePool(context); ExReleaseResourceLite(&Vcb->send_load_lock); return STATUS_INSUFFICIENT_RESOURCES; } context->datalen = 0; send_subvol_header(context, fcb->subvol, ccb->fileref); // FIXME - fileref needs some sort of lock here KeInitializeEvent(&context->buffer_event, NotificationEvent, false); send = ExAllocatePoolWithTag(NonPagedPool, sizeof(send_info), ALLOC_TAG); if (!send) { ERR("out of memory\n"); ExFreePool(context->data); ExFreePool(context); if (clones) ExFreePool(clones); ExReleaseResourceLite(&Vcb->send_load_lock); return STATUS_INSUFFICIENT_RESOURCES; } KeInitializeEvent(&send->cleared_event, NotificationEvent, false); send->context = context; context->send = send; ccb->send = send; send->ccb = ccb; ccb->send_status = STATUS_SUCCESS; send->cancelling = false; InterlockedIncrement(&Vcb->running_sends); InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); Status = PsCreateSystemThread(&send->thread, 0, &oa, NULL, NULL, send_thread, context); if (!NT_SUCCESS(Status)) { ERR("PsCreateSystemThread returned %08lx\n", Status); ccb->send = NULL; InterlockedDecrement(&Vcb->running_sends); ExFreePool(send); ExFreePool(context->data); ExFreePool(context); if (clones) ExFreePool(clones); ExReleaseResourceLite(&Vcb->send_load_lock); return Status; } InsertTailList(&Vcb->send_ops, &send->list_entry); ExReleaseResourceLite(&Vcb->send_load_lock); return STATUS_SUCCESS; } NTSTATUS read_send_buffer(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, ULONG_PTR* retlen, KPROCESSOR_MODE processor_mode) { ccb* ccb; send_context* context; ccb = FileObject ? FileObject->FsContext2 : NULL; if (!ccb) return STATUS_INVALID_PARAMETER; if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD; ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, true); if (!ccb->send) { ExReleaseResourceLite(&Vcb->send_load_lock); return !NT_SUCCESS(ccb->send_status) ? ccb->send_status : STATUS_END_OF_FILE; } context = (send_context*)ccb->send->context; KeWaitForSingleObject(&context->buffer_event, Executive, KernelMode, false, NULL); if (datalen == 0) { ExReleaseResourceLite(&Vcb->send_load_lock); return STATUS_SUCCESS; } RtlCopyMemory(data, context->data, min(datalen, context->datalen)); if (datalen < context->datalen) { // not empty yet *retlen = datalen; RtlMoveMemory(context->data, &context->data[datalen], context->datalen - datalen); context->datalen -= datalen; ExReleaseResourceLite(&Vcb->send_load_lock); } else { *retlen = context->datalen; context->datalen = 0; ExReleaseResourceLite(&Vcb->send_load_lock); KeClearEvent(&context->buffer_event); KeSetEvent(&ccb->send->cleared_event, 0, false); } return STATUS_SUCCESS; } ================================================ FILE: src/sha256.c ================================================ #include #include // Public domain code from https://github.com/amosnier/sha-2 // FIXME - x86 SHA extensions #define CHUNK_SIZE 64 #define TOTAL_LEN_LEN 8 /* * ABOUT bool: this file does not use bool in order to be as pre-C99 compatible as possible. */ /* * Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here. * When useful for clarification, portions of the pseudo-code are reproduced here too. */ /* * Initialize array of round constants: * (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311): */ static const uint32_t k[] = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; struct buffer_state { const uint8_t * p; size_t len; size_t total_len; int single_one_delivered; /* bool */ int total_len_delivered; /* bool */ }; static inline uint32_t right_rot(uint32_t value, unsigned int count) { /* * Defined behaviour in standard C for all count where 0 < count < 32, * which is what we need here. */ return value >> count | value << (32 - count); } static void init_buf_state(struct buffer_state * state, const void * input, size_t len) { state->p = input; state->len = len; state->total_len = len; state->single_one_delivered = 0; state->total_len_delivered = 0; } /* Return value: bool */ static int calc_chunk(uint8_t chunk[CHUNK_SIZE], struct buffer_state * state) { size_t space_in_chunk; if (state->total_len_delivered) { return 0; } if (state->len >= CHUNK_SIZE) { memcpy(chunk, state->p, CHUNK_SIZE); state->p += CHUNK_SIZE; state->len -= CHUNK_SIZE; return 1; } memcpy(chunk, state->p, state->len); chunk += state->len; space_in_chunk = CHUNK_SIZE - state->len; state->p += state->len; state->len = 0; /* If we are here, space_in_chunk is one at minimum. */ if (!state->single_one_delivered) { *chunk++ = 0x80; space_in_chunk -= 1; state->single_one_delivered = 1; } /* * Now: * - either there is enough space left for the total length, and we can conclude, * - or there is too little space left, and we have to pad the rest of this chunk with zeroes. * In the latter case, we will conclude at the next invokation of this function. */ if (space_in_chunk >= TOTAL_LEN_LEN) { const size_t left = space_in_chunk - TOTAL_LEN_LEN; size_t len = state->total_len; int i; memset(chunk, 0x00, left); chunk += left; /* Storing of len * 8 as a big endian 64-bit without overflow. */ chunk[7] = (uint8_t) (len << 3); len >>= 5; for (i = 6; i >= 0; i--) { chunk[i] = (uint8_t) len; len >>= 8; } state->total_len_delivered = 1; } else { memset(chunk, 0x00, space_in_chunk); } return 1; } /* * Limitations: * - Since input is a pointer in RAM, the data to hash should be in RAM, which could be a problem * for large data sizes. * - SHA algorithms theoretically operate on bit strings. However, this implementation has no support * for bit string lengths that are not multiples of eight, and it really operates on arrays of bytes. * In particular, the len parameter is a number of bytes. */ void calc_sha256(uint8_t* hash, const void* input, size_t len) { /* * Note 1: All integers (expect indexes) are 32-bit unsigned integers and addition is calculated modulo 2^32. * 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 * Note 3: The compression function uses 8 working variables, a through h * Note 4: Big-endian convention is used when expressing the constants in this pseudocode, * and when parsing message block data from bytes to words, for example, * the first word of the input message "abc" after padding is 0x61626380 */ /* * Initialize hash values: * (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19): */ uint32_t h[] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }; unsigned i, j; /* 512-bit chunks is what we will operate on. */ uint8_t chunk[64]; struct buffer_state state; init_buf_state(&state, input, len); while (calc_chunk(chunk, &state)) { uint32_t ah[8]; const uint8_t *p = chunk; /* Initialize working variables to current hash value: */ for (i = 0; i < 8; i++) ah[i] = h[i]; /* Compression function main loop: */ for (i = 0; i < 4; i++) { /* * The w-array is really w[64], but since we only need * 16 of them at a time, we save stack by calculating * 16 at a time. * * This optimization was not there initially and the * rest of the comments about w[64] are kept in their * initial state. */ /* * create a 64-entry message schedule array w[0..63] of 32-bit words * (The initial values in w[0..63] don't matter, so many implementations zero them here) * copy chunk into first 16 words w[0..15] of the message schedule array */ uint32_t w[16]; for (j = 0; j < 16; j++) { if (i == 0) { w[j] = (uint32_t) p[0] << 24 | (uint32_t) p[1] << 16 | (uint32_t) p[2] << 8 | (uint32_t) p[3]; p += 4; } else { /* Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array: */ const uint32_t s0 = right_rot(w[(j + 1) & 0xf], 7) ^ right_rot(w[(j + 1) & 0xf], 18) ^ (w[(j + 1) & 0xf] >> 3); const uint32_t s1 = right_rot(w[(j + 14) & 0xf], 17) ^ right_rot(w[(j + 14) & 0xf], 19) ^ (w[(j + 14) & 0xf] >> 10); w[j] = w[j] + s0 + w[(j + 9) & 0xf] + s1; } const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25); const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]); const uint32_t temp1 = ah[7] + s1 + ch + k[i << 4 | j] + w[j]; const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22); const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]); const uint32_t temp2 = s0 + maj; ah[7] = ah[6]; ah[6] = ah[5]; ah[5] = ah[4]; ah[4] = ah[3] + temp1; ah[3] = ah[2]; ah[2] = ah[1]; ah[1] = ah[0]; ah[0] = temp1 + temp2; } } /* Add the compressed chunk to the current hash value: */ for (i = 0; i < 8; i++) h[i] += ah[i]; } /* Produce the final hash value (big-endian): */ for (i = 0, j = 0; i < 8; i++) { hash[j++] = (uint8_t) (h[i] >> 24); hash[j++] = (uint8_t) (h[i] >> 16); hash[j++] = (uint8_t) (h[i] >> 8); hash[j++] = (uint8_t) h[i]; } } ================================================ FILE: src/shellext/balance.cpp ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "shellext.h" #include "balance.h" #include "resource.h" #include "../btrfsioctl.h" #include #include #include #include #include #define NO_SHLWAPI_STRFCNS #include #include static uint64_t convtypes2[] = { BLOCK_FLAG_SINGLE, BLOCK_FLAG_DUPLICATE, BLOCK_FLAG_RAID0, BLOCK_FLAG_RAID1, BLOCK_FLAG_RAID5, BLOCK_FLAG_RAID1C3, BLOCK_FLAG_RAID6, BLOCK_FLAG_RAID10, BLOCK_FLAG_RAID1C4 }; static WCHAR hex_digit(uint8_t u) { if (u >= 0xa && u <= 0xf) return (uint8_t)(u - 0xa + 'a'); else return (uint8_t)(u + '0'); } static void serialize(void* data, ULONG len, WCHAR* s) { uint8_t* d; d = (uint8_t*)data; while (true) { *s = hex_digit((uint8_t)(*d >> 4)); s++; *s = hex_digit(*d & 0xf); s++; d++; len--; if (len == 0) { *s = 0; return; } } } void BtrfsBalance::StartBalance(HWND hwndDlg) { wstring t; WCHAR modfn[MAX_PATH], u[600]; SHELLEXECUTEINFOW sei; btrfs_start_balance bsb; GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR)); t = L"\""s + modfn + L"\",StartBalance "s + fn + L" "s; RtlCopyMemory(&bsb.opts[0], &data_opts, sizeof(btrfs_balance_opts)); RtlCopyMemory(&bsb.opts[1], &metadata_opts, sizeof(btrfs_balance_opts)); RtlCopyMemory(&bsb.opts[2], &system_opts, sizeof(btrfs_balance_opts)); if (IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED) bsb.opts[0].flags |= BTRFS_BALANCE_OPTS_ENABLED; else bsb.opts[0].flags &= ~BTRFS_BALANCE_OPTS_ENABLED; if (IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED) bsb.opts[1].flags |= BTRFS_BALANCE_OPTS_ENABLED; else bsb.opts[1].flags &= ~BTRFS_BALANCE_OPTS_ENABLED; if (IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED) bsb.opts[2].flags |= BTRFS_BALANCE_OPTS_ENABLED; else bsb.opts[2].flags &= ~BTRFS_BALANCE_OPTS_ENABLED; serialize(&bsb, sizeof(btrfs_start_balance), u); t += u; RtlZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); sei.hwnd = hwndDlg; sei.lpVerb = L"runas"; sei.lpFile = L"rundll32.exe"; sei.lpParameters = t.c_str(); sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExW(&sei)) throw last_error(GetLastError()); cancelling = false; removing = false; shrinking = false; balance_status = BTRFS_BALANCE_RUNNING; EnableWindow(GetDlgItem(hwndDlg, IDC_PAUSE_BALANCE), true); EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_BALANCE), true); EnableWindow(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), true); EnableWindow(GetDlgItem(hwndDlg, IDC_DATA), false); EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA), false); EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM), false); EnableWindow(GetDlgItem(hwndDlg, IDC_DATA_OPTIONS), data_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA_OPTIONS), metadata_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM_OPTIONS), system_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), false); WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); } void BtrfsBalance::PauseBalance(HWND hwndDlg) { WCHAR modfn[MAX_PATH]; wstring t; SHELLEXECUTEINFOW sei; GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR)); t = L"\""s + modfn + L"\",PauseBalance " + fn; RtlZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); sei.hwnd = hwndDlg; sei.lpVerb = L"runas"; sei.lpFile = L"rundll32.exe"; sei.lpParameters = t.c_str(); sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExW(&sei)) throw last_error(GetLastError()); WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); } void BtrfsBalance::StopBalance(HWND hwndDlg) { WCHAR modfn[MAX_PATH]; wstring t; SHELLEXECUTEINFOW sei; GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR)); t = L"\""s + modfn + L"\",StopBalance " + fn; RtlZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); sei.hwnd = hwndDlg; sei.lpVerb = L"runas"; sei.lpFile = L"rundll32.exe"; sei.lpParameters = t.c_str(); sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExW(&sei)) throw last_error(GetLastError()); cancelling = true; WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); } void BtrfsBalance::RefreshBalanceDlg(HWND hwndDlg, bool first) { bool balancing = false; wstring s, t; { win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_BALANCE, nullptr, 0, &bqb, sizeof(btrfs_query_balance)); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } else throw last_error(GetLastError()); } if (cancelling) bqb.status = BTRFS_BALANCE_STOPPED; balancing = bqb.status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED); if (!balancing) { if (first || balance_status != BTRFS_BALANCE_STOPPED) { int resid; EnableWindow(GetDlgItem(hwndDlg, IDC_PAUSE_BALANCE), false); EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_BALANCE), false); SendMessageW(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), PBM_SETSTATE, PBST_NORMAL, 0); EnableWindow(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), false); EnableWindow(GetDlgItem(hwndDlg, IDC_DATA), true); EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA), true); EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM), true); if (balance_status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED)) { CheckDlgButton(hwndDlg, IDC_DATA, BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_METADATA, BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_SYSTEM, BST_UNCHECKED); SendMessageW(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), PBM_SETPOS, 0, 0); } EnableWindow(GetDlgItem(hwndDlg, IDC_DATA_OPTIONS), IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA_OPTIONS), IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM_OPTIONS), IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED ? true : false); if (bqb.status & BTRFS_BALANCE_ERROR) { if (removing) resid = IDS_BALANCE_FAILED_REMOVAL; else if (shrinking) resid = IDS_BALANCE_FAILED_SHRINK; else resid = IDS_BALANCE_FAILED; if (!load_string(module, resid, s)) throw last_error(GetLastError()); wstring_sprintf(t, s, bqb.error, format_ntstatus(bqb.error).c_str()); SetDlgItemTextW(hwndDlg, IDC_BALANCE_STATUS, t.c_str()); } else { if (cancelling) resid = removing ? IDS_BALANCE_CANCELLED_REMOVAL : (shrinking ? IDS_BALANCE_CANCELLED_SHRINK : IDS_BALANCE_CANCELLED); else if (balance_status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED)) resid = removing ? IDS_BALANCE_COMPLETE_REMOVAL : (shrinking ? IDS_BALANCE_COMPLETE_SHRINK : IDS_BALANCE_COMPLETE); else resid = IDS_NO_BALANCE; if (!load_string(module, resid, s)) throw last_error(GetLastError()); SetDlgItemTextW(hwndDlg, IDC_BALANCE_STATUS, s.c_str()); } EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), !readonly && (IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED || IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED || IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED) ? true: false); balance_status = bqb.status; cancelling = false; } return; } if (first || !(balance_status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED))) { EnableWindow(GetDlgItem(hwndDlg, IDC_PAUSE_BALANCE), true); EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_BALANCE), true); EnableWindow(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), true); EnableWindow(GetDlgItem(hwndDlg, IDC_DATA), false); EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA), false); EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM), false); CheckDlgButton(hwndDlg, IDC_DATA, bqb.data_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_METADATA, bqb.metadata_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_SYSTEM, bqb.system_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? BST_CHECKED : BST_UNCHECKED); EnableWindow(GetDlgItem(hwndDlg, IDC_DATA_OPTIONS), bqb.data_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA_OPTIONS), bqb.metadata_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM_OPTIONS), bqb.system_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), false); } SendMessageW(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), PBM_SETRANGE32, 0, (LPARAM)bqb.total_chunks); SendMessageW(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), PBM_SETPOS, (WPARAM)(bqb.total_chunks - bqb.chunks_left), 0); if (bqb.status & BTRFS_BALANCE_PAUSED && balance_status != bqb.status) SendMessageW(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), PBM_SETSTATE, PBST_PAUSED, 0); else if (!(bqb.status & BTRFS_BALANCE_PAUSED) && balance_status & BTRFS_BALANCE_PAUSED) SendMessageW(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), PBM_SETSTATE, PBST_NORMAL, 0); balance_status = bqb.status; if (bqb.status & BTRFS_BALANCE_REMOVAL) { if (!load_string(module, balance_status & BTRFS_BALANCE_PAUSED ? IDS_BALANCE_PAUSED_REMOVAL : IDS_BALANCE_RUNNING_REMOVAL, s)) throw last_error(GetLastError()); wstring_sprintf(t, s, bqb.data_opts.devid, bqb.total_chunks - bqb.chunks_left, bqb.total_chunks, (float)(bqb.total_chunks - bqb.chunks_left) * 100.0f / (float)bqb.total_chunks); removing = true; shrinking = false; } else if (bqb.status & BTRFS_BALANCE_SHRINKING) { if (!load_string(module, balance_status & BTRFS_BALANCE_PAUSED ? IDS_BALANCE_PAUSED_SHRINK : IDS_BALANCE_RUNNING_SHRINK, s)) throw last_error(GetLastError()); wstring_sprintf(t, s, bqb.data_opts.devid, bqb.total_chunks - bqb.chunks_left, bqb.total_chunks, (float)(bqb.total_chunks - bqb.chunks_left) * 100.0f / (float)bqb.total_chunks); removing = false; shrinking = true; } else { if (!load_string(module, balance_status & BTRFS_BALANCE_PAUSED ? IDS_BALANCE_PAUSED : IDS_BALANCE_RUNNING, s)) throw last_error(GetLastError()); wstring_sprintf(t, s, bqb.total_chunks - bqb.chunks_left, bqb.total_chunks, (float)(bqb.total_chunks - bqb.chunks_left) * 100.0f / (float)bqb.total_chunks); removing = false; shrinking = false; } SetDlgItemTextW(hwndDlg, IDC_BALANCE_STATUS, t.c_str()); } void BtrfsBalance::SaveBalanceOpts(HWND hwndDlg) { btrfs_balance_opts* opts; switch (opts_type) { case 1: opts = &data_opts; break; case 2: opts = &metadata_opts; break; case 3: opts = &system_opts; break; default: return; } RtlZeroMemory(opts, sizeof(btrfs_balance_opts)); if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES) == BST_CHECKED) { opts->flags |= BTRFS_BALANCE_OPTS_PROFILES; if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_SINGLE) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_SINGLE; if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_DUP) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_DUPLICATE; if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID0) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID0; if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID1) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID1; if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID10) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID10; if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID5) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID5; if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID6) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID6; if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID1C3) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID1C3; if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID1C4) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID1C4; } if (IsDlgButtonChecked(hwndDlg, IDC_DEVID) == BST_CHECKED) { opts->flags |= BTRFS_BALANCE_OPTS_DEVID; auto sel = SendMessageW(GetDlgItem(hwndDlg, IDC_DEVID_COMBO), CB_GETCURSEL, 0, 0); if (sel == CB_ERR) opts->flags &= ~BTRFS_BALANCE_OPTS_DEVID; else { btrfs_device* bd = devices; int i = 0; while (true) { if (i == sel) { opts->devid = bd->dev_id; break; } i++; if (bd->next_entry > 0) bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry); else break; } if (opts->devid == 0) opts->flags &= ~BTRFS_BALANCE_OPTS_DEVID; } } if (IsDlgButtonChecked(hwndDlg, IDC_DRANGE) == BST_CHECKED) { WCHAR s[255]; opts->flags |= BTRFS_BALANCE_OPTS_DRANGE; GetWindowTextW(GetDlgItem(hwndDlg, IDC_DRANGE_START), s, sizeof(s) / sizeof(WCHAR)); opts->drange_start = _wtoi64(s); GetWindowTextW(GetDlgItem(hwndDlg, IDC_DRANGE_END), s, sizeof(s) / sizeof(WCHAR)); opts->drange_end = _wtoi64(s); if (opts->drange_end < opts->drange_start) throw string_error(IDS_DRANGE_END_BEFORE_START); } if (IsDlgButtonChecked(hwndDlg, IDC_VRANGE) == BST_CHECKED) { WCHAR s[255]; opts->flags |= BTRFS_BALANCE_OPTS_VRANGE; GetWindowTextW(GetDlgItem(hwndDlg, IDC_VRANGE_START), s, sizeof(s) / sizeof(WCHAR)); opts->vrange_start = _wtoi64(s); GetWindowTextW(GetDlgItem(hwndDlg, IDC_VRANGE_END), s, sizeof(s) / sizeof(WCHAR)); opts->vrange_end = _wtoi64(s); if (opts->vrange_end < opts->vrange_start) throw string_error(IDS_VRANGE_END_BEFORE_START); } if (IsDlgButtonChecked(hwndDlg, IDC_LIMIT) == BST_CHECKED) { WCHAR s[255]; opts->flags |= BTRFS_BALANCE_OPTS_LIMIT; GetWindowTextW(GetDlgItem(hwndDlg, IDC_LIMIT_START), s, sizeof(s) / sizeof(WCHAR)); opts->limit_start = _wtoi64(s); GetWindowTextW(GetDlgItem(hwndDlg, IDC_LIMIT_END), s, sizeof(s) / sizeof(WCHAR)); opts->limit_end = _wtoi64(s); if (opts->limit_end < opts->limit_start) throw string_error(IDS_LIMIT_END_BEFORE_START); } if (IsDlgButtonChecked(hwndDlg, IDC_STRIPES) == BST_CHECKED) { WCHAR s[255]; opts->flags |= BTRFS_BALANCE_OPTS_STRIPES; GetWindowTextW(GetDlgItem(hwndDlg, IDC_STRIPES_START), s, sizeof(s) / sizeof(WCHAR)); opts->stripes_start = (uint8_t)_wtoi(s); GetWindowTextW(GetDlgItem(hwndDlg, IDC_STRIPES_END), s, sizeof(s) / sizeof(WCHAR)); opts->stripes_end = (uint8_t)_wtoi(s); if (opts->stripes_end < opts->stripes_start) throw string_error(IDS_STRIPES_END_BEFORE_START); } if (IsDlgButtonChecked(hwndDlg, IDC_USAGE) == BST_CHECKED) { WCHAR s[255]; opts->flags |= BTRFS_BALANCE_OPTS_USAGE; GetWindowTextW(GetDlgItem(hwndDlg, IDC_USAGE_START), s, sizeof(s) / sizeof(WCHAR)); opts->usage_start = (uint8_t)_wtoi(s); GetWindowTextW(GetDlgItem(hwndDlg, IDC_USAGE_END), s, sizeof(s) / sizeof(WCHAR)); opts->usage_end = (uint8_t)_wtoi(s); if (opts->usage_end < opts->usage_start) throw string_error(IDS_USAGE_END_BEFORE_START); } if (IsDlgButtonChecked(hwndDlg, IDC_CONVERT) == BST_CHECKED) { opts->flags |= BTRFS_BALANCE_OPTS_CONVERT; auto sel = SendMessageW(GetDlgItem(hwndDlg, IDC_CONVERT_COMBO), CB_GETCURSEL, 0, 0); if (sel == CB_ERR || (unsigned int)sel >= sizeof(convtypes2) / sizeof(convtypes2[0])) opts->flags &= ~BTRFS_BALANCE_OPTS_CONVERT; else { opts->convert = convtypes2[sel]; if (IsDlgButtonChecked(hwndDlg, IDC_SOFT) == BST_CHECKED) opts->flags |= BTRFS_BALANCE_OPTS_SOFT; } } EndDialog(hwndDlg, 0); } INT_PTR CALLBACK BtrfsBalance::BalanceOptsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) { try { switch (uMsg) { case WM_INITDIALOG: { HWND devcb, convcb; btrfs_device* bd; btrfs_balance_opts* opts; static int convtypes[] = { IDS_SINGLE2, IDS_DUP, IDS_RAID0, IDS_RAID1, IDS_RAID5, IDS_RAID1C3, IDS_RAID6, IDS_RAID10, IDS_RAID1C4, 0 }; int i, num_devices = 0, num_writeable_devices = 0; wstring s, u; bool balance_started = balance_status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED); switch (opts_type) { case 1: opts = balance_started ? &bqb.data_opts : &data_opts; break; case 2: opts = balance_started ? &bqb.metadata_opts : &metadata_opts; break; case 3: opts = balance_started ? &bqb.system_opts : &system_opts; break; default: return true; } EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); devcb = GetDlgItem(hwndDlg, IDC_DEVID_COMBO); if (!load_string(module, IDS_DEVID_LIST, u)) throw last_error(GetLastError()); bd = devices; while (true) { wstring t, v; if (bd->device_number == 0xffffffff) s = wstring(bd->name, bd->namelen); else if (bd->partition_number == 0) { if (!load_string(module, IDS_DISK_NUM, v)) throw last_error(GetLastError()); wstring_sprintf(s, v, bd->device_number); } else { if (!load_string(module, IDS_DISK_PART_NUM, v)) throw last_error(GetLastError()); wstring_sprintf(s, v, bd->device_number, bd->partition_number); } wstring_sprintf(t, u, bd->dev_id, s.c_str()); SendMessageW(devcb, CB_ADDSTRING, 0, (LPARAM)t.c_str()); if (opts->devid == bd->dev_id) SendMessageW(devcb, CB_SETCURSEL, num_devices, 0); num_devices++; if (!bd->readonly) num_writeable_devices++; if (bd->next_entry > 0) bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry); else break; } convcb = GetDlgItem(hwndDlg, IDC_CONVERT_COMBO); if (num_writeable_devices == 0) num_writeable_devices = num_devices; i = 0; while (convtypes[i] != 0) { if (!load_string(module, convtypes[i], s)) throw last_error(GetLastError()); SendMessageW(convcb, CB_ADDSTRING, 0, (LPARAM)s.c_str()); if (opts->convert == convtypes2[i]) SendMessageW(convcb, CB_SETCURSEL, i, 0); i++; if (num_writeable_devices < 2 && i == 2) break; else if (num_writeable_devices < 3 && i == 4) break; else if (num_writeable_devices < 4 && i == 6) break; } // profiles CheckDlgButton(hwndDlg, IDC_PROFILES, opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_PROFILES_SINGLE, opts->profiles & BLOCK_FLAG_SINGLE ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_PROFILES_DUP, opts->profiles & BLOCK_FLAG_DUPLICATE ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_PROFILES_RAID0, opts->profiles & BLOCK_FLAG_RAID0 ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_PROFILES_RAID1, opts->profiles & BLOCK_FLAG_RAID1 ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_PROFILES_RAID10, opts->profiles & BLOCK_FLAG_RAID10 ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_PROFILES_RAID5, opts->profiles & BLOCK_FLAG_RAID5 ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_PROFILES_RAID6, opts->profiles & BLOCK_FLAG_RAID6 ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_PROFILES_RAID1C3, opts->profiles & BLOCK_FLAG_RAID1C3 ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_PROFILES_RAID1C4, opts->profiles & BLOCK_FLAG_RAID1C4 ? BST_CHECKED : BST_UNCHECKED); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_SINGLE), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_DUP), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID0), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID1), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID10), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID5), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID6), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID1C3), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID1C4), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES), balance_started ? false : true); // usage CheckDlgButton(hwndDlg, IDC_USAGE, opts->flags & BTRFS_BALANCE_OPTS_USAGE ? BST_CHECKED : BST_UNCHECKED); s = to_wstring(opts->usage_start); SetDlgItemTextW(hwndDlg, IDC_USAGE_START, s.c_str()); SendMessageW(GetDlgItem(hwndDlg, IDC_USAGE_START_SPINNER), UDM_SETRANGE32, 0, 100); s = to_wstring(opts->usage_end); SetDlgItemTextW(hwndDlg, IDC_USAGE_END, s.c_str()); SendMessageW(GetDlgItem(hwndDlg, IDC_USAGE_END_SPINNER), UDM_SETRANGE32, 0, 100); EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_START), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_USAGE ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_START_SPINNER), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_USAGE ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_END), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_USAGE ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_END_SPINNER), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_USAGE ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE), balance_started ? false : true); // devid if (num_devices < 2 || balance_started) EnableWindow(GetDlgItem(hwndDlg, IDC_DEVID), false); CheckDlgButton(hwndDlg, IDC_DEVID, opts->flags & BTRFS_BALANCE_OPTS_DEVID ? BST_CHECKED : BST_UNCHECKED); EnableWindow(devcb, (opts->flags & BTRFS_BALANCE_OPTS_DEVID && num_devices >= 2 && !balance_started) ? true : false); // drange CheckDlgButton(hwndDlg, IDC_DRANGE, opts->flags & BTRFS_BALANCE_OPTS_DRANGE ? BST_CHECKED : BST_UNCHECKED); s = to_wstring(opts->drange_start); SetDlgItemTextW(hwndDlg, IDC_DRANGE_START, s.c_str()); s = to_wstring(opts->drange_end); SetDlgItemTextW(hwndDlg, IDC_DRANGE_END, s.c_str()); EnableWindow(GetDlgItem(hwndDlg, IDC_DRANGE_START), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_DRANGE ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_DRANGE_END), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_DRANGE ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_DRANGE), balance_started ? false : true); // vrange CheckDlgButton(hwndDlg, IDC_VRANGE, opts->flags & BTRFS_BALANCE_OPTS_VRANGE ? BST_CHECKED : BST_UNCHECKED); s = to_wstring(opts->vrange_start); SetDlgItemTextW(hwndDlg, IDC_VRANGE_START, s.c_str()); s = to_wstring(opts->vrange_end); SetDlgItemTextW(hwndDlg, IDC_VRANGE_END, s.c_str()); EnableWindow(GetDlgItem(hwndDlg, IDC_VRANGE_START), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_VRANGE ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_VRANGE_END), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_VRANGE ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_VRANGE), balance_started ? false : true); // limit CheckDlgButton(hwndDlg, IDC_LIMIT, opts->flags & BTRFS_BALANCE_OPTS_LIMIT ? BST_CHECKED : BST_UNCHECKED); s = to_wstring(opts->limit_start); SetDlgItemTextW(hwndDlg, IDC_LIMIT_START, s.c_str()); SendMessageW(GetDlgItem(hwndDlg, IDC_LIMIT_START_SPINNER), UDM_SETRANGE32, 0, 0x7fffffff); s = to_wstring(opts->limit_end); SetDlgItemTextW(hwndDlg, IDC_LIMIT_END, s.c_str()); SendMessageW(GetDlgItem(hwndDlg, IDC_LIMIT_END_SPINNER), UDM_SETRANGE32, 0, 0x7fffffff); EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_START), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_LIMIT ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_START_SPINNER), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_LIMIT ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_END), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_LIMIT ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_END_SPINNER), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_LIMIT ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT), balance_started ? false : true); // stripes CheckDlgButton(hwndDlg, IDC_STRIPES, opts->flags & BTRFS_BALANCE_OPTS_STRIPES ? BST_CHECKED : BST_UNCHECKED); s = to_wstring(opts->stripes_start); SetDlgItemTextW(hwndDlg, IDC_STRIPES_START, s.c_str()); SendMessageW(GetDlgItem(hwndDlg, IDC_STRIPES_START_SPINNER), UDM_SETRANGE32, 0, 0xffff); s = to_wstring(opts->stripes_end); SetDlgItemTextW(hwndDlg, IDC_STRIPES_END, s.c_str()); SendMessageW(GetDlgItem(hwndDlg, IDC_STRIPES_END_SPINNER), UDM_SETRANGE32, 0, 0xffff); EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_START), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_STRIPES ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_START_SPINNER), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_STRIPES ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_END), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_STRIPES ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_END_SPINNER), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_STRIPES ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES), balance_started ? false : true); // convert CheckDlgButton(hwndDlg, IDC_CONVERT, opts->flags & BTRFS_BALANCE_OPTS_CONVERT ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_SOFT, opts->flags & BTRFS_BALANCE_OPTS_SOFT ? BST_CHECKED : BST_UNCHECKED); EnableWindow(GetDlgItem(hwndDlg, IDC_SOFT), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_CONVERT ? true : false); EnableWindow(convcb, !balance_started && opts->flags & BTRFS_BALANCE_OPTS_CONVERT ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_CONVERT), balance_started ? false : true); break; } case WM_COMMAND: switch (HIWORD(wParam)) { case BN_CLICKED: switch (LOWORD(wParam)) { case IDOK: if (balance_status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED)) EndDialog(hwndDlg, 0); else SaveBalanceOpts(hwndDlg); return true; case IDCANCEL: EndDialog(hwndDlg, 0); return true; case IDC_PROFILES: { bool enabled = IsDlgButtonChecked(hwndDlg, IDC_PROFILES) == BST_CHECKED ? true : false; EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_SINGLE), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_DUP), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID0), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID1), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID10), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID5), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID6), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID1C3), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID1C4), enabled); break; } case IDC_USAGE: { bool enabled = IsDlgButtonChecked(hwndDlg, IDC_USAGE) == BST_CHECKED ? true : false; EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_START), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_START_SPINNER), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_END), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_END_SPINNER), enabled); break; } case IDC_DEVID: { bool enabled = IsDlgButtonChecked(hwndDlg, IDC_DEVID) == BST_CHECKED ? true : false; EnableWindow(GetDlgItem(hwndDlg, IDC_DEVID_COMBO), enabled); break; } case IDC_DRANGE: { bool enabled = IsDlgButtonChecked(hwndDlg, IDC_DRANGE) == BST_CHECKED ? true : false; EnableWindow(GetDlgItem(hwndDlg, IDC_DRANGE_START), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_DRANGE_END), enabled); break; } case IDC_VRANGE: { bool enabled = IsDlgButtonChecked(hwndDlg, IDC_VRANGE) == BST_CHECKED ? true : false; EnableWindow(GetDlgItem(hwndDlg, IDC_VRANGE_START), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_VRANGE_END), enabled); break; } case IDC_LIMIT: { bool enabled = IsDlgButtonChecked(hwndDlg, IDC_LIMIT) == BST_CHECKED ? true : false; EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_START), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_START_SPINNER), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_END), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_END_SPINNER), enabled); break; } case IDC_STRIPES: { bool enabled = IsDlgButtonChecked(hwndDlg, IDC_STRIPES) == BST_CHECKED ? true : false; EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_START), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_START_SPINNER), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_END), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_END_SPINNER), enabled); break; } case IDC_CONVERT: { bool enabled = IsDlgButtonChecked(hwndDlg, IDC_CONVERT) == BST_CHECKED ? true : false; EnableWindow(GetDlgItem(hwndDlg, IDC_CONVERT_COMBO), enabled); EnableWindow(GetDlgItem(hwndDlg, IDC_SOFT), enabled); break; } } break; } break; } } catch (const exception& e) { error_message(hwndDlg, e.what()); } return false; } static INT_PTR CALLBACK stub_BalanceOptsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { BtrfsBalance* bb; if (uMsg == WM_INITDIALOG) { SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); bb = (BtrfsBalance*)lParam; } else { bb = (BtrfsBalance*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); } if (bb) return bb->BalanceOptsDlgProc(hwndDlg, uMsg, wParam, lParam); else return false; } void BtrfsBalance::ShowBalanceOptions(HWND hwndDlg, uint8_t type) { opts_type = type; DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_BALANCE_OPTIONS), hwndDlg, stub_BalanceOptsDlgProc, (LPARAM)this); } INT_PTR CALLBACK BtrfsBalance::BalanceDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) { try { switch (uMsg) { case WM_INITDIALOG: { EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); RtlZeroMemory(&data_opts, sizeof(btrfs_balance_opts)); RtlZeroMemory(&metadata_opts, sizeof(btrfs_balance_opts)); RtlZeroMemory(&system_opts, sizeof(btrfs_balance_opts)); removing = called_from_RemoveDevice; shrinking = called_from_ShrinkDevice; balance_status = (removing || shrinking) ? BTRFS_BALANCE_RUNNING : BTRFS_BALANCE_STOPPED; cancelling = false; RefreshBalanceDlg(hwndDlg, true); if (readonly) { EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), false); EnableWindow(GetDlgItem(hwndDlg, IDC_PAUSE_BALANCE), false); EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_BALANCE), false); } SendMessageW(GetDlgItem(hwndDlg, IDC_START_BALANCE), BCM_SETSHIELD, 0, true); SendMessageW(GetDlgItem(hwndDlg, IDC_PAUSE_BALANCE), BCM_SETSHIELD, 0, true); SendMessageW(GetDlgItem(hwndDlg, IDC_CANCEL_BALANCE), BCM_SETSHIELD, 0, true); SetTimer(hwndDlg, 1, 1000, nullptr); break; } case WM_COMMAND: switch (HIWORD(wParam)) { case BN_CLICKED: switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: KillTimer(hwndDlg, 1); EndDialog(hwndDlg, 0); return true; case IDC_DATA: EnableWindow(GetDlgItem(hwndDlg, IDC_DATA_OPTIONS), IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), !readonly && (IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED || IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED || IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED) ? true: false); return true; case IDC_METADATA: EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA_OPTIONS), IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), !readonly && (IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED || IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED || IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED) ? true: false); return true; case IDC_SYSTEM: EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM_OPTIONS), IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED ? true : false); EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), !readonly && (IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED || IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED || IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED) ? true: false); return true; case IDC_DATA_OPTIONS: ShowBalanceOptions(hwndDlg, 1); return true; case IDC_METADATA_OPTIONS: ShowBalanceOptions(hwndDlg, 2); return true; case IDC_SYSTEM_OPTIONS: ShowBalanceOptions(hwndDlg, 3); return true; case IDC_START_BALANCE: StartBalance(hwndDlg); return true; case IDC_PAUSE_BALANCE: PauseBalance(hwndDlg); RefreshBalanceDlg(hwndDlg, false); return true; case IDC_CANCEL_BALANCE: StopBalance(hwndDlg); RefreshBalanceDlg(hwndDlg, false); return true; } break; } break; case WM_TIMER: RefreshBalanceDlg(hwndDlg, false); break; } } catch (const exception& e) { error_message(hwndDlg, e.what()); } return false; } static INT_PTR CALLBACK stub_BalanceDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { BtrfsBalance* bb; if (uMsg == WM_INITDIALOG) { SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); bb = (BtrfsBalance*)lParam; } else { bb = (BtrfsBalance*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); } if (bb) return bb->BalanceDlgProc(hwndDlg, uMsg, wParam, lParam); else return false; } void BtrfsBalance::ShowBalance(HWND hwndDlg) { btrfs_device* bd; if (devices) { free(devices); devices = nullptr; } { win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; ULONG devsize, i; i = 0; devsize = 1024; devices = (btrfs_device*)malloc(devsize); while (true) { Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_DEVICES, nullptr, 0, devices, devsize); if (Status == STATUS_BUFFER_OVERFLOW) { if (i < 8) { devsize += 1024; free(devices); devices = (btrfs_device*)malloc(devsize); i++; } else return; } else break; } if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } else throw last_error(GetLastError()); } readonly = true; bd = devices; while (true) { if (!bd->readonly) { readonly = false; break; } if (bd->next_entry > 0) bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry); else break; } DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_BALANCE), hwndDlg, stub_BalanceDlgProc, (LPARAM)this); } static uint8_t from_hex_digit(WCHAR c) { if (c >= 'a' && c <= 'f') return (uint8_t)(c - 'a' + 0xa); else if (c >= 'A' && c <= 'F') return (uint8_t)(c - 'A' + 0xa); else return (uint8_t)(c - '0'); } static void unserialize(void* data, ULONG len, WCHAR* s) { uint8_t* d; d = (uint8_t*)data; while (s[0] != 0 && s[1] != 0 && len > 0) { *d = (uint8_t)(from_hex_digit(s[0]) << 4) | from_hex_digit(s[1]); s += 2; d++; len--; } } extern "C" void CALLBACK StartBalanceW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) { try { WCHAR *s, *vol, *block; win_handle h, token; btrfs_start_balance bsb; TOKEN_PRIVILEGES tp; LUID luid; s = wcsstr(lpszCmdLine, L" "); if (!s) return; s[0] = 0; vol = lpszCmdLine; block = &s[1]; RtlZeroMemory(&bsb, sizeof(btrfs_start_balance)); unserialize(&bsb, sizeof(btrfs_start_balance), block); if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) throw last_error(GetLastError()); if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) throw last_error(GetLastError()); tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) throw last_error(GetLastError()); h = CreateFileW(vol, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_START_BALANCE, &bsb, sizeof(btrfs_start_balance), nullptr, 0); if (Status == STATUS_DEVICE_NOT_READY) { btrfs_query_scrub bqs; NTSTATUS Status2; Status2 = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_SCRUB, nullptr, 0, &bqs, sizeof(btrfs_query_scrub)); if ((NT_SUCCESS(Status2) || Status2 == STATUS_BUFFER_OVERFLOW) && bqs.status != BTRFS_SCRUB_STOPPED) throw string_error(IDS_BALANCE_SCRUB_RUNNING); } if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } else throw last_error(GetLastError()); } catch (const exception& e) { error_message(hwnd, e.what()); } } extern "C" void CALLBACK PauseBalanceW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) { try { win_handle h, token; TOKEN_PRIVILEGES tp; LUID luid; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) throw last_error(GetLastError()); if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) throw last_error(GetLastError()); tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) throw last_error(GetLastError()); h = CreateFileW(lpszCmdLine, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_query_balance bqb2; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_BALANCE, nullptr, 0, &bqb2, sizeof(btrfs_query_balance)); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (bqb2.status & BTRFS_BALANCE_PAUSED) Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RESUME_BALANCE, nullptr, 0, nullptr, 0); else if (bqb2.status & BTRFS_BALANCE_RUNNING) Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_PAUSE_BALANCE, nullptr, 0, nullptr, 0); else return; if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } else throw last_error(GetLastError()); } catch (const exception& e) { error_message(hwnd, e.what()); } } extern "C" void CALLBACK StopBalanceW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) { try { win_handle h, token; TOKEN_PRIVILEGES tp; LUID luid; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) throw last_error(GetLastError()); if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) throw last_error(GetLastError()); tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) throw last_error(GetLastError()); h = CreateFileW(lpszCmdLine, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_query_balance bqb2; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_BALANCE, nullptr, 0, &bqb2, sizeof(btrfs_query_balance)); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (bqb2.status & BTRFS_BALANCE_PAUSED || bqb2.status & BTRFS_BALANCE_RUNNING) Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_STOP_BALANCE, nullptr, 0, nullptr, 0); else return; if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } else throw last_error(GetLastError()); } catch (const exception& e) { error_message(hwnd, e.what()); } } ================================================ FILE: src/shellext/balance.h ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #pragma once #include #include "../btrfsioctl.h" class BtrfsBalance { public: BtrfsBalance(const wstring& drive, bool RemoveDevice = false, bool ShrinkDevice = false) { removing = false; devices = nullptr; called_from_RemoveDevice = RemoveDevice; called_from_ShrinkDevice = ShrinkDevice; fn = drive; } void ShowBalance(HWND hwndDlg); INT_PTR CALLBACK BalanceDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); INT_PTR CALLBACK BalanceOptsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); private: void ShowBalanceOptions(HWND hwndDlg, uint8_t type); void SaveBalanceOpts(HWND hwndDlg); void StartBalance(HWND hwndDlg); void RefreshBalanceDlg(HWND hwndDlg, bool first); void PauseBalance(HWND hwndDlg); void StopBalance(HWND hwndDlg); uint32_t balance_status; btrfs_balance_opts data_opts, metadata_opts, system_opts; uint8_t opts_type; btrfs_query_balance bqb; bool cancelling; bool removing; bool shrinking; wstring fn; btrfs_device* devices; bool readonly; bool called_from_RemoveDevice, called_from_ShrinkDevice; }; ================================================ FILE: src/shellext/contextmenu.cpp ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "shellext.h" #include #include #include #include #include #include #include #define NO_SHLWAPI_STRFCNS #include #include "contextmenu.h" #include "resource.h" #include "../btrfsioctl.h" #define NEW_SUBVOL_VERBA "newsubvol" #define NEW_SUBVOL_VERBW L"newsubvol" #define SNAPSHOT_VERBA "snapshot" #define SNAPSHOT_VERBW L"snapshot" #define REFLINK_VERBA "reflink" #define REFLINK_VERBW L"reflink" #define RECV_VERBA "recvsubvol" #define RECV_VERBW L"recvsubvol" #define SEND_VERBA "sendsubvol" #define SEND_VERBW L"sendsubvol" typedef struct { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; } reparse_header; static void path_remove_file(wstring& path); // FIXME - don't assume subvol's top inode is 0x100 HRESULT __stdcall BtrfsContextMenu::QueryInterface(REFIID riid, void **ppObj) { if (riid == IID_IUnknown || riid == IID_IContextMenu) { *ppObj = static_cast(this); AddRef(); return S_OK; } else if (riid == IID_IShellExtInit) { *ppObj = static_cast(this); AddRef(); return S_OK; } *ppObj = nullptr; return E_NOINTERFACE; } HRESULT __stdcall BtrfsContextMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY) { IO_STATUS_BLOCK iosb; btrfs_get_file_ids bgfi; NTSTATUS Status; if (!pidlFolder) { FORMATETC format = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; UINT num_files, i; WCHAR fn[MAX_PATH]; HDROP hdrop; if (!pdtobj) return E_FAIL; stgm.tymed = TYMED_HGLOBAL; if (FAILED(pdtobj->GetData(&format, &stgm))) return E_INVALIDARG; stgm_set = true; global_lock gl(stgm.hGlobal); hdrop = (HDROP)gl.ptr; if (!hdrop) { ReleaseStgMedium(&stgm); stgm_set = false; return E_INVALIDARG; } num_files = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0); for (i = 0; i < num_files; i++) { if (DragQueryFileW(hdrop, i, fn, sizeof(fn) / sizeof(WCHAR))) { win_handle h = CreateFileW(fn, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (h != INVALID_HANDLE_VALUE) { Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids)); if (NT_SUCCESS(Status) && bgfi.inode == 0x100 && !bgfi.top) { wstring parpath; { win_handle h2; parpath = fn; path_remove_file(parpath); 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); if (h2 != INVALID_HANDLE_VALUE) allow_snapshot = true; } ignore = false; bg = false; return S_OK; } } } } return S_OK; } { WCHAR pathbuf[MAX_PATH]; if (!SHGetPathFromIDListW(pidlFolder, pathbuf)) return E_FAIL; path = pathbuf; } { // check we have permissions to create new subdirectory 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); if (h == INVALID_HANDLE_VALUE) return E_FAIL; // check is Btrfs volume Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids)); if (!NT_SUCCESS(Status)) return E_FAIL; } ignore = false; bg = true; return S_OK; } static bool get_volume_path_parent(const WCHAR* fn, WCHAR* volpath, ULONG volpathlen) { WCHAR *f, *p; bool b; f = PathFindFileNameW(fn); if (f == fn) return GetVolumePathNameW(fn, volpath, volpathlen); p = (WCHAR*)malloc((f - fn + 1) * sizeof(WCHAR)); memcpy(p, fn, (f - fn) * sizeof(WCHAR)); p[f - fn] = 0; b = GetVolumePathNameW(p, volpath, volpathlen); free(p); return b; } static bool show_reflink_paste(const wstring& path) { HDROP hdrop; HANDLE lh; ULONG num_files; WCHAR fn[MAX_PATH], volpath1[255], volpath2[255]; if (!IsClipboardFormatAvailable(CF_HDROP)) return false; if (!GetVolumePathNameW(path.c_str(), volpath1, sizeof(volpath1) / sizeof(WCHAR))) return false; if (!OpenClipboard(nullptr)) return false; hdrop = (HDROP)GetClipboardData(CF_HDROP); if (!hdrop) { CloseClipboard(); return false; } global_lock gl(hdrop); lh = gl.ptr; if (!lh) { CloseClipboard(); return false; } num_files = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0); if (num_files == 0) { CloseClipboard(); return false; } if (!DragQueryFileW(hdrop, 0, fn, sizeof(fn) / sizeof(WCHAR))) { CloseClipboard(); return false; } if (!get_volume_path_parent(fn, volpath2, sizeof(volpath2) / sizeof(WCHAR))) { CloseClipboard(); return false; } CloseClipboard(); return !wcscmp(volpath1, volpath2); } // The code for putting an icon against a menu item comes from: // 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 static void InitBitmapInfo(BITMAPINFO* pbmi, ULONG cbInfo, LONG cx, LONG cy, WORD bpp) { ZeroMemory(pbmi, cbInfo); pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pbmi->bmiHeader.biPlanes = 1; pbmi->bmiHeader.biCompression = BI_RGB; pbmi->bmiHeader.biWidth = cx; pbmi->bmiHeader.biHeight = cy; pbmi->bmiHeader.biBitCount = bpp; } static HRESULT Create32BitHBITMAP(HDC hdc, const SIZE *psize, void **ppvBits, HBITMAP* phBmp) { BITMAPINFO bmi; HDC hdcUsed; *phBmp = nullptr; InitBitmapInfo(&bmi, sizeof(bmi), psize->cx, psize->cy, 32); hdcUsed = hdc ? hdc : GetDC(nullptr); if (hdcUsed) { *phBmp = CreateDIBSection(hdcUsed, &bmi, DIB_RGB_COLORS, ppvBits, nullptr, 0); if (hdc != hdcUsed) ReleaseDC(nullptr, hdcUsed); } return !*phBmp ? E_OUTOFMEMORY : S_OK; } void BtrfsContextMenu::get_uac_icon() { IWICImagingFactory* factory = nullptr; IWICBitmap* bitmap; HRESULT hr; hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory)); if (SUCCEEDED(hr)) { HANDLE icon; // We can't use IDI_SHIELD, as that will only give us the full-size icon icon = LoadImageW(GetModuleHandleW(L"user32.dll"), MAKEINTRESOURCEW(106)/* UAC shield */, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR); hr = factory->CreateBitmapFromHICON((HICON)icon, &bitmap); if (SUCCEEDED(hr)) { UINT cx, cy; hr = bitmap->GetSize(&cx, &cy); if (SUCCEEDED(hr)) { SIZE sz; BYTE* buf; sz.cx = (int)cx; sz.cy = -(int)cy; hr = Create32BitHBITMAP(nullptr, &sz, (void**)&buf, &uacicon); if (SUCCEEDED(hr)) { UINT stride = (UINT)(cx * sizeof(DWORD)); UINT buflen = cy * stride; bitmap->CopyPixels(nullptr, stride, buflen, buf); } } bitmap->Release(); } factory->Release(); } } HRESULT __stdcall BtrfsContextMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { wstring str; ULONG entries = 0; if (ignore) return E_INVALIDARG; if (uFlags & CMF_DEFAULTONLY) return S_OK; if (!bg) { if (allow_snapshot) { if (load_string(module, IDS_CREATE_SNAPSHOT, str) == 0) return E_FAIL; if (!InsertMenuW(hmenu, indexMenu, MF_BYPOSITION, idCmdFirst, str.c_str())) return E_FAIL; entries = 1; } if (idCmdFirst + entries <= idCmdLast) { MENUITEMINFOW mii; if (load_string(module, IDS_SEND_SUBVOL, str) == 0) return E_FAIL; if (!uacicon) get_uac_icon(); memset(&mii, 0, sizeof(MENUITEMINFOW)); mii.cbSize = sizeof(MENUITEMINFOW); mii.fMask = MIIM_STRING | MIIM_ID | MIIM_BITMAP; mii.dwTypeData = (WCHAR*)str.c_str(); mii.wID = idCmdFirst + entries; mii.hbmpItem = uacicon; if (!InsertMenuItemW(hmenu, indexMenu + entries, true, &mii)) return E_FAIL; entries++; } } else { if (load_string(module, IDS_NEW_SUBVOL, str) == 0) return E_FAIL; if (!InsertMenuW(hmenu, indexMenu, MF_BYPOSITION, idCmdFirst, str.c_str())) return E_FAIL; entries = 1; if (idCmdFirst + 1 <= idCmdLast) { MENUITEMINFOW mii; if (load_string(module, IDS_RECV_SUBVOL, str) == 0) return E_FAIL; if (!uacicon) get_uac_icon(); memset(&mii, 0, sizeof(MENUITEMINFOW)); mii.cbSize = sizeof(MENUITEMINFOW); mii.fMask = MIIM_STRING | MIIM_ID | MIIM_BITMAP; mii.dwTypeData = (WCHAR*)str.c_str(); mii.wID = idCmdFirst + 1; mii.hbmpItem = uacicon; if (!InsertMenuItemW(hmenu, indexMenu + 1, true, &mii)) return E_FAIL; entries++; } if (idCmdFirst + 2 <= idCmdLast && show_reflink_paste(path)) { if (load_string(module, IDS_REFLINK_PASTE, str) == 0) return E_FAIL; if (!InsertMenuW(hmenu, indexMenu + 2, MF_BYPOSITION, idCmdFirst + 2, str.c_str())) return E_FAIL; entries++; } } return MAKE_HRESULT(SEVERITY_SUCCESS, 0, entries); } static void path_remove_file(wstring& path) { size_t bs = path.rfind(L"\\"); if (bs == string::npos) return; if (bs == path.find(L"\\")) { // only one backslash path = path.substr(0, bs + 1); return; } path = path.substr(0, bs); } static void path_strip_path(wstring& path) { size_t bs = path.rfind(L"\\"); if (bs == string::npos) { path = L""; return; } path = path.substr(bs + 1); } static void create_snapshot(HWND, const wstring& fn) { win_handle h; NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_get_file_ids bgfi; h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (h != INVALID_HANDLE_VALUE) { Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids)); if (NT_SUCCESS(Status) && bgfi.inode == 0x100 && !bgfi.top) { wstring subvolname, parpath, searchpath, temp1, name, nameorig; win_handle h2; WIN32_FIND_DATAW wfd; SYSTEMTIME time; parpath = fn; path_remove_file(parpath); subvolname = fn; path_strip_path(subvolname); 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); if (h2 == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); if (!load_string(module, IDS_SNAPSHOT_FILENAME, temp1)) throw last_error(GetLastError()); GetLocalTime(&time); wstring_sprintf(name, temp1, subvolname.c_str(), time.wYear, time.wMonth, time.wDay); nameorig = name; searchpath = parpath + L"\\" + name; fff_handle fff = FindFirstFileW(searchpath.c_str(), &wfd); if (fff != INVALID_HANDLE_VALUE) { ULONG num = 2; do { name = nameorig + L" (" + to_wstring(num) + L")"; searchpath = parpath + L"\\" + name; fff = FindFirstFileW(searchpath.c_str(), &wfd); num++; } while (fff != INVALID_HANDLE_VALUE); } size_t namelen = name.length() * sizeof(WCHAR); auto bcs = (btrfs_create_snapshot*)malloc(sizeof(btrfs_create_snapshot) - 1 + namelen); bcs->readonly = false; bcs->posix = false; bcs->subvol = h; bcs->namelen = (uint16_t)namelen; memcpy(bcs->name, name.c_str(), namelen); Status = NtFsControlFile(h2, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, (ULONG)(sizeof(btrfs_create_snapshot) - 1 + namelen), nullptr, 0); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } } else throw last_error(GetLastError()); } static uint64_t __inline sector_align(uint64_t n, uint64_t a) { if (n & (a - 1)) n = (n + a) & ~(a - 1); return n; } void BtrfsContextMenu::reflink_copy(HWND hwnd, const WCHAR* fn, const WCHAR* dir) { win_handle source, dest; WCHAR* name, volpath1[255], volpath2[255]; wstring dirw, newpath; FILE_BASIC_INFO fbi; FILETIME atime, mtime; btrfs_inode_info bii; btrfs_set_inode_info bsii; ULONG bytesret; NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_set_xattr bsxa; // Thanks to 0xbadfca11, whose version of reflink for Windows provided a few pointers on what // to do here - https://github.com/0xbadfca11/reflink name = PathFindFileNameW(fn); dirw = dir; if (dir[0] != 0 && dir[wcslen(dir) - 1] != '\\') dirw += L"\\"; newpath = dirw; newpath += name; if (!get_volume_path_parent(fn, volpath1, sizeof(volpath1) / sizeof(WCHAR))) throw last_error(GetLastError()); if (!GetVolumePathNameW(dir, volpath2, sizeof(volpath2) / sizeof(WCHAR))) throw last_error(GetLastError()); if (wcscmp(volpath1, volpath2)) // different filesystems throw string_error(IDS_CANT_REFLINK_DIFFERENT_FS); source = CreateFileW(fn, GENERIC_READ | FILE_TRAVERSE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT, nullptr); if (source == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii, sizeof(btrfs_inode_info)); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); // if subvol, do snapshot instead if (bii.inode == SUBVOL_ROOT_INODE) { btrfs_create_snapshot* bcs; win_handle dirh; wstring destname, search; WIN32_FIND_DATAW wfd; int num = 2; dirh = CreateFileW(dir, FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (dirh == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); search = dirw; search += name; destname = name; fff_handle fff = FindFirstFileW(search.c_str(), &wfd); if (fff != INVALID_HANDLE_VALUE) { do { wstringstream ss; ss << name; ss << L" ("; ss << num; ss << L")"; destname = ss.str(); search = dirw + destname; fff = FindFirstFileW(search.c_str(), &wfd); num++; } while (fff != INVALID_HANDLE_VALUE); } bcs = (btrfs_create_snapshot*)malloc(sizeof(btrfs_create_snapshot) - sizeof(WCHAR) + (destname.length() * sizeof(WCHAR))); bcs->subvol = source; bcs->namelen = (uint16_t)(destname.length() * sizeof(WCHAR)); memcpy(bcs->name, destname.c_str(), destname.length() * sizeof(WCHAR)); Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, (ULONG)(sizeof(btrfs_create_snapshot) - sizeof(WCHAR) + bcs->namelen), nullptr, 0); free(bcs); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); return; } Status = NtQueryInformationFile(source, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (bii.type == BTRFS_TYPE_CHARDEV || bii.type == BTRFS_TYPE_BLOCKDEV || bii.type == BTRFS_TYPE_FIFO || bii.type == BTRFS_TYPE_SOCKET) { win_handle dirh; btrfs_mknod* bmn; dirh = CreateFileW(dir, FILE_ADD_FILE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (dirh == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); size_t bmnsize = offsetof(btrfs_mknod, name[0]) + (wcslen(name) * sizeof(WCHAR)); bmn = (btrfs_mknod*)malloc(bmnsize); bmn->inode = 0; bmn->type = bii.type; bmn->st_rdev = bii.st_rdev; bmn->namelen = (uint16_t)(wcslen(name) * sizeof(WCHAR)); memcpy(bmn->name, name, bmn->namelen); Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_MKNOD, bmn, (ULONG)bmnsize, nullptr, 0); if (!NT_SUCCESS(Status)) { free(bmn); throw ntstatus_error(Status); } free(bmn); dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, OPEN_EXISTING, 0, nullptr); } else if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (CreateDirectoryExW(fn, newpath.c_str(), nullptr)) dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); else dest = INVALID_HANDLE_VALUE; } else dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, source); if (dest == INVALID_HANDLE_VALUE) { int num = 2; if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_ALREADY_EXISTS && wcscmp(fn, newpath.c_str())) throw last_error(GetLastError()); do { WCHAR* ext; wstringstream ss; ext = PathFindExtensionW(fn); ss << dirw; if (*ext == 0) { ss << name; ss << L" ("; ss << num; ss << L")"; } else { wstring namew = name; ss << namew.substr(0, ext - name); ss << L" ("; ss << num; ss << L")"; ss << ext; } newpath = ss.str(); if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (CreateDirectoryExW(fn, newpath.c_str(), nullptr)) dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); else dest = INVALID_HANDLE_VALUE; } else dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, source); if (dest == INVALID_HANDLE_VALUE) { if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_ALREADY_EXISTS) throw last_error(GetLastError()); num++; } else break; } while (true); } try { memset(&bsii, 0, sizeof(btrfs_set_inode_info)); bsii.flags_changed = true; bsii.flags = bii.flags; if (bii.flags & BTRFS_INODE_COMPRESS) { bsii.compression_type_changed = true; bsii.compression_type = bii.compression_type; } Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (!(fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { fff_handle h; WIN32_FIND_DATAW fff; wstring qs; qs = fn; qs += L"\\*"; h = FindFirstFileW(qs.c_str(), &fff); if (h != INVALID_HANDLE_VALUE) { do { wstring fn2; if (fff.cFileName[0] == '.' && (fff.cFileName[1] == 0 || (fff.cFileName[1] == '.' && fff.cFileName[2] == 0))) continue; fn2 = fn; fn2 += L"\\"; fn2 += fff.cFileName; reflink_copy(hwnd, fn2.c_str(), newpath.c_str()); } while (FindNextFileW(h, &fff)); } } // CreateDirectoryExW also copies streams, no need to do it here } else { if (fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { reparse_header rh; uint8_t* rp; if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, &rh, sizeof(reparse_header), &bytesret, nullptr)) { if (GetLastError() != ERROR_MORE_DATA) throw last_error(GetLastError()); } size_t rplen = sizeof(reparse_header) + rh.ReparseDataLength; rp = (uint8_t*)malloc(rplen); if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, rp, (ULONG)rplen, &bytesret, nullptr)) throw last_error(GetLastError()); if (!DeviceIoControl(dest, FSCTL_SET_REPARSE_POINT, rp, (ULONG)rplen, nullptr, 0, &bytesret, nullptr)) throw last_error(GetLastError()); free(rp); } else { FILE_STANDARD_INFO fsi; FILE_END_OF_FILE_INFO feofi; FSCTL_GET_INTEGRITY_INFORMATION_BUFFER fgiib; FSCTL_SET_INTEGRITY_INFORMATION_BUFFER fsiib; DUPLICATE_EXTENTS_DATA ded; uint64_t offset, alloc_size; ULONG maxdup; Status = NtQueryInformationFile(source, &iosb, &fsi, sizeof(FILE_STANDARD_INFO), FileStandardInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (!DeviceIoControl(source, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &fgiib, sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER), &bytesret, nullptr)) throw last_error(GetLastError()); if (fbi.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) { if (!DeviceIoControl(dest, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesret, nullptr)) throw last_error(GetLastError()); } fsiib.ChecksumAlgorithm = fgiib.ChecksumAlgorithm; fsiib.Reserved = 0; fsiib.Flags = fgiib.Flags; if (!DeviceIoControl(dest, FSCTL_SET_INTEGRITY_INFORMATION, &fsiib, sizeof(FSCTL_SET_INTEGRITY_INFORMATION_BUFFER), nullptr, 0, &bytesret, nullptr)) throw last_error(GetLastError()); feofi.EndOfFile = fsi.EndOfFile; Status = NtSetInformationFile(dest, &iosb, &feofi, sizeof(FILE_END_OF_FILE_INFO), FileEndOfFileInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); ded.FileHandle = source; maxdup = 0xffffffff - fgiib.ClusterSizeInBytes + 1; alloc_size = sector_align(fsi.EndOfFile.QuadPart, fgiib.ClusterSizeInBytes); offset = 0; while (offset < alloc_size) { ded.SourceFileOffset.QuadPart = ded.TargetFileOffset.QuadPart = offset; ded.ByteCount.QuadPart = maxdup < (alloc_size - offset) ? maxdup : (alloc_size - offset); if (!DeviceIoControl(dest, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &ded, sizeof(DUPLICATE_EXTENTS_DATA), nullptr, 0, &bytesret, nullptr)) throw last_error(GetLastError()); offset += ded.ByteCount.QuadPart; } } ULONG streambufsize = 0; vector streambuf; do { streambufsize += 0x1000; streambuf.resize(streambufsize); memset(streambuf.data(), 0, streambufsize); Status = NtQueryInformationFile(source, &iosb, streambuf.data(), streambufsize, FileStreamInformation); } while (Status == STATUS_BUFFER_OVERFLOW); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); auto fsi = reinterpret_cast(streambuf.data()); while (true) { if (fsi->StreamNameLength > 0) { wstring sn = wstring(fsi->StreamName, fsi->StreamNameLength / sizeof(WCHAR)); if (sn != L"::$DATA" && sn.length() > 6 && sn.substr(sn.length() - 6, 6) == L":$DATA") { win_handle stream; uint8_t* data = nullptr; auto stream_size = (uint16_t)fsi->StreamSize.QuadPart; if (stream_size > 0) { wstring fn2; fn2 = fn; fn2 += sn; stream = CreateFileW(fn2.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr); if (stream == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); // We can get away with this because our streams are guaranteed to be below 64 KB - // don't do this on NTFS! data = (uint8_t*)malloc(stream_size); if (!ReadFile(stream, data, stream_size, &bytesret, nullptr)) { free(data); throw last_error(GetLastError()); } } stream = CreateFileW((newpath + sn).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, nullptr); if (stream == INVALID_HANDLE_VALUE) { if (data) free(data); throw last_error(GetLastError()); } if (data) { if (!WriteFile(stream, data, stream_size, &bytesret, nullptr)) { free(data); throw last_error(GetLastError()); } free(data); } } } if (fsi->NextEntryOffset == 0) break; fsi = reinterpret_cast(reinterpret_cast(fsi) + fsi->NextEntryOffset); } } atime.dwLowDateTime = fbi.LastAccessTime.LowPart; atime.dwHighDateTime = fbi.LastAccessTime.HighPart; mtime.dwLowDateTime = fbi.LastWriteTime.LowPart; mtime.dwHighDateTime = fbi.LastWriteTime.HighPart; SetFileTime(dest, nullptr, &atime, &mtime); Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, &bsxa, sizeof(btrfs_set_xattr)); if (Status == STATUS_BUFFER_OVERFLOW || (NT_SUCCESS(Status) && bsxa.valuelen > 0)) { ULONG xalen = 0; btrfs_set_xattr *xa = nullptr, *xa2; do { xalen += 1024; if (xa) free(xa); xa = (btrfs_set_xattr*)malloc(xalen); Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, xa, xalen); } while (Status == STATUS_BUFFER_OVERFLOW); if (!NT_SUCCESS(Status)) { free(xa); throw ntstatus_error(Status); } xa2 = xa; while (xa2->valuelen > 0) { Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, xa2, (ULONG)(offsetof(btrfs_set_xattr, data[0]) + xa2->namelen + xa2->valuelen), nullptr, 0); if (!NT_SUCCESS(Status)) { free(xa); throw ntstatus_error(Status); } xa2 = (btrfs_set_xattr*)&xa2->data[xa2->namelen + xa2->valuelen]; } free(xa); } else if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } catch (...) { FILE_DISPOSITION_INFO fdi; fdi.DeleteFile = true; Status = NtSetInformationFile(dest, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); throw; } } HRESULT __stdcall BtrfsContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO picia) { LPCMINVOKECOMMANDINFOEX pici = (LPCMINVOKECOMMANDINFOEX)picia; try { if (ignore) return E_INVALIDARG; if (!bg) { if ((IS_INTRESOURCE(pici->lpVerb) && allow_snapshot && pici->lpVerb == 0) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, SNAPSHOT_VERBA))) { UINT num_files, i; WCHAR fn[MAX_PATH]; if (!stgm_set) return E_FAIL; num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0); if (num_files == 0) return E_FAIL; for (i = 0; i < num_files; i++) { if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) { create_snapshot(pici->hwnd, fn); } } return S_OK; } else if ((IS_INTRESOURCE(pici->lpVerb) && ((allow_snapshot && (ULONG_PTR)pici->lpVerb == 1) || (!allow_snapshot && (ULONG_PTR)pici->lpVerb == 0))) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, SEND_VERBA))) { UINT num_files, i; WCHAR dll[MAX_PATH], fn[MAX_PATH]; wstring t; SHELLEXECUTEINFOW sei; GetModuleFileNameW(module, dll, sizeof(dll) / sizeof(WCHAR)); if (!stgm_set) return E_FAIL; num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0); if (num_files == 0) return E_FAIL; for (i = 0; i < num_files; i++) { if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) { t = L"\""; t += dll; t += L"\",SendSubvolGUI "; t += fn; RtlZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); sei.hwnd = pici->hwnd; sei.lpVerb = L"runas"; sei.lpFile = L"rundll32.exe"; sei.lpParameters = t.c_str(); sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExW(&sei)) throw last_error(GetLastError()); WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); } } return S_OK; } } else { if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 0) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, NEW_SUBVOL_VERBA))) { win_handle h; IO_STATUS_BLOCK iosb; NTSTATUS Status; wstring name, nameorig, searchpath; btrfs_create_subvol* bcs; WIN32_FIND_DATAW wfd; if (!load_string(module, IDS_NEW_SUBVOL_FILENAME, name)) throw last_error(GetLastError()); 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); if (h == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); searchpath = path + L"\\" + name; nameorig = name; { fff_handle fff = FindFirstFileW(searchpath.c_str(), &wfd); if (fff != INVALID_HANDLE_VALUE) { ULONG num = 2; do { name = nameorig + L" (" + to_wstring(num) + L")"; searchpath = path + L"\\" + name; fff = FindFirstFileW(searchpath.c_str(), &wfd); num++; } while (fff != INVALID_HANDLE_VALUE); } } size_t bcslen = offsetof(btrfs_create_subvol, name[0]) + (name.length() * sizeof(WCHAR)); bcs = (btrfs_create_subvol*)malloc(bcslen); bcs->readonly = false; bcs->posix = false; bcs->namelen = (uint16_t)(name.length() * sizeof(WCHAR)); memcpy(bcs->name, name.c_str(), name.length() * sizeof(WCHAR)); Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SUBVOL, bcs, (ULONG)bcslen, nullptr, 0); free(bcs); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); return S_OK; } else if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 1) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, RECV_VERBA))) { WCHAR dll[MAX_PATH]; wstring t; SHELLEXECUTEINFOW sei; GetModuleFileNameW(module, dll, sizeof(dll) / sizeof(WCHAR)); t = L"\""; t += dll; t += L"\",RecvSubvolGUI "; t += path; RtlZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); sei.hwnd = pici->hwnd; sei.lpVerb = L"runas"; sei.lpFile = L"rundll32.exe"; sei.lpParameters = t.c_str(); sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExW(&sei)) throw last_error(GetLastError()); WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); return S_OK; } else if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 2) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, REFLINK_VERBA))) { HDROP hdrop; if (!IsClipboardFormatAvailable(CF_HDROP)) return S_OK; if (!OpenClipboard(pici->hwnd)) throw last_error(GetLastError()); try { hdrop = (HDROP)GetClipboardData(CF_HDROP); if (hdrop) { global_lock gl(hdrop); if (gl.ptr) { ULONG num_files, i; WCHAR fn[MAX_PATH]; num_files = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0); for (i = 0; i < num_files; i++) { if (DragQueryFileW(hdrop, i, fn, sizeof(fn) / sizeof(WCHAR))) { reflink_copy(pici->hwnd, fn, pici->lpDirectoryW); } } } } } catch (...) { CloseClipboard(); throw; } CloseClipboard(); return S_OK; } } } catch (const exception& e) { error_message(pici->hwnd, e.what()); } return E_FAIL; } HRESULT __stdcall BtrfsContextMenu::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT*, LPSTR pszName, UINT cchMax) { if (ignore) return E_INVALIDARG; if (idCmd != 0) return E_INVALIDARG; if (!bg) { if (idCmd == 0) { switch (uFlags) { case GCS_HELPTEXTA: if (LoadStringA(module, IDS_CREATE_SNAPSHOT_HELP_TEXT, pszName, cchMax)) return S_OK; else return E_FAIL; case GCS_HELPTEXTW: if (LoadStringW(module, IDS_CREATE_SNAPSHOT_HELP_TEXT, (LPWSTR)pszName, cchMax)) return S_OK; else return E_FAIL; case GCS_VALIDATEA: case GCS_VALIDATEW: return S_OK; case GCS_VERBA: return StringCchCopyA(pszName, cchMax, SNAPSHOT_VERBA); case GCS_VERBW: return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, SNAPSHOT_VERBW); default: return E_INVALIDARG; } } else if (idCmd == 1) { switch (uFlags) { case GCS_HELPTEXTA: if (LoadStringA(module, IDS_SEND_SUBVOL_HELP, pszName, cchMax)) return S_OK; else return E_FAIL; case GCS_HELPTEXTW: if (LoadStringW(module, IDS_SEND_SUBVOL_HELP, (LPWSTR)pszName, cchMax)) return S_OK; else return E_FAIL; case GCS_VALIDATEA: case GCS_VALIDATEW: return S_OK; case GCS_VERBA: return StringCchCopyA(pszName, cchMax, SEND_VERBA); case GCS_VERBW: return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, SEND_VERBW); default: return E_INVALIDARG; } } else return E_INVALIDARG; } else { if (idCmd == 0) { switch (uFlags) { case GCS_HELPTEXTA: if (LoadStringA(module, IDS_NEW_SUBVOL_HELP_TEXT, pszName, cchMax)) return S_OK; else return E_FAIL; case GCS_HELPTEXTW: if (LoadStringW(module, IDS_NEW_SUBVOL_HELP_TEXT, (LPWSTR)pszName, cchMax)) return S_OK; else return E_FAIL; case GCS_VALIDATEA: case GCS_VALIDATEW: return S_OK; case GCS_VERBA: return StringCchCopyA(pszName, cchMax, NEW_SUBVOL_VERBA); case GCS_VERBW: return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, NEW_SUBVOL_VERBW); default: return E_INVALIDARG; } } else if (idCmd == 1) { switch (uFlags) { case GCS_HELPTEXTA: if (LoadStringA(module, IDS_RECV_SUBVOL_HELP, pszName, cchMax)) return S_OK; else return E_FAIL; case GCS_HELPTEXTW: if (LoadStringW(module, IDS_RECV_SUBVOL_HELP, (LPWSTR)pszName, cchMax)) return S_OK; else return E_FAIL; case GCS_VALIDATEA: case GCS_VALIDATEW: return S_OK; case GCS_VERBA: return StringCchCopyA(pszName, cchMax, RECV_VERBA); case GCS_VERBW: return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, RECV_VERBW); default: return E_INVALIDARG; } } else if (idCmd == 2) { switch (uFlags) { case GCS_HELPTEXTA: if (LoadStringA(module, IDS_REFLINK_PASTE_HELP, pszName, cchMax)) return S_OK; else return E_FAIL; case GCS_HELPTEXTW: if (LoadStringW(module, IDS_REFLINK_PASTE_HELP, (LPWSTR)pszName, cchMax)) return S_OK; else return E_FAIL; case GCS_VALIDATEA: case GCS_VALIDATEW: return S_OK; case GCS_VERBA: return StringCchCopyA(pszName, cchMax, REFLINK_VERBA); case GCS_VERBW: return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, REFLINK_VERBW); default: return E_INVALIDARG; } } else return E_INVALIDARG; } } static void reflink_copy2(const wstring& srcfn, const wstring& destdir, const wstring& destname) { win_handle source, dest; FILE_BASIC_INFO fbi; FILETIME atime, mtime; btrfs_inode_info bii; btrfs_set_inode_info bsii; ULONG bytesret; NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_set_xattr bsxa; source = CreateFileW(srcfn.c_str(), GENERIC_READ | FILE_TRAVERSE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT, nullptr); if (source == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii, sizeof(btrfs_inode_info)); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); // if subvol, do snapshot instead if (bii.inode == SUBVOL_ROOT_INODE) { btrfs_create_snapshot* bcs; win_handle dirh; 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); if (dirh == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); size_t bcslen = offsetof(btrfs_create_snapshot, name[0]) + (destname.length() * sizeof(WCHAR)); bcs = (btrfs_create_snapshot*)malloc(bcslen); bcs->subvol = source; bcs->namelen = (uint16_t)(destname.length() * sizeof(WCHAR)); memcpy(bcs->name, destname.c_str(), destname.length() * sizeof(WCHAR)); Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, (ULONG)bcslen, nullptr, 0); if (!NT_SUCCESS(Status)) { free(bcs); throw ntstatus_error(Status); } free(bcs); return; } Status = NtQueryInformationFile(source, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (bii.type == BTRFS_TYPE_CHARDEV || bii.type == BTRFS_TYPE_BLOCKDEV || bii.type == BTRFS_TYPE_FIFO || bii.type == BTRFS_TYPE_SOCKET) { win_handle dirh; btrfs_mknod* bmn; 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); if (dirh == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); size_t bmnsize = offsetof(btrfs_mknod, name[0]) + (destname.length() * sizeof(WCHAR)); bmn = (btrfs_mknod*)malloc(bmnsize); bmn->inode = 0; bmn->type = bii.type; bmn->st_rdev = bii.st_rdev; bmn->namelen = (uint16_t)(destname.length() * sizeof(WCHAR)); memcpy(bmn->name, destname.c_str(), bmn->namelen); Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_MKNOD, bmn, (ULONG)bmnsize, nullptr, 0); if (!NT_SUCCESS(Status)) { free(bmn); throw ntstatus_error(Status); } free(bmn); dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, OPEN_EXISTING, 0, nullptr); } else if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (CreateDirectoryExW(srcfn.c_str(), (destdir + destname).c_str(), nullptr)) dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); else dest = INVALID_HANDLE_VALUE; } else dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, source); if (dest == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); memset(&bsii, 0, sizeof(btrfs_set_inode_info)); bsii.flags_changed = true; bsii.flags = bii.flags; if (bii.flags & BTRFS_INODE_COMPRESS) { bsii.compression_type_changed = true; bsii.compression_type = bii.compression_type; } try { Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (!(fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { WIN32_FIND_DATAW fff; wstring qs; qs = srcfn; qs += L"\\*"; fff_handle h = FindFirstFileW(qs.c_str(), &fff); if (h != INVALID_HANDLE_VALUE) { do { wstring fn2; if (fff.cFileName[0] == '.' && (fff.cFileName[1] == 0 || (fff.cFileName[1] == '.' && fff.cFileName[2] == 0))) continue; fn2 = srcfn; fn2 += L"\\"; fn2 += fff.cFileName; reflink_copy2(fn2, destdir + destname + L"\\", fff.cFileName); } while (FindNextFileW(h, &fff)); } } // CreateDirectoryExW also copies streams, no need to do it here } else { if (fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { reparse_header rh; uint8_t* rp; if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, &rh, sizeof(reparse_header), &bytesret, nullptr)) { if (GetLastError() != ERROR_MORE_DATA) throw last_error(GetLastError()); } size_t rplen = sizeof(reparse_header) + rh.ReparseDataLength; rp = (uint8_t*)malloc(rplen); try { if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, rp, (DWORD)rplen, &bytesret, nullptr)) throw last_error(GetLastError()); if (!DeviceIoControl(dest, FSCTL_SET_REPARSE_POINT, rp, (DWORD)rplen, nullptr, 0, &bytesret, nullptr)) throw last_error(GetLastError()); } catch (...) { free(rp); throw; } free(rp); } else { FILE_STANDARD_INFO fsi; FILE_END_OF_FILE_INFO feofi; FSCTL_GET_INTEGRITY_INFORMATION_BUFFER fgiib; FSCTL_SET_INTEGRITY_INFORMATION_BUFFER fsiib; DUPLICATE_EXTENTS_DATA ded; uint64_t offset, alloc_size; ULONG maxdup; Status = NtQueryInformationFile(source, &iosb, &fsi, sizeof(FILE_STANDARD_INFO), FileStandardInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (!DeviceIoControl(source, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &fgiib, sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER), &bytesret, nullptr)) throw last_error(GetLastError()); if (fbi.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) { if (!DeviceIoControl(dest, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesret, nullptr)) throw last_error(GetLastError()); } fsiib.ChecksumAlgorithm = fgiib.ChecksumAlgorithm; fsiib.Reserved = 0; fsiib.Flags = fgiib.Flags; if (!DeviceIoControl(dest, FSCTL_SET_INTEGRITY_INFORMATION, &fsiib, sizeof(FSCTL_SET_INTEGRITY_INFORMATION_BUFFER), nullptr, 0, &bytesret, nullptr)) throw last_error(GetLastError()); feofi.EndOfFile = fsi.EndOfFile; Status = NtSetInformationFile(dest, &iosb, &feofi, sizeof(FILE_END_OF_FILE_INFO), FileEndOfFileInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); ded.FileHandle = source; maxdup = 0xffffffff - fgiib.ClusterSizeInBytes + 1; alloc_size = sector_align(fsi.EndOfFile.QuadPart, fgiib.ClusterSizeInBytes); offset = 0; while (offset < alloc_size) { ded.SourceFileOffset.QuadPart = ded.TargetFileOffset.QuadPart = offset; ded.ByteCount.QuadPart = maxdup < (alloc_size - offset) ? maxdup : (alloc_size - offset); if (!DeviceIoControl(dest, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &ded, sizeof(DUPLICATE_EXTENTS_DATA), nullptr, 0, &bytesret, nullptr)) throw last_error(GetLastError()); offset += ded.ByteCount.QuadPart; } } ULONG streambufsize = 0; vector streambuf; do { streambufsize += 0x1000; streambuf.resize(streambufsize); memset(streambuf.data(), 0, streambufsize); Status = NtQueryInformationFile(source, &iosb, streambuf.data(), streambufsize, FileStreamInformation); } while (Status == STATUS_BUFFER_OVERFLOW); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); auto fsi = reinterpret_cast(streambuf.data()); while (true) { if (fsi->StreamNameLength > 0) { wstring sn = wstring(fsi->StreamName, fsi->StreamNameLength / sizeof(WCHAR)); if (sn != L"::$DATA" && sn.length() > 6 && sn.substr(sn.length() - 6, 6) == L":$DATA") { win_handle stream; uint8_t* data = nullptr; auto stream_size = (uint16_t)fsi->StreamSize.QuadPart; if (stream_size > 0) { wstring fn2; fn2 = srcfn; fn2 += sn; stream = CreateFileW(fn2.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr); if (stream == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); // We can get away with this because our streams are guaranteed to be below 64 KB - // don't do this on NTFS! data = (uint8_t*)malloc(stream_size); if (!ReadFile(stream, data, stream_size, &bytesret, nullptr)) { free(data); throw last_error(GetLastError()); } } stream = CreateFileW((destdir + destname + sn).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, nullptr); if (stream == INVALID_HANDLE_VALUE) { if (data) free(data); throw last_error(GetLastError()); } if (data) { if (!WriteFile(stream, data, stream_size, &bytesret, nullptr)) { free(data); throw last_error(GetLastError()); } free(data); } } } if (fsi->NextEntryOffset == 0) break; fsi = reinterpret_cast(reinterpret_cast(fsi) + fsi->NextEntryOffset); } } atime.dwLowDateTime = fbi.LastAccessTime.LowPart; atime.dwHighDateTime = fbi.LastAccessTime.HighPart; mtime.dwLowDateTime = fbi.LastWriteTime.LowPart; mtime.dwHighDateTime = fbi.LastWriteTime.HighPart; SetFileTime(dest, nullptr, &atime, &mtime); Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, &bsxa, sizeof(btrfs_set_xattr)); if (Status == STATUS_BUFFER_OVERFLOW || (NT_SUCCESS(Status) && bsxa.valuelen > 0)) { ULONG xalen = 0; btrfs_set_xattr *xa = nullptr, *xa2; do { xalen += 1024; if (xa) free(xa); xa = (btrfs_set_xattr*)malloc(xalen); Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, xa, xalen); } while (Status == STATUS_BUFFER_OVERFLOW); if (!NT_SUCCESS(Status)) { free(xa); throw ntstatus_error(Status); } xa2 = xa; while (xa2->valuelen > 0) { Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, xa2, (ULONG)(offsetof(btrfs_set_xattr, data[0]) + xa2->namelen + xa2->valuelen), nullptr, 0); if (!NT_SUCCESS(Status)) { free(xa); throw ntstatus_error(Status); } xa2 = (btrfs_set_xattr*)&xa2->data[xa2->namelen + xa2->valuelen]; } free(xa); } else if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } catch (...) { FILE_DISPOSITION_INFO fdi; fdi.DeleteFile = true; Status = NtSetInformationFile(dest, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); throw; } } extern "C" void CALLBACK ReflinkCopyW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) { vector args; command_line_to_args(lpszCmdLine, args); if (args.size() >= 2) { bool dest_is_dir = false; wstring dest = args[args.size() - 1], destdir, destname; WCHAR volpath2[MAX_PATH]; { win_handle destdirh = CreateFileW(dest.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (destdirh != INVALID_HANDLE_VALUE) { BY_HANDLE_FILE_INFORMATION bhfi; if (GetFileInformationByHandle(destdirh, &bhfi) && bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { dest_is_dir = true; destdir = dest; if (destdir.substr(destdir.length() - 1, 1) != L"\\") destdir += L"\\"; } } } if (!dest_is_dir) { size_t found = dest.rfind(L"\\"); if (found == wstring::npos) { destdir = L""; destname = dest; } else { destdir = dest.substr(0, found); destname = dest.substr(found + 1); } } if (!GetVolumePathNameW(dest.c_str(), volpath2, sizeof(volpath2) / sizeof(WCHAR))) return; for (unsigned int i = 0; i < args.size() - 1; i++) { WIN32_FIND_DATAW ffd; fff_handle h = FindFirstFileW(args[i].c_str(), &ffd); if (h != INVALID_HANDLE_VALUE) { WCHAR volpath1[MAX_PATH]; wstring path = args[i]; size_t found = path.rfind(L"\\"); if (found == wstring::npos) path = L""; else path = path.substr(0, found); path += L"\\"; if (get_volume_path_parent(path.c_str(), volpath1, sizeof(volpath1) / sizeof(WCHAR))) { if (!wcscmp(volpath1, volpath2)) { do { try { reflink_copy2(path + ffd.cFileName, destdir, dest_is_dir ? ffd.cFileName : destname); } catch (const exception& e) { cerr << "Error: " << e.what() << endl; } } while (FindNextFileW(h, &ffd)); } } } } } } ================================================ FILE: src/shellext/contextmenu.h ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #pragma once #include extern LONG objs_loaded; class BtrfsContextMenu : public IShellExtInit, IContextMenu { public: BtrfsContextMenu() { refcount = 0; ignore = true; stgm_set = false; uacicon = nullptr; allow_snapshot = false; InterlockedIncrement(&objs_loaded); } virtual ~BtrfsContextMenu() { if (stgm_set) ReleaseStgMedium(&stgm); if (uacicon) DeleteObject(uacicon); InterlockedDecrement(&objs_loaded); } // IUnknown HRESULT __stdcall QueryInterface(REFIID riid, void **ppObj); ULONG __stdcall AddRef() { return InterlockedIncrement(&refcount); } ULONG __stdcall Release() { LONG rc = InterlockedDecrement(&refcount); if (rc == 0) delete this; return rc; } // IShellExtInit virtual HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) override; // IContextMenu virtual HRESULT __stdcall QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) override; virtual HRESULT __stdcall InvokeCommand(LPCMINVOKECOMMANDINFO pici) override; virtual HRESULT __stdcall GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT* pwReserved, LPSTR pszName, UINT cchMax) override; private: LONG refcount; bool ignore, allow_snapshot; bool bg; wstring path; STGMEDIUM stgm; bool stgm_set; HBITMAP uacicon; void reflink_copy(HWND hwnd, const WCHAR* fn, const WCHAR* dir); void get_uac_icon(); }; ================================================ FILE: src/shellext/devices.cpp ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #define ISOLATION_AWARE_ENABLED 1 #define STRSAFE_NO_DEPRECATE #include "shellext.h" #include "devices.h" #include "resource.h" #include "balance.h" #include #include #include #include #include #include #include "../btrfs.h" #include "mountmgr.h" DEFINE_GUID(GUID_DEVINTERFACE_HIDDEN_VOLUME, 0x7f108a28L, 0x9833, 0x4b3b, 0xb7, 0x80, 0x2c, 0x6b, 0x5f, 0xa5, 0xc0, 0x62); static wstring get_mountdev_name(const nt_handle& h ) { NTSTATUS Status; IO_STATUS_BLOCK iosb; MOUNTDEV_NAME mdn, *mdn2; wstring name; Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, nullptr, 0, &mdn, sizeof(MOUNTDEV_NAME)); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) return L""; size_t mdnsize = offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength; mdn2 = (MOUNTDEV_NAME*)malloc(mdnsize); Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, nullptr, 0, mdn2, (ULONG)mdnsize); if (!NT_SUCCESS(Status)) { free(mdn2); return L""; } name = wstring(mdn2->Name, mdn2->NameLength / sizeof(WCHAR)); free(mdn2); return name; } static void find_devices(HWND, const GUID* guid, const mountmgr& mm, vector& device_list) { HDEVINFO h; static const wstring dosdevices = L"\\DosDevices\\"; h = SetupDiGetClassDevsW(guid, nullptr, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); if (h != INVALID_HANDLE_VALUE) { DWORD index = 0; SP_DEVICE_INTERFACE_DATA did; did.cbSize = sizeof(did); if (!SetupDiEnumDeviceInterfaces(h, nullptr, guid, index, &did)) return; do { SP_DEVINFO_DATA dd; SP_DEVICE_INTERFACE_DETAIL_DATA_W* detail; DWORD size; dd.cbSize = sizeof(dd); SetupDiGetDeviceInterfaceDetailW(h, &did, nullptr, 0, &size, nullptr); detail = (SP_DEVICE_INTERFACE_DETAIL_DATA_W*)malloc(size); memset(detail, 0, size); detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W); if (SetupDiGetDeviceInterfaceDetailW(h, &did, detail, size, &size, &dd)) { NTSTATUS Status; nt_handle file; device dev; STORAGE_DEVICE_NUMBER sdn; IO_STATUS_BLOCK iosb; UNICODE_STRING path; OBJECT_ATTRIBUTES attr; GET_LENGTH_INFORMATION gli; ULONG i; uint8_t sb[4096]; path.Buffer = detail->DevicePath; path.Length = path.MaximumLength = (uint16_t)(wcslen(detail->DevicePath) * sizeof(WCHAR)); if (path.Length > 4 * sizeof(WCHAR) && path.Buffer[0] == '\\' && path.Buffer[1] == '\\' && path.Buffer[2] == '?' && path.Buffer[3] == '\\') path.Buffer[1] = '?'; InitializeObjectAttributes(&attr, &path, 0, nullptr, nullptr); Status = NtOpenFile(&file, FILE_GENERIC_READ, &attr, &iosb, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_ALERT); if (!NT_SUCCESS(Status)) { free(detail); index++; continue; } dev.pnp_name = detail->DevicePath; Status = NtDeviceIoControlFile(file, nullptr, nullptr, nullptr, &iosb, IOCTL_DISK_GET_LENGTH_INFO, nullptr, 0, &gli, sizeof(GET_LENGTH_INFORMATION)); if (!NT_SUCCESS(Status)) { free(detail); index++; continue; } dev.size = gli.Length.QuadPart; Status = NtDeviceIoControlFile(file, nullptr, nullptr, nullptr, &iosb, IOCTL_STORAGE_GET_DEVICE_NUMBER, nullptr, 0, &sdn, sizeof(STORAGE_DEVICE_NUMBER)); if (!NT_SUCCESS(Status)) { dev.disk_num = 0xffffffff; dev.part_num = 0xffffffff; } else { dev.disk_num = sdn.DeviceNumber; dev.part_num = sdn.PartitionNumber; } dev.friendly_name = L""; dev.drive = L""; dev.fstype = L""; dev.has_parts = false; dev.ignore = false; dev.multi_device = false; dev.is_disk = RtlCompareMemory(guid, &GUID_DEVINTERFACE_DISK, sizeof(GUID)) == sizeof(GUID); if (dev.is_disk) { STORAGE_PROPERTY_QUERY spq; STORAGE_DEVICE_DESCRIPTOR sdd, *sdd2; ULONG dlisize; DRIVE_LAYOUT_INFORMATION_EX* dli; spq.PropertyId = StorageDeviceProperty; spq.QueryType = PropertyStandardQuery; spq.AdditionalParameters[0] = 0; Status = NtDeviceIoControlFile(file, nullptr, nullptr, nullptr, &iosb, IOCTL_STORAGE_QUERY_PROPERTY, &spq, sizeof(STORAGE_PROPERTY_QUERY), &sdd, sizeof(STORAGE_DEVICE_DESCRIPTOR)); if (NT_SUCCESS(Status) || Status == STATUS_BUFFER_OVERFLOW) { sdd2 = (STORAGE_DEVICE_DESCRIPTOR*)malloc(sdd.Size); Status = NtDeviceIoControlFile(file, nullptr, nullptr, nullptr, &iosb, IOCTL_STORAGE_QUERY_PROPERTY, &spq, sizeof(STORAGE_PROPERTY_QUERY), sdd2, sdd.Size); if (NT_SUCCESS(Status)) { string desc2; desc2 = ""; if (sdd2->VendorIdOffset != 0) { desc2 += (char*)((uint8_t*)sdd2 + sdd2->VendorIdOffset); while (desc2.length() > 0 && desc2[desc2.length() - 1] == ' ') desc2 = desc2.substr(0, desc2.length() - 1); } if (sdd2->ProductIdOffset != 0) { if (sdd2->VendorIdOffset != 0 && desc2.length() != 0 && desc2[desc2.length() - 1] != ' ') desc2 += " "; desc2 += (char*)((uint8_t*)sdd2 + sdd2->ProductIdOffset); while (desc2.length() > 0 && desc2[desc2.length() - 1] == ' ') desc2 = desc2.substr(0, desc2.length() - 1); } if (sdd2->VendorIdOffset != 0 || sdd2->ProductIdOffset != 0) { ULONG ss; ss = MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, desc2.c_str(), -1, nullptr, 0); if (ss > 0) { WCHAR* desc3 = (WCHAR*)malloc(ss * sizeof(WCHAR)); if (MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, desc2.c_str(), -1, desc3, ss)) dev.friendly_name = desc3; free(desc3); } } } free(sdd2); } dlisize = 0; dli = nullptr; do { dlisize += 1024; if (dli) free(dli); dli = (DRIVE_LAYOUT_INFORMATION_EX*)malloc(dlisize); Status = NtDeviceIoControlFile(file, nullptr, nullptr, nullptr, &iosb, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, nullptr, 0, dli, dlisize); } while (Status == STATUS_BUFFER_TOO_SMALL); if (NT_SUCCESS(Status) && dli->PartitionCount > 0) dev.has_parts = true; free(dli); } else { try { auto v = mm.query_points(L"", L"", wstring_view(path.Buffer, path.Length / sizeof(WCHAR))); for (const auto& p : v) { if (p.symlink.length() == 14 && p.symlink.substr(0, dosdevices.length()) == dosdevices && p.symlink[13] == ':') { WCHAR dr[3]; dr[0] = p.symlink[12]; dr[1] = ':'; dr[2] = 0; dev.drive = dr; break; } } } catch (...) { // don't fail entirely if mountmgr refuses to co-operate } } if (!dev.is_disk || !dev.has_parts) { i = 0; while (fs_ident[i].name) { if (i == 0 || fs_ident[i].kboff != fs_ident[i-1].kboff) { LARGE_INTEGER off; off.QuadPart = fs_ident[i].kboff * 1024; Status = NtReadFile(file, nullptr, nullptr, nullptr, &iosb, sb, sizeof(sb), &off, nullptr); } if (NT_SUCCESS(Status)) { if (RtlCompareMemory(sb + fs_ident[i].sboff, fs_ident[i].magic, fs_ident[i].magiclen) == fs_ident[i].magiclen) { dev.fstype = fs_ident[i].name; if (dev.fstype == L"Btrfs") { superblock* bsb = (superblock*)sb; RtlCopyMemory(&dev.fs_uuid, &bsb->uuid, sizeof(BTRFS_UUID)); RtlCopyMemory(&dev.dev_uuid, &bsb->dev_item.device_uuid, sizeof(BTRFS_UUID)); } break; } } i++; } if (dev.fstype == L"Btrfs" && RtlCompareMemory(guid, &GUID_DEVINTERFACE_DISK, sizeof(GUID)) != sizeof(GUID)) { wstring name; wstring pref = L"\\Device\\Btrfs{"; name = get_mountdev_name(file); if (name.length() > pref.length() && RtlCompareMemory(name.c_str(), pref.c_str(), pref.length() * sizeof(WCHAR)) == pref.length() * sizeof(WCHAR)) dev.ignore = true; } } device_list.push_back(dev); } free(detail); index++; } while (SetupDiEnumDeviceInterfaces(h, nullptr, guid, index, &did)); SetupDiDestroyDeviceInfoList(h); } else throw last_error(GetLastError()); } static bool sort_devices(device i, device j) { if (i.disk_num < j.disk_num) return true; if (i.disk_num == j.disk_num && i.part_num < j.part_num) return true; return false; } void BtrfsDeviceAdd::populate_device_tree(HWND tree) { HWND hwnd = GetParent(tree); unsigned int i; ULONG last_disk_num = 0xffffffff; HTREEITEM diskitem; NTSTATUS Status; OBJECT_ATTRIBUTES attr; UNICODE_STRING us; IO_STATUS_BLOCK iosb; btrfs_filesystem* bfs = nullptr; static WCHAR btrfs[] = L"\\Btrfs"; device_list.clear(); { mountmgr mm; { nt_handle btrfsh; us.Length = us.MaximumLength = (uint16_t)(wcslen(btrfs) * sizeof(WCHAR)); us.Buffer = btrfs; InitializeObjectAttributes(&attr, &us, 0, nullptr, nullptr); Status = NtOpenFile(&btrfsh, SYNCHRONIZE | FILE_READ_ATTRIBUTES, &attr, &iosb, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_ALERT); if (NT_SUCCESS(Status)) { ULONG bfssize = 0; do { bfssize += 1024; if (bfs) free(bfs); bfs = (btrfs_filesystem*)malloc(bfssize); Status = NtDeviceIoControlFile(btrfsh, nullptr, nullptr, nullptr, &iosb, IOCTL_BTRFS_QUERY_FILESYSTEMS, nullptr, 0, bfs, bfssize); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) { free(bfs); bfs = nullptr; break; } } while (Status == STATUS_BUFFER_OVERFLOW); if (bfs && bfs->num_devices == 0) { // no mounted filesystems found free(bfs); bfs = nullptr; } } } find_devices(hwnd, &GUID_DEVINTERFACE_DISK, mm, device_list); find_devices(hwnd, &GUID_DEVINTERFACE_VOLUME, mm, device_list); find_devices(hwnd, &GUID_DEVINTERFACE_HIDDEN_VOLUME, mm, device_list); } sort(device_list.begin(), device_list.end(), sort_devices); for (i = 0; i < device_list.size(); i++) { if (!device_list[i].ignore) { TVINSERTSTRUCTW tis; HTREEITEM item; wstring name, size; if (device_list[i].disk_num != 0xffffffff && device_list[i].disk_num == last_disk_num) tis.hParent = diskitem; else tis.hParent = TVI_ROOT; tis.hInsertAfter = TVI_LAST; tis.itemex.mask = TVIF_TEXT | TVIF_STATE | TVIF_PARAM; tis.itemex.state = TVIS_EXPANDED; tis.itemex.stateMask = TVIS_EXPANDED; if (device_list[i].disk_num != 0xffffffff) { wstring t; if (!load_string(module, device_list[i].part_num != 0 ? IDS_PARTITION : IDS_DISK_NUM, t)) throw last_error(GetLastError()); wstring_sprintf(name, t, device_list[i].part_num != 0 ? device_list[i].part_num : device_list[i].disk_num); } else name = device_list[i].pnp_name; // match child Btrfs devices to their parent if (bfs && device_list[i].drive == L"" && device_list[i].fstype == L"Btrfs") { btrfs_filesystem* bfs2 = bfs; while (true) { if (RtlCompareMemory(&bfs2->uuid, &device_list[i].fs_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { ULONG j, k; btrfs_filesystem_device* dev; for (j = 0; j < bfs2->num_devices; j++) { if (j == 0) dev = &bfs2->device; else dev = (btrfs_filesystem_device*)((uint8_t*)dev + offsetof(btrfs_filesystem_device, name[0]) + dev->name_length); if (RtlCompareMemory(&device_list[i].dev_uuid, &device_list[i].dev_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { for (k = 0; k < device_list.size(); k++) { if (k != i && device_list[k].fstype == L"Btrfs" && device_list[k].drive != L"" && RtlCompareMemory(&device_list[k].fs_uuid, &device_list[i].fs_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { device_list[i].drive = device_list[k].drive; break; } } device_list[i].multi_device = bfs2->num_devices > 1; break; } } break; } if (bfs2->next_entry != 0) bfs2 = (btrfs_filesystem*)((uint8_t*)bfs2 + bfs2->next_entry); else break; } } name += L" ("; if (device_list[i].friendly_name != L"") { name += device_list[i].friendly_name; name += L", "; } if (device_list[i].drive != L"") { name += device_list[i].drive; name += L", "; } if (device_list[i].fstype != L"") { name += device_list[i].fstype; name += L", "; } format_size(device_list[i].size, size, false); name += size; name += L")"; tis.itemex.pszText = (WCHAR*)name.c_str(); tis.itemex.cchTextMax = (int)name.length(); tis.itemex.lParam = (LPARAM)&device_list[i]; item = (HTREEITEM)SendMessageW(tree, TVM_INSERTITEMW, 0, (LPARAM)&tis); if (!item) throw string_error(IDS_TVM_INSERTITEM_FAILED); if (device_list[i].part_num == 0) { diskitem = item; last_disk_num = device_list[i].disk_num; } } } } void BtrfsDeviceAdd::AddDevice(HWND hwndDlg) { wstring mess, title; NTSTATUS Status; UNICODE_STRING vn; OBJECT_ATTRIBUTES attr; IO_STATUS_BLOCK iosb; if (!sel) { EndDialog(hwndDlg, 0); return; } if (sel->fstype != L"") { wstring s; if (!load_string(module, IDS_ADD_DEVICE_CONFIRMATION_FS, s)) throw last_error(GetLastError()); wstring_sprintf(mess, s, sel->fstype.c_str()); } else { if (!load_string(module, IDS_ADD_DEVICE_CONFIRMATION, mess)) throw last_error(GetLastError()); } if (!load_string(module, IDS_CONFIRMATION_TITLE, title)) throw last_error(GetLastError()); if (MessageBoxW(hwndDlg, mess.c_str(), title.c_str(), MB_YESNO) != IDYES) return; win_handle h = CreateFileW(cmdline, FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); { nt_handle h2; vn.Length = vn.MaximumLength = (uint16_t)(sel->pnp_name.length() * sizeof(WCHAR)); vn.Buffer = (WCHAR*)sel->pnp_name.c_str(); InitializeObjectAttributes(&attr, &vn, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, nullptr, nullptr); Status = NtOpenFile(&h2, FILE_GENERIC_READ | FILE_GENERIC_WRITE, &attr, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_ALERT); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (!sel->is_disk) { Status = NtFsControlFile(h2, nullptr, nullptr, nullptr, &iosb, FSCTL_LOCK_VOLUME, nullptr, 0, nullptr, 0); if (!NT_SUCCESS(Status)) throw string_error(IDS_LOCK_FAILED, Status); } Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_ADD_DEVICE, &h2, sizeof(HANDLE), nullptr, 0); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (!sel->is_disk) { Status = NtFsControlFile(h2, nullptr, nullptr, nullptr, &iosb, FSCTL_DISMOUNT_VOLUME, nullptr, 0, nullptr, 0); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); Status = NtFsControlFile(h2, nullptr, nullptr, nullptr, &iosb, FSCTL_UNLOCK_VOLUME, nullptr, 0, nullptr, 0); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } } EndDialog(hwndDlg, 0); } INT_PTR CALLBACK BtrfsDeviceAdd::DeviceAddDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { try { switch (uMsg) { case WM_INITDIALOG: { EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); populate_device_tree(GetDlgItem(hwndDlg, IDC_DEVICE_TREE)); EnableWindow(GetDlgItem(hwndDlg, IDOK), false); break; } case WM_COMMAND: switch (HIWORD(wParam)) { case BN_CLICKED: switch (LOWORD(wParam)) { case IDOK: AddDevice(hwndDlg); return true; case IDCANCEL: EndDialog(hwndDlg, 0); return true; } break; } break; case WM_NOTIFY: switch (((LPNMHDR)lParam)->code) { case TVN_SELCHANGEDW: { NMTREEVIEWW* nmtv = (NMTREEVIEWW*)lParam; TVITEMW tvi; bool enable = false; RtlZeroMemory(&tvi, sizeof(TVITEMW)); tvi.hItem = nmtv->itemNew.hItem; tvi.mask = TVIF_PARAM | TVIF_HANDLE; if (SendMessageW(GetDlgItem(hwndDlg, IDC_DEVICE_TREE), TVM_GETITEMW, 0, (LPARAM)&tvi)) sel = tvi.lParam == 0 ? nullptr : (device*)tvi.lParam; else sel = nullptr; if (sel) enable = (!sel->is_disk || !sel->has_parts) && !sel->multi_device; EnableWindow(GetDlgItem(hwndDlg, IDOK), enable); break; } } break; } } catch (const exception& e) { error_message(hwndDlg, e.what()); } return false; } static INT_PTR CALLBACK stub_DeviceAddDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { BtrfsDeviceAdd* bda; if (uMsg == WM_INITDIALOG) { SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); bda = (BtrfsDeviceAdd*)lParam; } else { bda = (BtrfsDeviceAdd*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); } if (bda) return bda->DeviceAddDlgProc(hwndDlg, uMsg, wParam, lParam); else return false; } void BtrfsDeviceAdd::ShowDialog() { DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_DEVICE_ADD), hwnd, stub_DeviceAddDlgProc, (LPARAM)this); } BtrfsDeviceAdd::BtrfsDeviceAdd(HINSTANCE hinst, HWND hwnd, WCHAR* cmdline) { this->hinst = hinst; this->hwnd = hwnd; this->cmdline = cmdline; sel = nullptr; } void BtrfsDeviceResize::do_resize(HWND hwndDlg) { NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_resize br; { win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); br.device = dev_id; br.size = new_size; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RESIZE, &br, sizeof(btrfs_resize), nullptr, 0); if (Status != STATUS_MORE_PROCESSING_REQUIRED && !NT_SUCCESS(Status)) throw ntstatus_error(Status); } if (Status != STATUS_MORE_PROCESSING_REQUIRED) { wstring s, t, u; load_string(module, IDS_RESIZE_SUCCESSFUL, s); format_size(new_size, u, true); wstring_sprintf(t, s, dev_id, u.c_str()); MessageBoxW(hwndDlg, t.c_str(), L"", MB_OK); EndDialog(hwndDlg, 0); } else { HWND par; par = GetParent(hwndDlg); EndDialog(hwndDlg, 0); BtrfsBalance bb(fn, false, true); bb.ShowBalance(par); } } INT_PTR CALLBACK BtrfsDeviceResize::DeviceResizeDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) { try { switch (uMsg) { case WM_INITDIALOG: { win_handle h; WCHAR s[255]; wstring t, u; EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); GetDlgItemTextW(hwndDlg, IDC_RESIZE_DEVICE_ID, s, sizeof(s) / sizeof(WCHAR)); wstring_sprintf(t, s, dev_id); SetDlgItemTextW(hwndDlg, IDC_RESIZE_DEVICE_ID, t.c_str()); h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_device *devices, *bd; ULONG devsize; bool found = false; HWND slider; devsize = 1024; devices = (btrfs_device*)malloc(devsize); while (true) { Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_DEVICES, nullptr, 0, devices, devsize); if (Status == STATUS_BUFFER_OVERFLOW) { devsize += 1024; free(devices); devices = (btrfs_device*)malloc(devsize); } else break; } if (!NT_SUCCESS(Status)) { free(devices); return false; } bd = devices; while (true) { if (bd->dev_id == dev_id) { memcpy(&dev_info, bd, sizeof(btrfs_device)); found = true; break; } if (bd->next_entry > 0) bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry); else break; } if (!found) { free(devices); return false; } free(devices); GetDlgItemTextW(hwndDlg, IDC_RESIZE_CURSIZE, s, sizeof(s) / sizeof(WCHAR)); format_size(dev_info.size, u, true); wstring_sprintf(t, s, u.c_str()); SetDlgItemTextW(hwndDlg, IDC_RESIZE_CURSIZE, t.c_str()); new_size = dev_info.size; GetDlgItemTextW(hwndDlg, IDC_RESIZE_NEWSIZE, new_size_text, sizeof(new_size_text) / sizeof(WCHAR)); wstring_sprintf(t, new_size_text, u.c_str()); SetDlgItemTextW(hwndDlg, IDC_RESIZE_NEWSIZE, t.c_str()); slider = GetDlgItem(hwndDlg, IDC_RESIZE_SLIDER); SendMessageW(slider, TBM_SETRANGEMIN, false, 0); SendMessageW(slider, TBM_SETRANGEMAX, false, (LPARAM)(dev_info.max_size / 1048576)); SendMessageW(slider, TBM_SETPOS, true, (LPARAM)(new_size / 1048576)); } else return false; break; } case WM_COMMAND: switch (HIWORD(wParam)) { case BN_CLICKED: switch (LOWORD(wParam)) { case IDOK: do_resize(hwndDlg); return true; case IDCANCEL: EndDialog(hwndDlg, 0); return true; } break; } break; case WM_HSCROLL: { wstring t, u; new_size = UInt32x32To64(SendMessageW(GetDlgItem(hwndDlg, IDC_RESIZE_SLIDER), TBM_GETPOS, 0, 0), 1048576); format_size(new_size, u, true); wstring_sprintf(t, new_size_text, u.c_str()); SetDlgItemTextW(hwndDlg, IDC_RESIZE_NEWSIZE, t.c_str()); EnableWindow(GetDlgItem(hwndDlg, IDOK), new_size > 0 ? true : false); break; } } } catch (const exception& e) { error_message(hwndDlg, e.what()); } return false; } static INT_PTR CALLBACK stub_DeviceResizeDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { BtrfsDeviceResize* bdr; if (uMsg == WM_INITDIALOG) { SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); bdr = (BtrfsDeviceResize*)lParam; } else bdr = (BtrfsDeviceResize*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); if (bdr) return bdr->DeviceResizeDlgProc(hwndDlg, uMsg, wParam, lParam); else return false; } void BtrfsDeviceResize::ShowDialog(HWND hwnd, const wstring& fn, uint64_t dev_id) { this->dev_id = dev_id; this->fn = fn; DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_RESIZE), hwnd, stub_DeviceResizeDlgProc, (LPARAM)this); } extern "C" { void CALLBACK AddDeviceW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int) { try { win_handle token; TOKEN_PRIVILEGES tp; LUID luid; set_dpi_aware(); if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) throw last_error(GetLastError()); if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) throw last_error(GetLastError()); tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) throw last_error(GetLastError()); BtrfsDeviceAdd bda(hinst, hwnd, lpszCmdLine); bda.ShowDialog(); } catch (const exception& e) { error_message(hwnd, e.what()); } } void CALLBACK RemoveDeviceW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) { try { WCHAR *s, *vol, *dev; uint64_t devid; win_handle h, token; TOKEN_PRIVILEGES tp; LUID luid; NTSTATUS Status; IO_STATUS_BLOCK iosb; set_dpi_aware(); if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) throw last_error(GetLastError()); if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) throw last_error(GetLastError()); tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) throw last_error(GetLastError()); s = wcsstr(lpszCmdLine, L"|"); if (!s) return; s[0] = 0; vol = lpszCmdLine; dev = &s[1]; devid = _wtoi(dev); if (devid == 0) return; h = CreateFileW(vol, FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_REMOVE_DEVICE, &devid, sizeof(uint64_t), nullptr, 0); if (!NT_SUCCESS(Status)) { if (Status == STATUS_CANNOT_DELETE) throw string_error(IDS_CANNOT_REMOVE_RAID); else throw ntstatus_error(Status); return; } BtrfsBalance bb(vol, true); bb.ShowBalance(hwnd); } catch (const exception& e) { error_message(hwnd, e.what()); } } void CALLBACK ResizeDeviceW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) { try { WCHAR *s, *vol, *dev; uint64_t devid; win_handle token; TOKEN_PRIVILEGES tp; LUID luid; set_dpi_aware(); if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) throw last_error(GetLastError()); if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) throw last_error(GetLastError()); tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) throw last_error(GetLastError()); s = wcsstr(lpszCmdLine, L"|"); if (!s) return; s[0] = 0; vol = lpszCmdLine; dev = &s[1]; devid = _wtoi(dev); if (devid == 0) return; BtrfsDeviceResize bdr; bdr.ShowDialog(hwnd, vol, devid); } catch (const exception& e) { error_message(hwnd, e.what()); } } } ================================================ FILE: src/shellext/devices.h ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #pragma once #include #include #include #include "../btrfsioctl.h" typedef struct { wstring pnp_name; wstring friendly_name; wstring drive; wstring fstype; ULONG disk_num; ULONG part_num; uint64_t size; bool has_parts; BTRFS_UUID fs_uuid; BTRFS_UUID dev_uuid; bool ignore; bool multi_device; bool is_disk; } device; typedef struct { const WCHAR* name; const char* magic; ULONG magiclen; uint32_t sboff; uint32_t kboff; } fs_identifier; // This list is compiled from information in libblkid, part of util-linux // and likewise under the LGPL. Thanks! const static fs_identifier fs_ident[] = { { L"BeFS", "BFS1", 4, 0x20, 0 }, { L"BeFS", "1SFB", 4, 0x20, 0 }, { L"BeFS", "BFS1", 4, 0x220, 0 }, { L"BeFS", "1SFB", 4, 0x220, 0 }, { L"BFS", "\xce\xfa\xad\x1b", 4, 0, 0 }, { L"RomFS", "-rom1fs-", 8, 0, 0 }, { L"SquashFS", "hsqs", 4, 0, 0 }, { L"SquashFS", "sqsh", 4, 0, 0 }, { L"SquashFS", "hsqs", 4, 0, 0 }, { L"UBIFS", "\x31\x18\x10\x06", 4, 0, 0 }, { L"XFS", "XFSB", 4, 0, 0 }, { L"ext2", "\123\357", 2, 0x38, 1 }, { L"F2FS", "\x10\x20\xF5\xF2", 4, 0, 1 }, { L"HFS", "BD", 2, 0, 1 }, { L"HFS", "BD", 2, 0, 1 }, { L"HFS", "H+", 2, 0, 1 }, { L"HFS", "HX", 2, 0, 1 }, { L"Minix", "\177\023", 2, 0x10, 1 }, { L"Minix", "\217\023", 2, 0x10, 1 }, { L"Minix", "\023\177", 2, 0x10, 1 }, { L"Minix", "\023\217", 2, 0x10, 1 }, { L"Minix", "\150\044", 2, 0x10, 1 }, { L"Minix", "\170\044", 2, 0x10, 1 }, { L"Minix", "\044\150", 2, 0x10, 1 }, { L"Minix", "\044\170", 2, 0x10, 1 }, { L"Minix", "\132\115", 2, 0x10, 1 }, { L"Minix", "\115\132", 2, 0x10, 1 }, { L"OCFS", "OCFSV2", 6, 0, 1 }, { L"SysV", "\x2b\x55\x44", 3, 0x400, 1 }, { L"SysV", "\x44\x55\x2b", 3, 0x400, 1 }, { L"VXFS", "\365\374\001\245", 4, 0, 1 }, { L"OCFS", "OCFSV2", 6, 0, 2 }, { L"ExFAT", "EXFAT ", 8, 3, 0 }, { L"NTFS", "NTFS ", 8, 3, 0 }, { L"NetWare", "SPB5", 4, 0, 4 }, { L"OCFS", "OCFSV2", 6, 0, 4 }, { L"HPFS", "\x49\xe8\x95\xf9", 4, 0, 8 }, { L"OCFS", "OracleCFS", 9, 0, 8 }, { L"OCFS", "OCFSV2", 6, 0, 8 }, { L"ReFS", "\000\000\000ReFS\000", 8, 0, 0 }, { L"ReiserFS", "ReIsErFs", 8, 0x34, 8 }, { L"ReiserFS", "ReIsErFs", 8, 20, 8 }, { L"ISO9660", "CD001", 5, 1, 32 }, { L"ISO9660", "CDROM", 5, 9, 32 }, { L"JFS", "JFS1", 4, 0, 32 }, { L"OCFS", "ORCLDISK", 8, 32, 0 }, { L"UDF", "BEA01", 5, 1, 32 }, { L"UDF", "BOOT2", 5, 1, 32 }, { L"UDF", "CD001", 5, 1, 32 }, { L"UDF", "CDW02", 5, 1, 32 }, { L"UDF", "NSR02", 5, 1, 32 }, { L"UDF", "NSR03", 5, 1, 32 }, { L"UDF", "TEA01", 5, 1, 32 }, { L"Btrfs", "_BHRfS_M", 8, 0x40, 64 }, { L"GFS", "\x01\x16\x19\x70", 4, 0, 64 }, { L"GFS", "\x01\x16\x19\x70", 4, 0, 64 }, { L"ReiserFS", "ReIsEr2Fs", 9, 0x34, 64 }, { L"ReiserFS", "ReIsEr3Fs", 9, 0x34, 64 }, { L"ReiserFS", "ReIsErFs", 8, 0x34, 64 }, { L"ReiserFS", "ReIsEr4", 7, 0, 64 }, { L"VMFS", "\x0d\xd0\x01\xc0", 4, 0, 1024 }, { L"VMFS", "\x5e\xf1\xab\x2f", 4, 0, 2048 }, { L"FAT", "MSWIN", 5, 0x52, 0 }, { L"FAT", "FAT32 ", 8, 0x52, 0 }, { L"FAT", "MSDOS", 5, 0x36, 0 }, { L"FAT", "FAT16 ", 8, 0x36, 0 }, { L"FAT", "FAT12 ", 8, 0x36, 0 }, { L"FAT", "FAT ", 8, 0x36, 0 }, { L"FAT", "\353", 1, 0, 0 }, { L"FAT", "\351", 1, 0, 0 }, { L"FAT", "\125\252", 2, 0x1fe, 0 }, { nullptr, nullptr, 0, 0, 0 } }; class BtrfsDeviceAdd { public: INT_PTR CALLBACK DeviceAddDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); void ShowDialog(); void AddDevice(HWND hwndDlg); BtrfsDeviceAdd(HINSTANCE hinst, HWND hwnd, WCHAR* cmdline); private: void populate_device_tree(HWND tree); HINSTANCE hinst; HWND hwnd; WCHAR* cmdline; device* sel; vector device_list; }; class BtrfsDeviceResize { public: INT_PTR CALLBACK DeviceResizeDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); void ShowDialog(HWND hwnd, const wstring& fn, uint64_t dev_id); private: void do_resize(HWND hwndDlg); uint64_t dev_id, new_size; wstring fn; WCHAR new_size_text[255]; btrfs_device dev_info; }; ================================================ FILE: src/shellext/factory.cpp ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "shellext.h" #include #include "factory.h" #include "iconoverlay.h" #include "contextmenu.h" #include "propsheet.h" #include "volpropsheet.h" HRESULT __stdcall Factory::QueryInterface(const IID& iid, void** ppv) { if (iid == IID_IUnknown || iid == IID_IClassFactory) { *ppv = static_cast(this); } else { *ppv = nullptr; return E_NOINTERFACE; } reinterpret_cast(*ppv)->AddRef(); return S_OK; } HRESULT __stdcall Factory::LockServer(BOOL) { return E_NOTIMPL; } HRESULT __stdcall Factory::CreateInstance(IUnknown* pUnknownOuter, const IID& iid, void** ppv) { if (pUnknownOuter) return CLASS_E_NOAGGREGATION; switch (type) { case FactoryIconHandler: if (iid == IID_IUnknown || iid == IID_IShellIconOverlayIdentifier) { BtrfsIconOverlay* bio = new BtrfsIconOverlay; if (!bio) return E_OUTOFMEMORY; return bio->QueryInterface(iid, ppv); } break; case FactoryContextMenu: if (iid == IID_IUnknown || iid == IID_IContextMenu || iid == IID_IShellExtInit) { BtrfsContextMenu* bcm = new BtrfsContextMenu; if (!bcm) return E_OUTOFMEMORY; return bcm->QueryInterface(iid, ppv); } break; case FactoryPropSheet: if (iid == IID_IUnknown || iid == IID_IShellPropSheetExt || iid == IID_IShellExtInit) { BtrfsPropSheet* bps = new BtrfsPropSheet; if (!bps) return E_OUTOFMEMORY; return bps->QueryInterface(iid, ppv); } break; case FactoryVolPropSheet: if (iid == IID_IUnknown || iid == IID_IShellPropSheetExt || iid == IID_IShellExtInit) { BtrfsVolPropSheet* bps = new BtrfsVolPropSheet; if (!bps) return E_OUTOFMEMORY; return bps->QueryInterface(iid, ppv); } break; default: break; } *ppv = nullptr; return E_NOINTERFACE; } ================================================ FILE: src/shellext/factory.h ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #pragma once extern LONG objs_loaded; typedef enum { FactoryUnknown, FactoryIconHandler, FactoryContextMenu, FactoryPropSheet, FactoryVolPropSheet } factory_type; class Factory : public IClassFactory { public: Factory() { refcount = 0; type = FactoryUnknown; InterlockedIncrement(&objs_loaded); } virtual ~Factory() { InterlockedDecrement(&objs_loaded); } // IUnknown HRESULT __stdcall QueryInterface(REFIID riid, void **ppObj); ULONG __stdcall AddRef() { return InterlockedIncrement(&refcount); } ULONG __stdcall Release() { LONG rc = InterlockedDecrement(&refcount); if (rc == 0) delete this; return rc; } // IClassFactory virtual HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter, const IID& iid, void** ppv) override; virtual HRESULT __stdcall LockServer(BOOL bLock) override; factory_type type; private: LONG refcount; }; ================================================ FILE: src/shellext/iconoverlay.cpp ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "shellext.h" #include #include #include "iconoverlay.h" #include "../btrfsioctl.h" HRESULT __stdcall BtrfsIconOverlay::QueryInterface(REFIID riid, void **ppObj) { if (riid == IID_IUnknown || riid == IID_IShellIconOverlayIdentifier) { *ppObj = static_cast(this); AddRef(); return S_OK; } *ppObj = nullptr; return E_NOINTERFACE; } HRESULT __stdcall BtrfsIconOverlay::GetOverlayInfo(PWSTR pwszIconFile, int cchMax, int* pIndex, DWORD* pdwFlags) noexcept { if (GetModuleFileNameW(module, pwszIconFile, cchMax) == 0) return E_FAIL; if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) return E_FAIL; if (!pIndex) return E_INVALIDARG; if (!pdwFlags) return E_INVALIDARG; *pIndex = 0; *pdwFlags = ISIOI_ICONFILE | ISIOI_ICONINDEX; return S_OK; } HRESULT __stdcall BtrfsIconOverlay::GetPriority(int *pPriority) noexcept { if (!pPriority) return E_INVALIDARG; *pPriority = 0; return S_OK; } HRESULT __stdcall BtrfsIconOverlay::IsMemberOf(PCWSTR pwszPath, DWORD) noexcept { win_handle h; NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_get_file_ids bgfi; 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); if (h == INVALID_HANDLE_VALUE) return S_FALSE; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids)); if (!NT_SUCCESS(Status)) return S_FALSE; return (bgfi.inode == 0x100 && !bgfi.top) ? S_OK : S_FALSE; } ================================================ FILE: src/shellext/iconoverlay.h ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #pragma once #include extern LONG objs_loaded; class BtrfsIconOverlay : public IShellIconOverlayIdentifier { public: BtrfsIconOverlay() { refcount = 0; InterlockedIncrement(&objs_loaded); } virtual ~BtrfsIconOverlay() { InterlockedDecrement(&objs_loaded); } // IUnknown HRESULT __stdcall QueryInterface(REFIID riid, void **ppObj); ULONG __stdcall AddRef() { return InterlockedIncrement(&refcount); } ULONG __stdcall Release() { LONG rc = InterlockedDecrement(&refcount); if (rc == 0) delete this; return rc; } // IShellIconOverlayIdentifier virtual HRESULT __stdcall GetOverlayInfo(PWSTR pwszIconFile, int cchMax, int* pIndex, DWORD* pdwFlags) noexcept override; virtual HRESULT __stdcall GetPriority(int *pPriority) noexcept override; virtual HRESULT __stdcall IsMemberOf(PCWSTR pwszPath, DWORD dwAttrib) noexcept override; private: LONG refcount; }; ================================================ FILE: src/shellext/main.cpp ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "shellext.h" #include #include #include #include #include #include "factory.h" #include "resource.h" static const GUID CLSID_ShellBtrfsIconHandler = { 0x2690b74f, 0xf353, 0x422d, { 0xbb, 0x12, 0x40, 0x15, 0x81, 0xee, 0xf8, 0xf0 } }; static const GUID CLSID_ShellBtrfsContextMenu = { 0x2690b74f, 0xf353, 0x422d, { 0xbb, 0x12, 0x40, 0x15, 0x81, 0xee, 0xf8, 0xf1 } }; static const GUID CLSID_ShellBtrfsPropSheet = { 0x2690b74f, 0xf353, 0x422d, { 0xbb, 0x12, 0x40, 0x15, 0x81, 0xee, 0xf8, 0xf2 } }; static const GUID CLSID_ShellBtrfsVolPropSheet = { 0x2690b74f, 0xf353, 0x422d, { 0xbb, 0x12, 0x40, 0x15, 0x81, 0xee, 0xf8, 0xf3 } }; #define COM_DESCRIPTION_ICON_HANDLER L"WinBtrfs shell extension (icon handler)" #define COM_DESCRIPTION_CONTEXT_MENU L"WinBtrfs shell extension (context menu)" #define COM_DESCRIPTION_PROP_SHEET L"WinBtrfs shell extension (property sheet)" #define COM_DESCRIPTION_VOL_PROP_SHEET L"WinBtrfs shell extension (volume property sheet)" #define ICON_OVERLAY_NAME L"WinBtrfs" typedef enum _PROCESS_DPI_AWARENESS { PROCESS_DPI_UNAWARE, PROCESS_SYSTEM_DPI_AWARE, PROCESS_PER_MONITOR_DPI_AWARE } PROCESS_DPI_AWARENESS; typedef ULONG (WINAPI *_RtlNtStatusToDosError)(NTSTATUS Status); typedef HRESULT (WINAPI *_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS value); HMODULE module; LONG objs_loaded = 0; void set_dpi_aware() { _SetProcessDpiAwareness SetProcessDpiAwareness; HMODULE shcore = LoadLibraryW(L"shcore.dll"); if (!shcore) return; SetProcessDpiAwareness = (_SetProcessDpiAwareness)GetProcAddress(shcore, "SetProcessDpiAwareness"); if (!SetProcessDpiAwareness) return; SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); } void format_size(uint64_t size, wstring& s, bool show_bytes) { wstring t, bytes, kb, nb; WCHAR nb2[255]; ULONG sr; float f; NUMBERFMTW fmt; WCHAR dec[2], thou[4], grouping[64], *c; nb = to_wstring(size); GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, thou, sizeof(thou) / sizeof(WCHAR)); dec[0] = '.'; dec[1] = 0; // not used, but silences gcc warning fmt.NumDigits = 0; fmt.LeadingZero = 1; fmt.lpDecimalSep = dec; fmt.lpThousandSep = thou; fmt.NegativeOrder = 0; // Grouping code copied from dlls/shlwapi/string.c in Wine - thank you fmt.Grouping = 0; GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, grouping, sizeof(grouping) / sizeof(WCHAR)); c = grouping; while (*c) { if (*c >= '0' && *c < '9') { fmt.Grouping *= 10; fmt.Grouping += *c - '0'; } c++; } if (fmt.Grouping % 10 == 0) fmt.Grouping /= 10; else fmt.Grouping *= 10; GetNumberFormatW(LOCALE_USER_DEFAULT, 0, nb.c_str(), &fmt, nb2, sizeof(nb2) / sizeof(WCHAR)); if (size < 1024) { if (!load_string(module, size == 1 ? IDS_SIZE_BYTE : IDS_SIZE_BYTES, t)) throw last_error(GetLastError()); wstring_sprintf(s, t, nb2); return; } if (show_bytes) { if (!load_string(module, IDS_SIZE_BYTES, t)) throw last_error(GetLastError()); wstring_sprintf(bytes, t, nb2); } if (size >= 1152921504606846976) { sr = IDS_SIZE_EB; f = (float)size / 1152921504606846976.0f; } else if (size >= 1125899906842624) { sr = IDS_SIZE_PB; f = (float)size / 1125899906842624.0f; } else if (size >= 1099511627776) { sr = IDS_SIZE_TB; f = (float)size / 1099511627776.0f; } else if (size >= 1073741824) { sr = IDS_SIZE_GB; f = (float)size / 1073741824.0f; } else if (size >= 1048576) { sr = IDS_SIZE_MB; f = (float)size / 1048576.0f; } else { sr = IDS_SIZE_KB; f = (float)size / 1024.0f; } if (!load_string(module, sr, t)) throw last_error(GetLastError()); if (show_bytes) { wstring_sprintf(kb, t, f); if (!load_string(module, IDS_SIZE_LARGE, t)) throw last_error(GetLastError()); wstring_sprintf(s, t, kb.c_str(), bytes.c_str()); } else wstring_sprintf(s, t, f); } wstring format_message(ULONG last_error) { WCHAR* buf; wstring s; if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, last_error, 0, (WCHAR*)&buf, 0, nullptr) == 0) { return L"(error retrieving message)"; } s = buf; LocalFree(buf); // remove trailing newline while (s.length() > 0 && (s.substr(s.length() - 1, 1) == L"\r" || s.substr(s.length() - 1, 1) == L"\n")) s = s.substr(0, s.length() - 1); return s; } wstring format_ntstatus(NTSTATUS Status) { _RtlNtStatusToDosError RtlNtStatusToDosError; wstring s; HMODULE ntdll = LoadLibraryW(L"ntdll.dll"); if (!ntdll) return L"(error loading ntdll.dll)"; RtlNtStatusToDosError = (_RtlNtStatusToDosError)GetProcAddress(ntdll, "RtlNtStatusToDosError"); if (!RtlNtStatusToDosError) { FreeLibrary(ntdll); return L"(error loading RtlNtStatusToDosError)"; } s = format_message(RtlNtStatusToDosError(Status)); FreeLibrary(ntdll); return s; } bool load_string(HMODULE module, UINT id, wstring& s) { int len; LPWSTR retstr = nullptr; len = LoadStringW(module, id, (LPWSTR)&retstr, 0); if (len == 0) return false; s = wstring(retstr, len); return true; } #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4996) #endif void wstring_sprintf(wstring& s, wstring fmt, ...) { int len; va_list args; va_start(args, fmt); len = _vsnwprintf(nullptr, 0, fmt.c_str(), args); if (len == 0) s = L""; else { s.resize(len); _vsnwprintf((wchar_t*)s.c_str(), len, fmt.c_str(), args); } va_end(args); } #ifdef _MSC_VER #pragma warning(pop) #endif STDAPI DllCanUnloadNow(void) { return objs_loaded == 0 ? S_OK : S_FALSE; } STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { if (rclsid == CLSID_ShellBtrfsIconHandler) { Factory* fact = new Factory; if (!fact) return E_OUTOFMEMORY; else { fact->type = FactoryIconHandler; return fact->QueryInterface(riid, ppv); } } else if (rclsid == CLSID_ShellBtrfsContextMenu) { Factory* fact = new Factory; if (!fact) return E_OUTOFMEMORY; else { fact->type = FactoryContextMenu; return fact->QueryInterface(riid, ppv); } } else if (rclsid == CLSID_ShellBtrfsPropSheet) { Factory* fact = new Factory; if (!fact) return E_OUTOFMEMORY; else { fact->type = FactoryPropSheet; return fact->QueryInterface(riid, ppv); } } else if (rclsid == CLSID_ShellBtrfsVolPropSheet) { Factory* fact = new Factory; if (!fact) return E_OUTOFMEMORY; else { fact->type = FactoryVolPropSheet; return fact->QueryInterface(riid, ppv); } } return CLASS_E_CLASSNOTAVAILABLE; } static void write_reg_key(HKEY root, const wstring& keyname, const WCHAR* val, const wstring& data) { LONG l; HKEY hk; DWORD dispos; l = RegCreateKeyExW(root, keyname.c_str(), 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &hk, &dispos); if (l != ERROR_SUCCESS) throw string_error(IDS_REGCREATEKEY_FAILED, l); l = RegSetValueExW(hk, val, 0, REG_SZ, (const BYTE*)data.c_str(), (DWORD)((data.length() + 1) * sizeof(WCHAR))); if (l != ERROR_SUCCESS) throw string_error(IDS_REGSETVALUEEX_FAILED, l); l = RegCloseKey(hk); if (l != ERROR_SUCCESS) throw string_error(IDS_REGCLOSEKEY_FAILED, l); } static void register_clsid(const GUID clsid, const WCHAR* description) { WCHAR* clsidstring; wstring inproc, clsidkeyname; WCHAR dllpath[MAX_PATH]; StringFromCLSID(clsid, &clsidstring); try { inproc = L"CLSID\\"s + clsidstring + L"\\InprocServer32"s; clsidkeyname = L"CLSID\\"s + clsidstring; write_reg_key(HKEY_CLASSES_ROOT, clsidkeyname, nullptr, description); GetModuleFileNameW(module, dllpath, sizeof(dllpath) / sizeof(WCHAR)); write_reg_key(HKEY_CLASSES_ROOT, inproc, nullptr, dllpath); write_reg_key(HKEY_CLASSES_ROOT, inproc, L"ThreadingModel", L"Apartment"); } catch (...) { CoTaskMemFree(clsidstring); throw; } CoTaskMemFree(clsidstring); } // implementation of RegDeleteTreeW, only available from Vista on static void reg_delete_tree(HKEY hkey, const wstring& keyname) { HKEY k; LSTATUS ret; ret = RegOpenKeyExW(hkey, keyname.c_str(), 0, KEY_READ, &k); if (ret != ERROR_SUCCESS) throw last_error(ret); try { WCHAR* buf; ULONG bufsize; ret = RegQueryInfoKeyW(k, nullptr, nullptr, nullptr, nullptr, &bufsize, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); if (ret != ERROR_SUCCESS) throw last_error(ret); bufsize++; buf = new WCHAR[bufsize]; try { do { ULONG size = bufsize; ret = RegEnumKeyExW(k, 0, buf, &size, nullptr, nullptr, nullptr, nullptr); if (ret == ERROR_NO_MORE_ITEMS) break; else if (ret != ERROR_SUCCESS) throw last_error(ret); reg_delete_tree(k, buf); } while (true); ret = RegDeleteKeyW(hkey, keyname.c_str()); if (ret != ERROR_SUCCESS) throw last_error(ret); } catch (...) { delete[] buf; throw; } delete[] buf; } catch (...) { RegCloseKey(k); throw; } RegCloseKey(k); } static void unregister_clsid(const GUID clsid) { WCHAR* clsidstring; StringFromCLSID(clsid, &clsidstring); try { reg_delete_tree(HKEY_CLASSES_ROOT, L"CLSID\\"s + clsidstring); } catch (...) { CoTaskMemFree(clsidstring); throw; } CoTaskMemFree(clsidstring); } static void reg_icon_overlay(const GUID clsid, const wstring& name) { WCHAR* clsidstring; StringFromCLSID(clsid, &clsidstring); try { wstring path = L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellIconOverlayIdentifiers\\"s + name; write_reg_key(HKEY_LOCAL_MACHINE, path, nullptr, clsidstring); } catch (...) { CoTaskMemFree(clsidstring); throw; } CoTaskMemFree(clsidstring); } static void unreg_icon_overlay(const wstring& name) { reg_delete_tree(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellIconOverlayIdentifiers\\"s + name); } static void reg_context_menu_handler(const GUID clsid, const wstring& filetype, const wstring& name) { WCHAR* clsidstring; StringFromCLSID(clsid, &clsidstring); try { wstring path = filetype + L"\\ShellEx\\ContextMenuHandlers\\"s + name; write_reg_key(HKEY_CLASSES_ROOT, path, nullptr, clsidstring); } catch (...) { CoTaskMemFree(clsidstring); throw; } } static void unreg_context_menu_handler(const wstring& filetype, const wstring& name) { reg_delete_tree(HKEY_CLASSES_ROOT, filetype + L"\\ShellEx\\ContextMenuHandlers\\"s + name); } static void reg_prop_sheet_handler(const GUID clsid, const wstring& filetype, const wstring& name) { WCHAR* clsidstring; StringFromCLSID(clsid, &clsidstring); try { wstring path = filetype + L"\\ShellEx\\PropertySheetHandlers\\"s + name; write_reg_key(HKEY_CLASSES_ROOT, path, nullptr, clsidstring); } catch (...) { CoTaskMemFree(clsidstring); throw; } } static void unreg_prop_sheet_handler(const wstring& filetype, const wstring& name) { reg_delete_tree(HKEY_CLASSES_ROOT, filetype + L"\\ShellEx\\PropertySheetHandlers\\"s + name); } STDAPI DllRegisterServer(void) { try { register_clsid(CLSID_ShellBtrfsIconHandler, COM_DESCRIPTION_ICON_HANDLER); register_clsid(CLSID_ShellBtrfsContextMenu, COM_DESCRIPTION_CONTEXT_MENU); register_clsid(CLSID_ShellBtrfsPropSheet, COM_DESCRIPTION_PROP_SHEET); register_clsid(CLSID_ShellBtrfsVolPropSheet, COM_DESCRIPTION_VOL_PROP_SHEET); reg_icon_overlay(CLSID_ShellBtrfsIconHandler, ICON_OVERLAY_NAME); reg_context_menu_handler(CLSID_ShellBtrfsContextMenu, L"Directory\\Background", ICON_OVERLAY_NAME); reg_context_menu_handler(CLSID_ShellBtrfsContextMenu, L"Folder", ICON_OVERLAY_NAME); reg_prop_sheet_handler(CLSID_ShellBtrfsPropSheet, L"Folder", ICON_OVERLAY_NAME); reg_prop_sheet_handler(CLSID_ShellBtrfsPropSheet, L"*", ICON_OVERLAY_NAME); reg_prop_sheet_handler(CLSID_ShellBtrfsVolPropSheet, L"Drive", ICON_OVERLAY_NAME); } catch (const exception& e) { error_message(nullptr, e.what()); return E_FAIL; } return S_OK; } STDAPI DllUnregisterServer(void) { try { unreg_prop_sheet_handler(L"Folder", ICON_OVERLAY_NAME); unreg_prop_sheet_handler(L"*", ICON_OVERLAY_NAME); unreg_prop_sheet_handler(L"Drive", ICON_OVERLAY_NAME); unreg_context_menu_handler(L"Folder", ICON_OVERLAY_NAME); unreg_context_menu_handler(L"Directory\\Background", ICON_OVERLAY_NAME); unreg_icon_overlay(ICON_OVERLAY_NAME); unregister_clsid(CLSID_ShellBtrfsVolPropSheet); unregister_clsid(CLSID_ShellBtrfsPropSheet); unregister_clsid(CLSID_ShellBtrfsContextMenu); unregister_clsid(CLSID_ShellBtrfsIconHandler); } catch (const exception& e) { error_message(nullptr, e.what()); return E_FAIL; } return S_OK; } STDAPI DllInstall(BOOL bInstall, LPCWSTR) { if (bInstall) return DllRegisterServer(); else return DllUnregisterServer(); } extern "C" BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void*) { if (dwReason == DLL_PROCESS_ATTACH) module = (HMODULE)hModule; return true; } static void create_subvol(const wstring& fn) { size_t found = fn.rfind(L"\\"); wstring path, file; win_handle h; btrfs_create_subvol* bcs; IO_STATUS_BLOCK iosb; if (found == wstring::npos) { path = L""; file = fn; } else { path = fn.substr(0, found); file = fn.substr(found + 1); } path += L"\\"; 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); if (h == INVALID_HANDLE_VALUE) return; size_t bcslen = offsetof(btrfs_create_subvol, name[0]) + (file.length() * sizeof(WCHAR)); bcs = (btrfs_create_subvol*)malloc(bcslen); bcs->readonly = false; bcs->posix = false; bcs->namelen = (uint16_t)(file.length() * sizeof(WCHAR)); memcpy(bcs->name, file.c_str(), bcs->namelen); NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SUBVOL, bcs, (ULONG)bcslen, nullptr, 0); } extern "C" void CALLBACK CreateSubvolW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) { vector args; command_line_to_args(lpszCmdLine, args); if (args.size() >= 1) create_subvol(args[0]); } static void create_snapshot2(const wstring& source, const wstring& fn) { size_t found = fn.rfind(L"\\"); wstring path, file; win_handle h, src; btrfs_create_snapshot* bcs; IO_STATUS_BLOCK iosb; if (found == wstring::npos) { path = L""; file = fn; } else { path = fn.substr(0, found); file = fn.substr(found + 1); } path += L"\\"; src = CreateFileW(source.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (src == INVALID_HANDLE_VALUE) return; 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); if (h == INVALID_HANDLE_VALUE) return; size_t bcslen = offsetof(btrfs_create_snapshot, name[0]) + (file.length() * sizeof(WCHAR)); bcs = (btrfs_create_snapshot*)malloc(bcslen); bcs->readonly = false; bcs->posix = false; bcs->namelen = (uint16_t)(file.length() * sizeof(WCHAR)); memcpy(bcs->name, file.c_str(), bcs->namelen); bcs->subvol = src; NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, (ULONG)bcslen, nullptr, 0); } extern "C" void CALLBACK CreateSnapshotW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) { vector args; command_line_to_args(lpszCmdLine, args); if (args.size() >= 2) create_snapshot2(args[0], args[1]); } void command_line_to_args(LPWSTR cmdline, vector& args) { LPWSTR* l; int num_args; args.clear(); l = CommandLineToArgvW(cmdline, &num_args); if (!l) return; try { args.reserve(num_args); for (unsigned int i = 0; i < (unsigned int)num_args; i++) { args.push_back(l[i]); } } catch (...) { LocalFree(l); throw; } LocalFree(l); } static string utf16_to_utf8(wstring_view utf16) { string utf8; char* buf; if (utf16.empty()) return ""; auto utf8len = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.length()), nullptr, 0, nullptr, nullptr); if (utf8len == 0) throw last_error(GetLastError()); buf = (char*)malloc(utf8len + sizeof(char)); if (!buf) throw string_error(IDS_OUT_OF_MEMORY); if (WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.length()), buf, utf8len, nullptr, nullptr) == 0) { auto le = GetLastError(); free(buf); throw last_error(le); } buf[utf8len] = 0; utf8 = buf; free(buf); return utf8; } #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4996) #endif string_error::string_error(int resno, ...) { wstring fmt, s; int len; va_list args; if (!load_string(module, resno, fmt)) throw runtime_error("LoadString failed."); // FIXME va_start(args, resno); len = _vsnwprintf(nullptr, 0, fmt.c_str(), args); if (len == 0) s = L""; else { s.resize(len); _vsnwprintf((wchar_t*)s.c_str(), len, fmt.c_str(), args); } va_end(args); msg = utf16_to_utf8(s); } #ifdef _MSC_VER #pragma warning(pop) #endif wstring utf8_to_utf16(string_view utf8) { wstring ret; WCHAR* buf; if (utf8.empty()) return L""; auto utf16len = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), (int)utf8.length(), nullptr, 0); if (utf16len == 0) throw last_error(GetLastError()); buf = (WCHAR*)malloc((utf16len + 1) * sizeof(WCHAR)); if (!buf) throw string_error(IDS_OUT_OF_MEMORY); if (MultiByteToWideChar(CP_UTF8, 0, utf8.data(), (int)utf8.length(), buf, utf16len) == 0) { auto le = GetLastError(); free(buf); throw last_error(le); } buf[utf16len] = 0; ret = buf; free(buf); return ret; } last_error::last_error(DWORD errnum) { WCHAR* buf; if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, errnum, 0, (WCHAR*)&buf, 0, nullptr) == 0) throw runtime_error("FormatMessage failed"); try { msg = utf16_to_utf8(buf); } catch (...) { LocalFree(buf); throw; } LocalFree(buf); } void error_message(HWND hwnd, const char* msg) { wstring title; load_string(module, IDS_ERROR, title); auto wmsg = utf8_to_utf16(msg); MessageBoxW(hwnd, wmsg.c_str(), title.c_str(), MB_ICONERROR); } ntstatus_error::ntstatus_error(NTSTATUS Status) : Status(Status) { _RtlNtStatusToDosError RtlNtStatusToDosError; HMODULE ntdll = LoadLibraryW(L"ntdll.dll"); WCHAR* buf; if (!ntdll) throw runtime_error("Error loading ntdll.dll."); try { RtlNtStatusToDosError = (_RtlNtStatusToDosError)GetProcAddress(ntdll, "RtlNtStatusToDosError"); if (!RtlNtStatusToDosError) throw runtime_error("Error loading RtlNtStatusToDosError in ntdll.dll."); if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, RtlNtStatusToDosError(Status), 0, (WCHAR*)&buf, 0, nullptr) == 0) throw runtime_error("FormatMessage failed"); try { msg = utf16_to_utf8(buf); } catch (...) { LocalFree(buf); throw; } LocalFree(buf); } catch (...) { FreeLibrary(ntdll); throw; } FreeLibrary(ntdll); } ================================================ FILE: src/shellext/mappings.cpp ================================================ #include "shellext.h" #include "resource.h" #include #include #include #include #define _NTDEF_ #include #include #include #include #include #include using namespace std; static const WCHAR UID_REG_PATH[] = L"SYSTEM\\CurrentControlSet\\Services\\btrfs\\Mappings"; static const WCHAR GID_REG_PATH[] = L"SYSTEM\\CurrentControlSet\\Services\\btrfs\\GroupMappings"; class formatted_error : public exception { public: template formatted_error(string_view s, Args&&... args) : msg(vformat(s, make_format_args(args...))) { } const char* what() const noexcept { return msg.c_str(); } private: string msg; }; class lsa_handle_closer { public: using pointer = LSA_HANDLE; void operator()(LSA_HANDLE h) { LsaClose(h); } }; using unique_lsa_handle = unique_ptr; template class lsa_pointer_freer { public: using pointer = T; void operator()(T ptr) { LsaFreeMemory(ptr); } }; using unique_lsa_translated_name = unique_ptr>; using unique_lsa_referenced_domain_list = unique_ptr>; class hkey_closer { public: using pointer = HKEY; void operator()(HKEY key) { RegCloseKey(key); } }; using unique_hkey = unique_ptr; template class local_freer { public: using pointer = T; void operator()(T ptr) { LocalFree(ptr); } }; using unique_sid = unique_ptr>; struct mapping_entry { unique_sid sid; DWORD value; u16string domain; u16string name; SID_NAME_USE use; }; static unique_lsa_handle lsa_open_policy(ACCESS_MASK access) { LSA_OBJECT_ATTRIBUTES oa; NTSTATUS Status; LSA_HANDLE h; memset(&oa, 0, sizeof(oa)); Status = LsaOpenPolicy(nullptr, &oa, access, &h); if (Status != STATUS_SUCCESS) throw formatted_error("LsaOpenPolicy returned {:08x}", (uint32_t)Status); return unique_lsa_handle{h}; } static void lsa_lookup_sids(LSA_HANDLE h, span sids, unique_lsa_translated_name& names, unique_lsa_referenced_domain_list& domains) { NTSTATUS Status; LSA_REFERENCED_DOMAIN_LIST* domains_ptr; LSA_TRANSLATED_NAME* names_ptr; Status = LsaLookupSids(h, sids.size(), (PSID*)sids.data(), &domains_ptr, &names_ptr); if (Status != STATUS_SUCCESS && Status != STATUS_NONE_MAPPED && Status != STATUS_SOME_NOT_MAPPED) throw formatted_error("LsaLookupSids returned {:08x}", (uint32_t)Status); names.reset(names_ptr); domains.reset(domains_ptr); } static void resolve_names(span entries) { if (entries.empty()) return; vector sids; auto h = lsa_open_policy(POLICY_LOOKUP_NAMES); sids.reserve(entries.size()); for (const auto& ent : entries) { sids.push_back(ent.sid.get()); } unique_lsa_translated_name names; unique_lsa_referenced_domain_list domains; lsa_lookup_sids(h.get(), sids, names, domains); for (unsigned int i = 0; i < entries.size(); i++) { auto& ent = entries[i]; const auto& n = names.get()[i]; ent.use = n.Use; ent.name = u16string_view((char16_t*)n.Name.Buffer, n.Name.Length / sizeof(char16_t)); if (n.DomainIndex >= 0 && (ULONG)n.DomainIndex < domains->Entries) { const auto& d = domains->Domains[n.DomainIndex]; ent.domain = u16string_view((char16_t*)d.Name.Buffer, d.Name.Length / sizeof(char16_t)); } } } static void populate_list(HWND hwnd) { LSTATUS ret; unique_hkey k; array name; DWORD name_len, type; vector entries; wstring uidgid_str; LVCOLUMNW lvc; auto tab = GetDlgItem(hwnd, IDC_MAPPINGS_TAB); auto tabsel = SendMessageW(tab, TCM_GETCURSEL, 0, 0); auto groups = tabsel == 1; auto list = GetDlgItem(hwnd, IDC_MAPPINGS_LIST); // change column to UID or GID load_string(module, groups ? IDS_MAPPINGS_GID : IDS_MAPPINGS_UID, uidgid_str); lvc.iSubItem = 1; lvc.pszText = (WCHAR*)uidgid_str.c_str(); lvc.mask = LVCF_TEXT; SendMessageW(list, LVM_SETCOLUMNW, 1, (LPARAM)&lvc); ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, groups ? GID_REG_PATH : UID_REG_PATH, 0, KEY_QUERY_VALUE, out_ptr(k)); if (ret != ERROR_SUCCESS) throw formatted_error("RegOpenKeyEx failed (error {})", ret); for (DWORD index = 0; ; index++) { unique_sid sid; mapping_entry me; DWORD val, val_len; name_len = name.size(); val_len = sizeof(val); ret = RegEnumValueW(k.get(), index, name.data(), &name_len, nullptr, &type, (LPBYTE)&val, &val_len); if (ret == ERROR_NO_MORE_ITEMS) break; if ((ret == ERROR_SUCCESS || ret == ERROR_MORE_DATA) && type != REG_DWORD) continue; if (ret != ERROR_SUCCESS) throw formatted_error("RegEnumValue failed (error {})", ret); if (!ConvertStringSidToSidW(name.data(), out_ptr(sid))) continue; me.sid = std::move(sid); me.value = val; entries.emplace_back(std::move(me)); } resolve_names(entries); SendMessageW(list, LVM_DELETEALLITEMS, 0, 0); SendMessageW(list, LVM_SETITEMCOUNT, entries.size(), 0); for (size_t i = 0; i < entries.size(); i++) { const auto& ent = entries[i]; LVITEMW lvi; // FIXME - different icons for SID types u16string s; if (!ent.domain.empty()) s = ent.domain + u"\\"; s += ent.name; lvi.pszText = (WCHAR*)s.c_str(); lvi.mask = LVIF_TEXT; lvi.iItem = i; lvi.iSubItem = 0; SendMessageW(list, LVM_INSERTITEMW, 0, (LPARAM)&lvi); auto s2 = to_wstring(ent.value); lvi.pszText = (WCHAR*)s2.c_str(); lvi.mask = LVIF_TEXT; lvi.iSubItem = 1; SendMessageW(list, LVM_SETITEMTEXTW, i, (LPARAM)&lvi); } } static void init_dialog(HWND hwnd) { TCITEMW tie; LVCOLUMNW lvc; wstring uid_mappings_str, gid_mappings_str, principal_str, uid_str; load_string(module, IDS_MAPPINGS_UID_MAPPINGS, uid_mappings_str); load_string(module, IDS_MAPPINGS_GID_MAPPINGS, gid_mappings_str); load_string(module, IDS_MAPPINGS_PRINCIPAL, principal_str); load_string(module, IDS_MAPPINGS_UID, uid_str); auto tab = GetDlgItem(hwnd, IDC_MAPPINGS_TAB); memset(&tie, 0, sizeof(tie)); tie.mask = TCIF_TEXT; tie.iImage = -1; tie.pszText = (WCHAR*)uid_mappings_str.c_str(); SendMessageW(tab, TCM_INSERTITEMW, 0, (LPARAM)&tie); tie.pszText = (WCHAR*)gid_mappings_str.c_str(); SendMessageW(tab, TCM_INSERTITEMW, 1, (LPARAM)&tie); auto list = GetDlgItem(hwnd, IDC_MAPPINGS_LIST); lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; lvc.iSubItem = 0; lvc.pszText = (WCHAR*)principal_str.c_str(); lvc.cx = 300; lvc.fmt = LVCFMT_LEFT; SendMessageW(list, LVM_INSERTCOLUMNW, 0, (LPARAM)&lvc); lvc.iSubItem = 1; lvc.pszText = (WCHAR*)uid_str.c_str(); lvc.cx = 100; lvc.fmt = LVCFMT_LEFT; SendMessageW(list, LVM_INSERTCOLUMNW, 1, (LPARAM)&lvc); try { populate_list(GetParent(list)); } catch (const exception& e) { error_message(GetParent(list), e.what()); } } static INT_PTR CALLBACK MappingsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { try { switch (uMsg) { case WM_INITDIALOG: init_dialog(hwndDlg); return true; case WM_COMMAND: switch (HIWORD(wParam)) { case BN_CLICKED: switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: EndDialog(hwndDlg, 1); return true; } break; } break; case WM_NOTIFY: switch (((LPNMHDR)lParam)->code) { case TCN_SELCHANGE: try { populate_list(hwndDlg); } catch (const exception& e) { error_message(hwndDlg, e.what()); } break; } break; } } catch (const exception& e) { error_message(hwndDlg, e.what()); } return false; } extern "C" void CALLBACK MappingsTest(HWND hwnd, HINSTANCE, LPWSTR, int) { try { set_dpi_aware(); if (DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_MAPPINGS), hwnd, MappingsDlgProc, 0) <= 0) throw last_error(GetLastError()); } catch (const exception& e) { error_message(hwnd, e.what()); } } ================================================ FILE: src/shellext/mountmgr.cpp ================================================ #include "shellext.h" #include "mountmgr.h" #include using namespace std; mountmgr::mountmgr() { UNICODE_STRING us; OBJECT_ATTRIBUTES attr; IO_STATUS_BLOCK iosb; NTSTATUS Status; RtlInitUnicodeString(&us, MOUNTMGR_DEVICE_NAME); InitializeObjectAttributes(&attr, &us, 0, nullptr, nullptr); Status = NtOpenFile(&h, FILE_GENERIC_READ | FILE_GENERIC_WRITE, &attr, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_ALERT); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } mountmgr::~mountmgr() { NtClose(h); } void mountmgr::create_point(wstring_view symlink, wstring_view device) const { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf(sizeof(MOUNTMGR_CREATE_POINT_INPUT) + ((symlink.length() + device.length()) * sizeof(WCHAR))); auto mcpi = reinterpret_cast(buf.data()); mcpi->SymbolicLinkNameOffset = sizeof(MOUNTMGR_CREATE_POINT_INPUT); mcpi->SymbolicLinkNameLength = (USHORT)(symlink.length() * sizeof(WCHAR)); mcpi->DeviceNameOffset = (USHORT)(mcpi->SymbolicLinkNameOffset + mcpi->SymbolicLinkNameLength); mcpi->DeviceNameLength = (USHORT)(device.length() * sizeof(WCHAR)); memcpy((uint8_t*)mcpi + mcpi->SymbolicLinkNameOffset, symlink.data(), symlink.length() * sizeof(WCHAR)); memcpy((uint8_t*)mcpi + mcpi->DeviceNameOffset, device.data(), device.length() * sizeof(WCHAR)); Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTMGR_CREATE_POINT, buf.data(), (ULONG)buf.size(), nullptr, 0); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } void mountmgr::delete_points(wstring_view symlink, wstring_view unique_id, wstring_view device_name) const { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf(sizeof(MOUNTMGR_MOUNT_POINT) + ((symlink.length() + unique_id.length() + device_name.length()) * sizeof(WCHAR))); auto mmp = reinterpret_cast(buf.data()); memset(mmp, 0, sizeof(MOUNTMGR_MOUNT_POINT)); if (symlink.length() > 0) { mmp->SymbolicLinkNameOffset = sizeof(MOUNTMGR_MOUNT_POINT); mmp->SymbolicLinkNameLength = (USHORT)(symlink.length() * sizeof(WCHAR)); memcpy((uint8_t*)mmp + mmp->SymbolicLinkNameOffset, symlink.data(), symlink.length() * sizeof(WCHAR)); } if (unique_id.length() > 0) { if (mmp->SymbolicLinkNameLength == 0) mmp->UniqueIdOffset = sizeof(MOUNTMGR_MOUNT_POINT); else mmp->UniqueIdOffset = mmp->SymbolicLinkNameOffset + mmp->SymbolicLinkNameLength; mmp->UniqueIdLength = (USHORT)(unique_id.length() * sizeof(WCHAR)); memcpy((uint8_t*)mmp + mmp->UniqueIdOffset, unique_id.data(), unique_id.length() * sizeof(WCHAR)); } if (device_name.length() > 0) { if (mmp->SymbolicLinkNameLength == 0 && mmp->UniqueIdOffset == 0) mmp->DeviceNameOffset = sizeof(MOUNTMGR_MOUNT_POINT); else if (mmp->SymbolicLinkNameLength != 0) mmp->DeviceNameOffset = mmp->SymbolicLinkNameOffset + mmp->SymbolicLinkNameLength; else mmp->DeviceNameOffset = mmp->UniqueIdOffset + mmp->UniqueIdLength; mmp->DeviceNameLength = (USHORT)(device_name.length() * sizeof(WCHAR)); memcpy((uint8_t*)mmp + mmp->DeviceNameOffset, device_name.data(), device_name.length() * sizeof(WCHAR)); } vector buf2(sizeof(MOUNTMGR_MOUNT_POINTS)); auto mmps = reinterpret_cast(buf2.data()); Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTMGR_DELETE_POINTS, buf.data(), (ULONG)buf.size(), buf2.data(), (ULONG)buf2.size()); if (Status == STATUS_BUFFER_OVERFLOW) { buf2.resize(mmps->Size); Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTMGR_DELETE_POINTS, buf.data(), (ULONG)buf.size(), buf2.data(), (ULONG)buf2.size()); } if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } vector mountmgr::query_points(wstring_view symlink, wstring_view unique_id, wstring_view device_name) const { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector v; vector buf(sizeof(MOUNTMGR_MOUNT_POINT) + ((symlink.length() + unique_id.length() + device_name.length()) * sizeof(WCHAR))); auto mmp = reinterpret_cast(buf.data()); memset(mmp, 0, sizeof(MOUNTMGR_MOUNT_POINT)); if (symlink.length() > 0) { mmp->SymbolicLinkNameOffset = sizeof(MOUNTMGR_MOUNT_POINT); mmp->SymbolicLinkNameLength = (USHORT)(symlink.length() * sizeof(WCHAR)); memcpy((uint8_t*)mmp + mmp->SymbolicLinkNameOffset, symlink.data(), symlink.length() * sizeof(WCHAR)); } if (unique_id.length() > 0) { if (mmp->SymbolicLinkNameLength == 0) mmp->UniqueIdOffset = sizeof(MOUNTMGR_MOUNT_POINT); else mmp->UniqueIdOffset = mmp->SymbolicLinkNameOffset + mmp->SymbolicLinkNameLength; mmp->UniqueIdLength = (USHORT)(unique_id.length() * sizeof(WCHAR)); memcpy((uint8_t*)mmp + mmp->UniqueIdOffset, unique_id.data(), unique_id.length() * sizeof(WCHAR)); } if (device_name.length() > 0) { if (mmp->SymbolicLinkNameLength == 0 && mmp->UniqueIdOffset == 0) mmp->DeviceNameOffset = sizeof(MOUNTMGR_MOUNT_POINT); else if (mmp->SymbolicLinkNameLength != 0) mmp->DeviceNameOffset = mmp->SymbolicLinkNameOffset + mmp->SymbolicLinkNameLength; else mmp->DeviceNameOffset = mmp->UniqueIdOffset + mmp->UniqueIdLength; mmp->DeviceNameLength = (USHORT)(device_name.length() * sizeof(WCHAR)); memcpy((uint8_t*)mmp + mmp->DeviceNameOffset, device_name.data(), device_name.length() * sizeof(WCHAR)); } vector buf2(sizeof(MOUNTMGR_MOUNT_POINTS)); auto mmps = reinterpret_cast(buf2.data()); Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTMGR_QUERY_POINTS, buf.data(), (ULONG)buf.size(), buf2.data(), (ULONG)buf2.size()); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) throw ntstatus_error(Status); buf2.resize(mmps->Size); mmps = reinterpret_cast(buf2.data()); Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTMGR_QUERY_POINTS, buf.data(), (ULONG)buf.size(), buf2.data(), (ULONG)buf2.size()); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); for (ULONG i = 0; i < mmps->NumberOfMountPoints; i++) { wstring_view mpsl, mpdn; string_view mpuid; if (mmps->MountPoints[i].SymbolicLinkNameLength) mpsl = wstring_view((WCHAR*)((uint8_t*)mmps + mmps->MountPoints[i].SymbolicLinkNameOffset), mmps->MountPoints[i].SymbolicLinkNameLength / sizeof(WCHAR)); if (mmps->MountPoints[i].UniqueIdLength) mpuid = string_view((char*)((uint8_t*)mmps + mmps->MountPoints[i].UniqueIdOffset), mmps->MountPoints[i].UniqueIdLength); if (mmps->MountPoints[i].DeviceNameLength) mpdn = wstring_view((WCHAR*)((uint8_t*)mmps + mmps->MountPoints[i].DeviceNameOffset), mmps->MountPoints[i].DeviceNameLength / sizeof(WCHAR)); v.emplace_back(mpsl, mpuid, mpdn); } return v; } ================================================ FILE: src/shellext/mountmgr.h ================================================ #pragma once #include #include #include #include #include #include class mountmgr_point { public: 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) { } std::wstring symlink, device_name; std::string unique_id; }; class mountmgr { public: mountmgr(); ~mountmgr(); void create_point(std::wstring_view symlink, std::wstring_view device) const; void delete_points(std::wstring_view symlink, std::wstring_view unique_id = L"", std::wstring_view device_name = L"") const; std::vector query_points(std::wstring_view symlink = L"", std::wstring_view unique_id = L"", std::wstring_view device_name = L"") const; private: HANDLE h; }; ================================================ FILE: src/shellext/propsheet.cpp ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #define ISOLATION_AWARE_ENABLED 1 #define STRSAFE_NO_DEPRECATE #include "shellext.h" #include #include #include #define NO_SHLWAPI_STRFCNS #include #include #include "propsheet.h" #include "resource.h" #define SUBVOL_ROOT_INODE 0x100 #ifndef __MINGW32__ // in winternl.h in mingw typedef struct _FILE_ACCESS_INFORMATION { ACCESS_MASK AccessFlags; } FILE_ACCESS_INFORMATION, *PFILE_ACCESS_INFORMATION; #define FileAccessInformation (FILE_INFORMATION_CLASS)8 typedef struct _FILE_STANDARD_INFORMATION { LARGE_INTEGER AllocationSize; LARGE_INTEGER EndOfFile; ULONG NumberOfLinks; BOOLEAN DeletePending; BOOLEAN Directory; } FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION; #define FileStandardInformation (FILE_INFORMATION_CLASS)5 typedef struct _FILE_FS_SIZE_INFORMATION { LARGE_INTEGER TotalAllocationUnits; LARGE_INTEGER AvailableAllocationUnits; ULONG SectorsPerAllocationUnit; ULONG BytesPerSector; } FILE_FS_SIZE_INFORMATION, *PFILE_FS_SIZE_INFORMATION; #endif HRESULT __stdcall BtrfsPropSheet::QueryInterface(REFIID riid, void **ppObj) { if (riid == IID_IUnknown || riid == IID_IShellPropSheetExt) { *ppObj = static_cast(this); AddRef(); return S_OK; } else if (riid == IID_IShellExtInit) { *ppObj = static_cast(this); AddRef(); return S_OK; } *ppObj = nullptr; return E_NOINTERFACE; } void BtrfsPropSheet::do_search(const wstring& fn) { wstring ss; WIN32_FIND_DATAW ffd; ss = fn + L"\\*"s; fff_handle h = FindFirstFileW(ss.c_str(), &ffd); if (h == INVALID_HANDLE_VALUE) return; do { if (ffd.cFileName[0] != '.' || ((ffd.cFileName[1] != 0) && (ffd.cFileName[1] != '.' || ffd.cFileName[2] != 0))) { wstring fn2; fn2 = fn + L"\\"s + ffd.cFileName; if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) search_list.push_back(fn2); else { win_handle fh = CreateFileW(fn2.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (fh != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_inode_info bii2; memset(&bii2, 0, sizeof(bii2)); Status = NtFsControlFile(fh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii2, sizeof(btrfs_inode_info)); if (NT_SUCCESS(Status)) { sizes[0] += bii2.inline_length; sizes[1] += bii2.disk_size_uncompressed; sizes[2] += bii2.disk_size_zlib; sizes[3] += bii2.disk_size_lzo; sizes[4] += bii2.disk_size_zstd; totalsize += bii2.inline_length + bii2.disk_size_uncompressed + bii2.disk_size_zlib + bii2.disk_size_lzo + bii2.disk_size_zstd; sparsesize += bii2.sparse_size; num_extents += bii2.num_extents == 0 ? 0 : (bii2.num_extents - 1); } FILE_STANDARD_INFORMATION fsi; Status = NtQueryInformationFile(fh, &iosb, &fsi, sizeof(fsi), FileStandardInformation); if (NT_SUCCESS(Status)) { if (bii2.inline_length > 0) allocsize += fsi.EndOfFile.QuadPart; else allocsize += fsi.AllocationSize.QuadPart; } } } } } while (FindNextFileW(h, &ffd)); } DWORD BtrfsPropSheet::search_list_thread() { while (!search_list.empty()) { do_search(search_list.front()); search_list.pop_front(); } thread = nullptr; return 0; } static DWORD WINAPI global_search_list_thread(LPVOID lpParameter) { BtrfsPropSheet* bps = (BtrfsPropSheet*)lpParameter; return bps->search_list_thread(); } HRESULT BtrfsPropSheet::check_file(const wstring& fn, UINT i, UINT num_files, UINT* sv) { win_handle h; IO_STATUS_BLOCK iosb; NTSTATUS Status; FILE_ACCESS_INFORMATION fai; BY_HANDLE_FILE_INFORMATION bhfi; btrfs_inode_info bii2; h = CreateFileW(fn.c_str(), MAXIMUM_ALLOWED, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h == INVALID_HANDLE_VALUE) return E_FAIL; Status = NtQueryInformationFile(h, &iosb, &fai, sizeof(FILE_ACCESS_INFORMATION), FileAccessInformation); if (!NT_SUCCESS(Status)) return E_FAIL; if (fai.AccessFlags & FILE_READ_ATTRIBUTES) can_change_perms = fai.AccessFlags & WRITE_DAC; readonly = !(fai.AccessFlags & FILE_WRITE_ATTRIBUTES); if (!readonly && num_files == 1 && !can_change_perms) show_admin_button = true; if (GetFileInformationByHandle(h, &bhfi) && bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) search_list.push_back(fn); memset(&bii2, 0, sizeof(bii2)); Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii2, sizeof(btrfs_inode_info)); if (NT_SUCCESS(Status) && !bii2.top) { LARGE_INTEGER filesize; if (i == 0) { subvol = bii2.subvol; inode = bii2.inode; type = bii2.type; uid = bii2.st_uid; gid = bii2.st_gid; rdev = bii2.st_rdev; } else { if (subvol != bii2.subvol) various_subvols = true; if (inode != bii2.inode) various_inodes = true; if (type != bii2.type) various_types = true; if (uid != bii2.st_uid) various_uids = true; if (gid != bii2.st_gid) various_gids = true; } if (bii2.inline_length > 0) { totalsize += bii2.inline_length; sizes[0] += bii2.inline_length; } if (bii2.disk_size_uncompressed > 0) { totalsize += bii2.disk_size_uncompressed; sizes[1] += bii2.disk_size_uncompressed; } if (bii2.disk_size_zlib > 0) { totalsize += bii2.disk_size_zlib; sizes[2] += bii2.disk_size_zlib; } if (bii2.disk_size_lzo > 0) { totalsize += bii2.disk_size_lzo; sizes[3] += bii2.disk_size_lzo; } if (bii2.disk_size_zstd > 0) { totalsize += bii2.disk_size_zstd; sizes[4] += bii2.disk_size_zstd; } sparsesize += bii2.sparse_size; num_extents += bii2.num_extents == 0 ? 0 : (bii2.num_extents - 1); FILE_STANDARD_INFORMATION fsi; Status = NtQueryInformationFile(h, &iosb, &fsi, sizeof(fsi), FileStandardInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (bii2.inline_length > 0) allocsize += fsi.EndOfFile.QuadPart; else allocsize += fsi.AllocationSize.QuadPart; min_mode |= ~bii2.st_mode; max_mode |= bii2.st_mode; min_flags |= ~bii2.flags; max_flags |= bii2.flags; min_compression_type = bii2.compression_type < min_compression_type ? bii2.compression_type : min_compression_type; max_compression_type = bii2.compression_type > max_compression_type ? bii2.compression_type : max_compression_type; if (bii2.inode == SUBVOL_ROOT_INODE) { bool ro = bhfi.dwFileAttributes & FILE_ATTRIBUTE_READONLY; has_subvols = true; if (*sv == 0) ro_subvol = ro; else { if (ro_subvol != ro) various_ro = true; } (*sv)++; } ignore = false; if (bii2.type != BTRFS_TYPE_DIRECTORY && GetFileSizeEx(h, &filesize)) { if (filesize.QuadPart != 0) can_change_nocow = false; } { FILE_FS_SIZE_INFORMATION ffsi; Status = NtQueryVolumeInformationFile(h, &iosb, &ffsi, sizeof(ffsi), FileFsSizeInformation); if (NT_SUCCESS(Status)) sector_size = ffsi.BytesPerSector; if (sector_size == 0) sector_size = 4096; } } else return E_FAIL; return S_OK; } HRESULT BtrfsPropSheet::load_file_list() { UINT num_files, i, sv = 0; WCHAR fn[MAX_PATH]; num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0); min_mode = 0; max_mode = 0; min_flags = 0; max_flags = 0; min_compression_type = 0xff; max_compression_type = 0; various_subvols = various_inodes = various_types = various_uids = various_gids = various_ro = false; can_change_perms = true; can_change_nocow = true; sizes[0] = sizes[1] = sizes[2] = sizes[3] = sizes[4] = 0; totalsize = allocsize = sparsesize = 0; for (i = 0; i < num_files; i++) { if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) { HRESULT hr; hr = check_file(fn, i, num_files, &sv); if (FAILED(hr)) return hr; } else return E_FAIL; } min_mode = ~min_mode; min_flags = ~min_flags; mode = min_mode; mode_set = ~(min_mode ^ max_mode); flags = min_flags; flags_set = ~(min_flags ^ max_flags); return S_OK; } HRESULT __stdcall BtrfsPropSheet::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY) { try { FORMATETC format = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; HRESULT hr; if (pidlFolder) return E_FAIL; if (!pdtobj) return E_FAIL; stgm.tymed = TYMED_HGLOBAL; if (FAILED(pdtobj->GetData(&format, &stgm))) return E_INVALIDARG; stgm_set = true; global_lock gl(stgm.hGlobal); if (!gl.ptr) { ReleaseStgMedium(&stgm); stgm_set = false; return E_INVALIDARG; } hr = load_file_list(); if (FAILED(hr)) return hr; if (search_list.size() > 0) { thread = CreateThread(nullptr, 0, global_search_list_thread, this, 0, nullptr); if (!thread) throw last_error(GetLastError()); } } catch (const exception& e) { error_message(nullptr, e.what()); return E_FAIL; } return S_OK; } void BtrfsPropSheet::set_cmdline(const wstring& cmdline) { win_handle h; IO_STATUS_BLOCK iosb; NTSTATUS Status; UINT sv = 0; BY_HANDLE_FILE_INFORMATION bhfi; btrfs_inode_info bii2; FILE_ACCESS_INFORMATION fai; min_mode = 0; max_mode = 0; min_flags = 0; max_flags = 0; min_compression_type = 0xff; max_compression_type = 0; various_subvols = various_inodes = various_types = various_uids = various_gids = various_ro = false; can_change_perms = true; can_change_nocow = true; h = CreateFileW(cmdline.c_str(), MAXIMUM_ALLOWED, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); Status = NtQueryInformationFile(h, &iosb, &fai, sizeof(FILE_ACCESS_INFORMATION), FileAccessInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (fai.AccessFlags & FILE_READ_ATTRIBUTES) can_change_perms = fai.AccessFlags & WRITE_DAC; readonly = !(fai.AccessFlags & FILE_WRITE_ATTRIBUTES); if (GetFileInformationByHandle(h, &bhfi) && bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) search_list.push_back(cmdline); memset(&bii2, 0, sizeof(bii2)); Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii2, sizeof(btrfs_inode_info)); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (!bii2.top) { LARGE_INTEGER filesize; subvol = bii2.subvol; inode = bii2.inode; type = bii2.type; uid = bii2.st_uid; gid = bii2.st_gid; rdev = bii2.st_rdev; if (bii2.inline_length > 0) { totalsize += bii2.inline_length; sizes[0] += bii2.inline_length; } if (bii2.disk_size_uncompressed > 0) { totalsize += bii2.disk_size_uncompressed; sizes[1] += bii2.disk_size_uncompressed; } if (bii2.disk_size_zlib > 0) { totalsize += bii2.disk_size_zlib; sizes[2] += bii2.disk_size_zlib; } if (bii2.disk_size_lzo > 0) { totalsize += bii2.disk_size_lzo; sizes[3] += bii2.disk_size_lzo; } if (bii2.disk_size_zstd > 0) { totalsize += bii2.disk_size_zstd; sizes[4] += bii2.disk_size_zstd; } sparsesize += bii2.sparse_size; FILE_STANDARD_INFORMATION fsi; Status = NtQueryInformationFile(h, &iosb, &fsi, sizeof(fsi), FileStandardInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (bii2.inline_length > 0) allocsize += fsi.EndOfFile.QuadPart; else allocsize += fsi.AllocationSize.QuadPart; min_mode |= ~bii2.st_mode; max_mode |= bii2.st_mode; min_flags |= ~bii2.flags; max_flags |= bii2.flags; min_compression_type = bii2.compression_type < min_compression_type ? bii2.compression_type : min_compression_type; max_compression_type = bii2.compression_type > max_compression_type ? bii2.compression_type : max_compression_type; if (bii2.inode == SUBVOL_ROOT_INODE) { bool ro = bhfi.dwFileAttributes & FILE_ATTRIBUTE_READONLY; has_subvols = true; if (sv == 0) ro_subvol = ro; else { if (ro_subvol != ro) various_ro = true; } sv++; } ignore = false; if (bii2.type != BTRFS_TYPE_DIRECTORY && GetFileSizeEx(h, &filesize)) { if (filesize.QuadPart != 0) can_change_nocow = false; } } else return; min_mode = ~min_mode; min_flags = ~min_flags; mode = min_mode; mode_set = ~(min_mode ^ max_mode); flags = min_flags; flags_set = ~(min_flags ^ max_flags); if (search_list.size() > 0) { thread = CreateThread(nullptr, 0, global_search_list_thread, this, 0, nullptr); if (!thread) throw last_error(GetLastError()); } this->filename = cmdline; } static ULONG inode_type_to_string_ref(uint8_t type) { switch (type) { case BTRFS_TYPE_FILE: return IDS_INODE_FILE; case BTRFS_TYPE_DIRECTORY: return IDS_INODE_DIR; case BTRFS_TYPE_CHARDEV: return IDS_INODE_CHAR; case BTRFS_TYPE_BLOCKDEV: return IDS_INODE_BLOCK; case BTRFS_TYPE_FIFO: return IDS_INODE_FIFO; case BTRFS_TYPE_SOCKET: return IDS_INODE_SOCKET; case BTRFS_TYPE_SYMLINK: return IDS_INODE_SYMLINK; default: return IDS_INODE_UNKNOWN; } } void BtrfsPropSheet::change_inode_flag(HWND hDlg, uint64_t flag, UINT state) { if (flag & BTRFS_INODE_NODATACOW) flag |= BTRFS_INODE_NODATASUM; if (state == BST_CHECKED) { flags |= flag; flags_set |= flag; } else if (state == BST_UNCHECKED) { flags &= ~flag; flags_set |= flag; } else if (state == BST_INDETERMINATE) { flags_set = ~flag; } if (flags & BTRFS_INODE_NODATACOW && flags_set & BTRFS_INODE_NODATACOW) { EnableWindow(GetDlgItem(hDlg, IDC_COMPRESS), false); EnableWindow(GetDlgItem(hDlg, IDC_COMPRESS_TYPE), false); } else { EnableWindow(GetDlgItem(hDlg, IDC_COMPRESS), true); EnableWindow(GetDlgItem(hDlg, IDC_COMPRESS_TYPE), flags & BTRFS_INODE_COMPRESS && flags_set & BTRFS_INODE_COMPRESS); } EnableWindow(GetDlgItem(hDlg, IDC_NODATACOW), !(flags & BTRFS_INODE_COMPRESS) || !(flags_set & BTRFS_INODE_COMPRESS)); flags_changed = true; SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0); } void BtrfsPropSheet::apply_changes_file(HWND, const wstring& fn) { win_handle h; IO_STATUS_BLOCK iosb; NTSTATUS Status; btrfs_set_inode_info bsii; btrfs_inode_info bii2; ULONG perms = FILE_TRAVERSE | FILE_READ_ATTRIBUTES; if (flags_changed || ro_changed) perms |= FILE_WRITE_ATTRIBUTES; if (perms_changed || gid_changed || uid_changed) perms |= WRITE_DAC; if (mode_set & S_ISUID && (((min_mode & S_ISUID) != (max_mode & S_ISUID)) || ((min_mode & S_ISUID) != (mode & S_ISUID)))) perms |= WRITE_OWNER; h = CreateFileW(fn.c_str(), perms, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); ZeroMemory(&bsii, sizeof(btrfs_set_inode_info)); Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii2, sizeof(btrfs_inode_info)); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); if (bii2.inode == SUBVOL_ROOT_INODE && ro_changed) { BY_HANDLE_FILE_INFORMATION bhfi; FILE_BASIC_INFO fbi; if (!GetFileInformationByHandle(h, &bhfi)) throw last_error(GetLastError()); memset(&fbi, 0, sizeof(fbi)); fbi.FileAttributes = bhfi.dwFileAttributes; if (ro_subvol) fbi.FileAttributes |= FILE_ATTRIBUTE_READONLY; else fbi.FileAttributes &= ~FILE_ATTRIBUTE_READONLY; Status = NtSetInformationFile(h, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } if (flags_changed || perms_changed || uid_changed || gid_changed || compress_type_changed) { if (flags_changed) { bsii.flags_changed = true; bsii.flags = (bii2.flags & ~flags_set) | (flags & flags_set); } if (perms_changed) { bsii.mode_changed = true; bsii.st_mode = (bii2.st_mode & ~mode_set) | (mode & mode_set); } if (uid_changed) { bsii.uid_changed = true; bsii.st_uid = uid; } if (gid_changed) { bsii.gid_changed = true; bsii.st_gid = gid; } if (compress_type_changed) { bsii.compression_type_changed = true; bsii.compression_type = compress_type; } Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } } void BtrfsPropSheet::apply_changes(HWND hDlg) { UINT num_files, i; WCHAR fn[MAX_PATH]; // FIXME - is this long enough? if (various_uids) uid_changed = false; if (various_gids) gid_changed = false; if (!flags_changed && !perms_changed && !uid_changed && !gid_changed && !compress_type_changed && !ro_changed) return; if (filename[0] != 0) apply_changes_file(hDlg, filename); else { num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0); for (i = 0; i < num_files; i++) { if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) { apply_changes_file(hDlg, fn); } } } flags_changed = false; perms_changed = false; uid_changed = false; gid_changed = false; ro_changed = false; } void BtrfsPropSheet::set_size_on_disk(HWND hwndDlg) { wstring s, size_on_disk, cr, frag; WCHAR old_text[1024]; float ratio; format_size(totalsize, size_on_disk, true); wstring_sprintf(s, size_format, size_on_disk.c_str()); if (allocsize == sparsesize || totalsize == 0) ratio = 0.0f; else ratio = 100.0f * (1.0f - ((float)totalsize / (float)(allocsize - sparsesize))); wstring_sprintf(cr, cr_format, ratio); GetDlgItemTextW(hwndDlg, IDC_SIZE_ON_DISK, old_text, sizeof(old_text) / sizeof(WCHAR)); if (s != old_text) SetDlgItemTextW(hwndDlg, IDC_SIZE_ON_DISK, s.c_str()); GetDlgItemTextW(hwndDlg, IDC_COMPRESSION_RATIO, old_text, sizeof(old_text) / sizeof(WCHAR)); if (cr != old_text) SetDlgItemTextW(hwndDlg, IDC_COMPRESSION_RATIO, cr.c_str()); uint64_t extent_size = (allocsize - sparsesize - sizes[0]) / (sector_size == 0 ? 4096 : sector_size); if (num_extents == 0 || extent_size <= 1) ratio = 0.0f; else ratio = 100.0f * ((float)num_extents / (float)(extent_size - 1)); wstring_sprintf(frag, frag_format, ratio); GetDlgItemTextW(hwndDlg, IDC_FRAGMENTATION, old_text, sizeof(old_text) / sizeof(WCHAR)); if (frag != old_text) SetDlgItemTextW(hwndDlg, IDC_FRAGMENTATION, frag.c_str()); } void BtrfsPropSheet::change_perm_flag(HWND hDlg, ULONG flag, UINT state) { if (state == BST_CHECKED) { mode |= flag; mode_set |= flag; } else if (state == BST_UNCHECKED) { mode &= ~flag; mode_set |= flag; } else if (state == BST_INDETERMINATE) { mode_set = ~flag; } perms_changed = true; SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0); } void BtrfsPropSheet::change_uid(HWND hDlg, uint32_t uid) { if (this->uid != uid) { this->uid = uid; uid_changed = true; SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0); } } void BtrfsPropSheet::change_gid(HWND hDlg, uint32_t gid) { if (this->gid != gid) { this->gid = gid; gid_changed = true; SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0); } } void BtrfsPropSheet::update_size_details_dialog(HWND hDlg) { wstring size; WCHAR old_text[1024]; int i; ULONG items[] = { IDC_SIZE_INLINE, IDC_SIZE_UNCOMPRESSED, IDC_SIZE_ZLIB, IDC_SIZE_LZO, IDC_SIZE_ZSTD }; for (i = 0; i < 5; i++) { format_size(sizes[i], size, true); GetDlgItemTextW(hDlg, items[i], old_text, sizeof(old_text) / sizeof(WCHAR)); if (size != old_text) SetDlgItemTextW(hDlg, items[i], size.c_str()); } } static INT_PTR CALLBACK SizeDetailsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { try { switch (uMsg) { case WM_INITDIALOG: { BtrfsPropSheet* bps = (BtrfsPropSheet*)lParam; SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)bps); bps->update_size_details_dialog(hwndDlg); if (bps->thread) SetTimer(hwndDlg, 1, 250, nullptr); return true; } case WM_COMMAND: if (HIWORD(wParam) == BN_CLICKED && (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) { EndDialog(hwndDlg, 0); return true; } break; case WM_TIMER: { BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); if (bps) { bps->update_size_details_dialog(hwndDlg); if (!bps->thread) KillTimer(hwndDlg, 1); } break; } } } catch (const exception& e) { error_message(hwndDlg, e.what()); } return false; } static void set_check_box(HWND hwndDlg, ULONG id, uint64_t min, uint64_t max) { if (min && max) { SendDlgItemMessageW(hwndDlg, id, BM_SETCHECK, BST_CHECKED, 0); } else if (!min && !max) { SendDlgItemMessageW(hwndDlg, id, BM_SETCHECK, BST_UNCHECKED, 0); } else { LONG_PTR style; style = GetWindowLongPtrW(GetDlgItem(hwndDlg, id), GWL_STYLE); style &= ~BS_AUTOCHECKBOX; style |= BS_AUTO3STATE; SetWindowLongPtrW(GetDlgItem(hwndDlg, id), GWL_STYLE, style); SendDlgItemMessageW(hwndDlg, id, BM_SETCHECK, BST_INDETERMINATE, 0); } } void BtrfsPropSheet::open_as_admin(HWND hwndDlg) { ULONG num_files, i; WCHAR fn[MAX_PATH], modfn[MAX_PATH]; num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0); if (num_files == 0) return; GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR)); for (i = 0; i < num_files; i++) { if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) { wstring t; SHELLEXECUTEINFOW sei; t = L"\""s + modfn + L"\",ShowPropSheet "s + fn; RtlZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); sei.hwnd = hwndDlg; sei.lpVerb = L"runas"; sei.lpFile = L"rundll32.exe"; sei.lpParameters = t.c_str(); sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExW(&sei)) throw last_error(GetLastError()); WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); load_file_list(); init_propsheet(hwndDlg); } } } // based on functions in sys/sysmacros.h #define major(rdev) ((((rdev) >> 8) & 0xFFF) | ((uint32_t)((rdev) >> 32) & ~0xFFF)) #define minor(rdev) (((rdev) & 0xFF) | ((uint32_t)((rdev) >> 12) & ~0xFF)) void BtrfsPropSheet::init_propsheet(HWND hwndDlg) { wstring s; ULONG sr; int i; HWND comptype; static ULONG perm_controls[] = { IDC_USERR, IDC_USERW, IDC_USERX, IDC_GROUPR, IDC_GROUPW, IDC_GROUPX, IDC_OTHERR, IDC_OTHERW, IDC_OTHERX, IDC_SETUID, IDC_SETGID, IDC_STICKY, 0 }; 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 }; static ULONG comp_types[] = { IDS_COMPRESS_ANY, IDS_COMPRESS_ZLIB, IDS_COMPRESS_LZO, IDS_COMPRESS_ZSTD, 0 }; if (various_subvols) { if (!load_string(module, IDS_VARIOUS, s)) throw last_error(GetLastError()); } else wstring_sprintf(s, L"%llx", subvol); SetDlgItemTextW(hwndDlg, IDC_SUBVOL, s.c_str()); if (various_inodes) { if (!load_string(module, IDS_VARIOUS, s)) throw last_error(GetLastError()); } else wstring_sprintf(s, L"%llx", inode); SetDlgItemTextW(hwndDlg, IDC_INODE, s.c_str()); if (various_types) sr = IDS_VARIOUS; else sr = inode_type_to_string_ref(type); if (various_inodes) { if (sr == IDS_INODE_CHAR) sr = IDS_INODE_CHAR_SIMPLE; else if (sr == IDS_INODE_BLOCK) sr = IDS_INODE_BLOCK_SIMPLE; } if (sr == IDS_INODE_UNKNOWN) { wstring t; if (!load_string(module, sr, t)) throw last_error(GetLastError()); wstring_sprintf(s, t, type); } else if (sr == IDS_INODE_CHAR || sr == IDS_INODE_BLOCK) { wstring t; if (!load_string(module, sr, t)) throw last_error(GetLastError()); wstring_sprintf(s, t, major(rdev), minor(rdev)); } else { if (!load_string(module, sr, s)) throw last_error(GetLastError()); } SetDlgItemTextW(hwndDlg, IDC_TYPE, s.c_str()); if (size_format[0] == 0) GetDlgItemTextW(hwndDlg, IDC_SIZE_ON_DISK, size_format, sizeof(size_format) / sizeof(WCHAR)); if (cr_format[0] == 0) GetDlgItemTextW(hwndDlg, IDC_COMPRESSION_RATIO, cr_format, sizeof(cr_format) / sizeof(WCHAR)); if (frag_format[0] == 0) GetDlgItemTextW(hwndDlg, IDC_FRAGMENTATION, frag_format, sizeof(frag_format) / sizeof(WCHAR)); set_size_on_disk(hwndDlg); if (thread) SetTimer(hwndDlg, 1, 250, nullptr); set_check_box(hwndDlg, IDC_NODATACOW, min_flags & BTRFS_INODE_NODATACOW, max_flags & BTRFS_INODE_NODATACOW); set_check_box(hwndDlg, IDC_COMPRESS, min_flags & BTRFS_INODE_COMPRESS, max_flags & BTRFS_INODE_COMPRESS); if (min_flags & BTRFS_INODE_NODATACOW || max_flags & BTRFS_INODE_NODATACOW) EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS), false); else if (min_flags & BTRFS_INODE_COMPRESS || max_flags & BTRFS_INODE_COMPRESS) EnableWindow(GetDlgItem(hwndDlg, IDC_NODATACOW), false); comptype = GetDlgItem(hwndDlg, IDC_COMPRESS_TYPE); while (SendMessageW(comptype, CB_GETCOUNT, 0, 0) > 0) { SendMessageW(comptype, CB_DELETESTRING, 0, 0); } if (min_compression_type != max_compression_type) { SendMessageW(comptype, CB_ADDSTRING, 0, (LPARAM)L""); SendMessageW(comptype, CB_SETCURSEL, 0, 0); } i = 0; while (comp_types[i] != 0) { wstring t; if (!load_string(module, comp_types[i], t)) throw last_error(GetLastError()); SendMessageW(comptype, CB_ADDSTRING, 0, (LPARAM)t.c_str()); i++; } if (min_compression_type == max_compression_type) { SendMessageW(comptype, CB_SETCURSEL, min_compression_type, 0); compress_type = min_compression_type; } EnableWindow(comptype, max_flags & BTRFS_INODE_COMPRESS); i = 0; while (perm_controls[i] != 0) { set_check_box(hwndDlg, perm_controls[i], min_mode & perms[i], max_mode & perms[i]); i++; } if (various_uids) { if (!load_string(module, IDS_VARIOUS, s)) throw last_error(GetLastError()); EnableWindow(GetDlgItem(hwndDlg, IDC_UID), 0); } else s = to_wstring(uid); SetDlgItemTextW(hwndDlg, IDC_UID, s.c_str()); if (various_gids) { if (!load_string(module, IDS_VARIOUS, s)) throw last_error(GetLastError()); EnableWindow(GetDlgItem(hwndDlg, IDC_GID), 0); } else s = to_wstring(gid); SetDlgItemTextW(hwndDlg, IDC_GID, s.c_str()); ShowWindow(GetDlgItem(hwndDlg, IDC_SUBVOL_RO), has_subvols); if (has_subvols) set_check_box(hwndDlg, IDC_SUBVOL_RO, ro_subvol, various_ro ? (!ro_subvol) : ro_subvol); if (!can_change_nocow) EnableWindow(GetDlgItem(hwndDlg, IDC_NODATACOW), 0); if (!can_change_perms) { i = 0; while (perm_controls[i] != 0) { EnableWindow(GetDlgItem(hwndDlg, perm_controls[i]), 0); i++; } EnableWindow(GetDlgItem(hwndDlg, IDC_UID), 0); EnableWindow(GetDlgItem(hwndDlg, IDC_GID), 0); EnableWindow(GetDlgItem(hwndDlg, IDC_SETUID), 0); } if (readonly) { EnableWindow(GetDlgItem(hwndDlg, IDC_NODATACOW), 0); EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS), 0); EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS_TYPE), 0); } if (show_admin_button) { SendMessageW(GetDlgItem(hwndDlg, IDC_OPEN_ADMIN), BCM_SETSHIELD, 0, true); ShowWindow(GetDlgItem(hwndDlg, IDC_OPEN_ADMIN), SW_SHOW); } else ShowWindow(GetDlgItem(hwndDlg, IDC_OPEN_ADMIN), SW_HIDE); } static INT_PTR CALLBACK PropSheetDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { try { switch (uMsg) { case WM_INITDIALOG: { PROPSHEETPAGEW* psp = (PROPSHEETPAGEW*)lParam; BtrfsPropSheet* bps = (BtrfsPropSheet*)psp->lParam; EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)bps); bps->init_propsheet(hwndDlg); return false; } case WM_COMMAND: { BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); if (bps && !bps->readonly) { switch (HIWORD(wParam)) { case BN_CLICKED: { switch (LOWORD(wParam)) { case IDC_NODATACOW: bps->change_inode_flag(hwndDlg, BTRFS_INODE_NODATACOW, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); break; case IDC_COMPRESS: bps->change_inode_flag(hwndDlg, BTRFS_INODE_COMPRESS, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS_TYPE), IsDlgButtonChecked(hwndDlg, LOWORD(wParam)) != BST_UNCHECKED); break; case IDC_USERR: bps->change_perm_flag(hwndDlg, S_IRUSR, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); break; case IDC_USERW: bps->change_perm_flag(hwndDlg, S_IWUSR, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); break; case IDC_USERX: bps->change_perm_flag(hwndDlg, S_IXUSR, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); break; case IDC_GROUPR: bps->change_perm_flag(hwndDlg, S_IRGRP, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); break; case IDC_GROUPW: bps->change_perm_flag(hwndDlg, S_IWGRP, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); break; case IDC_GROUPX: bps->change_perm_flag(hwndDlg, S_IXGRP, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); break; case IDC_OTHERR: bps->change_perm_flag(hwndDlg, S_IROTH, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); break; case IDC_OTHERW: bps->change_perm_flag(hwndDlg, S_IWOTH, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); break; case IDC_OTHERX: bps->change_perm_flag(hwndDlg, S_IXOTH, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); break; case IDC_SETUID: bps->change_perm_flag(hwndDlg, S_ISUID, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); break; case IDC_SETGID: bps->change_perm_flag(hwndDlg, S_ISGID, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); break; case IDC_STICKY: bps->change_perm_flag(hwndDlg, S_ISVTX, IsDlgButtonChecked(hwndDlg, LOWORD(wParam))); break; case IDC_SUBVOL_RO: switch (IsDlgButtonChecked(hwndDlg, LOWORD(wParam))) { case BST_CHECKED: bps->ro_subvol = true; bps->ro_changed = true; break; case BST_UNCHECKED: bps->ro_subvol = false; bps->ro_changed = true; break; case BST_INDETERMINATE: bps->ro_changed = false; break; } SendMessageW(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0); break; case IDC_OPEN_ADMIN: bps->open_as_admin(hwndDlg); break; } break; } case EN_CHANGE: { switch (LOWORD(wParam)) { case IDC_UID: { WCHAR s[255]; GetDlgItemTextW(hwndDlg, LOWORD(wParam), s, sizeof(s) / sizeof(WCHAR)); bps->change_uid(hwndDlg, _wtoi(s)); break; } case IDC_GID: { WCHAR s[255]; GetDlgItemTextW(hwndDlg, LOWORD(wParam), s, sizeof(s) / sizeof(WCHAR)); bps->change_gid(hwndDlg, _wtoi(s)); break; } } break; } case CBN_SELCHANGE: { switch (LOWORD(wParam)) { case IDC_COMPRESS_TYPE: { auto sel = SendMessageW(GetDlgItem(hwndDlg, LOWORD(wParam)), CB_GETCURSEL, 0, 0); if (bps->min_compression_type != bps->max_compression_type) { if (sel == 0) bps->compress_type_changed = false; else { bps->compress_type = (uint8_t)(sel - 1); bps->compress_type_changed = true; } } else { bps->compress_type = (uint8_t)sel; bps->compress_type_changed = true; } SendMessageW(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0); break; } } break; } } } break; } case WM_NOTIFY: { switch (((LPNMHDR)lParam)->code) { case PSN_KILLACTIVE: SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, false); break; case PSN_APPLY: { BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); bps->apply_changes(hwndDlg); SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); break; } case NM_CLICK: case NM_RETURN: { if (((LPNMHDR)lParam)->hwndFrom == GetDlgItem(hwndDlg, IDC_SIZE_ON_DISK)) { PNMLINK pNMLink = (PNMLINK)lParam; if (pNMLink->item.iLink == 0) DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_SIZE_DETAILS), hwndDlg, SizeDetailsDlgProc, GetWindowLongPtrW(hwndDlg, GWLP_USERDATA)); } break; } } break; } case WM_TIMER: { BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); if (bps) { bps->set_size_on_disk(hwndDlg); if (!bps->thread) KillTimer(hwndDlg, 1); } break; } } } catch (const exception& e) { error_message(hwndDlg, e.what()); } return false; } HRESULT __stdcall BtrfsPropSheet::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) { try { PROPSHEETPAGEW psp; HPROPSHEETPAGE hPage; INITCOMMONCONTROLSEX icex; if (ignore) return S_OK; icex.dwSize = sizeof(icex); icex.dwICC = ICC_LINK_CLASS; if (!InitCommonControlsEx(&icex)) throw string_error(IDS_INITCOMMONCONTROLSEX_FAILED); psp.dwSize = sizeof(psp); psp.dwFlags = PSP_USEREFPARENT | PSP_USETITLE; psp.hInstance = module; psp.pszTemplate = MAKEINTRESOURCEW(IDD_PROP_SHEET); psp.hIcon = 0; psp.pszTitle = MAKEINTRESOURCEW(IDS_PROP_SHEET_TITLE); psp.pfnDlgProc = (DLGPROC)PropSheetDlgProc; psp.pcRefParent = (UINT*)&objs_loaded; psp.pfnCallback = nullptr; psp.lParam = (LPARAM)this; hPage = CreatePropertySheetPageW(&psp); if (hPage) { if (pfnAddPage(hPage, lParam)) { this->AddRef(); return S_OK; } else DestroyPropertySheetPage(hPage); } else return E_OUTOFMEMORY; } catch (const exception& e) { error_message(nullptr, e.what()); } return E_FAIL; } HRESULT __stdcall BtrfsPropSheet::ReplacePage(UINT, LPFNADDPROPSHEETPAGE, LPARAM) { return S_OK; } extern "C" { void CALLBACK ShowPropSheetW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) { try { BtrfsPropSheet bps; PROPSHEETPAGEW psp; PROPSHEETHEADERW psh; INITCOMMONCONTROLSEX icex; wstring title; set_dpi_aware(); load_string(module, IDS_STANDALONE_PROPSHEET_TITLE, title); bps.set_cmdline(lpszCmdLine); icex.dwSize = sizeof(icex); icex.dwICC = ICC_LINK_CLASS; if (!InitCommonControlsEx(&icex)) throw string_error(IDS_INITCOMMONCONTROLSEX_FAILED); psp.dwSize = sizeof(psp); psp.dwFlags = PSP_USETITLE; psp.hInstance = module; psp.pszTemplate = MAKEINTRESOURCEW(IDD_PROP_SHEET); psp.hIcon = 0; psp.pszTitle = MAKEINTRESOURCEW(IDS_PROP_SHEET_TITLE); psp.pfnDlgProc = (DLGPROC)PropSheetDlgProc; psp.pfnCallback = nullptr; psp.lParam = (LPARAM)&bps; memset(&psh, 0, sizeof(PROPSHEETHEADERW)); psh.dwSize = sizeof(PROPSHEETHEADERW); psh.dwFlags = PSH_PROPSHEETPAGE; psh.hwndParent = hwnd; psh.hInstance = psp.hInstance; psh.pszCaption = title.c_str(); psh.nPages = 1; psh.ppsp = &psp; if (PropertySheetW(&psh) < 0) throw last_error(GetLastError()); } catch (const exception& e) { error_message(hwnd, e.what()); } } } ================================================ FILE: src/shellext/propsheet.h ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #pragma once #include #include #include "../btrfsioctl.h" #ifndef S_IRUSR #define S_IRUSR 0000400 #endif #ifndef S_IWUSR #define S_IWUSR 0000200 #endif #ifndef S_IXUSR #define S_IXUSR 0000100 #endif #ifndef S_IRGRP #define S_IRGRP (S_IRUSR >> 3) #endif #ifndef S_IWGRP #define S_IWGRP (S_IWUSR >> 3) #endif #ifndef S_IXGRP #define S_IXGRP (S_IXUSR >> 3) #endif #ifndef S_IROTH #define S_IROTH (S_IRGRP >> 3) #endif #ifndef S_IWOTH #define S_IWOTH (S_IWGRP >> 3) #endif #ifndef S_IXOTH #define S_IXOTH (S_IXGRP >> 3) #endif #ifndef S_ISUID #define S_ISUID 0004000 #endif #ifndef S_ISGID #define S_ISGID 0002000 #endif #ifndef S_ISVTX #define S_ISVTX 0001000 #endif #define BTRFS_INODE_NODATASUM 0x001 #define BTRFS_INODE_NODATACOW 0x002 #define BTRFS_INODE_READONLY 0x004 #define BTRFS_INODE_NOCOMPRESS 0x008 #define BTRFS_INODE_PREALLOC 0x010 #define BTRFS_INODE_SYNC 0x020 #define BTRFS_INODE_IMMUTABLE 0x040 #define BTRFS_INODE_APPEND 0x080 #define BTRFS_INODE_NODUMP 0x100 #define BTRFS_INODE_NOATIME 0x200 #define BTRFS_INODE_DIRSYNC 0x400 #define BTRFS_INODE_COMPRESS 0x800 extern LONG objs_loaded; class BtrfsPropSheet : public IShellExtInit, IShellPropSheetExt { public: BtrfsPropSheet() { refcount = 0; ignore = true; stgm_set = false; readonly = false; flags_changed = false; perms_changed = false; uid_changed = false; gid_changed = false; compress_type_changed = false; ro_changed = false; can_change_perms = false; show_admin_button = false; thread = nullptr; mode = mode_set = 0; flags = flags_set = 0; has_subvols = false; filename = L""; sizes[0] = sizes[1] = sizes[2] = sizes[3] = sizes[4] = 0; totalsize = allocsize = sparsesize = 0; num_extents = 0; sector_size = 0; size_format[0] = 0; cr_format[0] = 0; frag_format[0] = 0; InterlockedIncrement(&objs_loaded); } virtual ~BtrfsPropSheet() { if (stgm_set) ReleaseStgMedium(&stgm); InterlockedDecrement(&objs_loaded); } // IUnknown HRESULT __stdcall QueryInterface(REFIID riid, void **ppObj); ULONG __stdcall AddRef() { return InterlockedIncrement(&refcount); } ULONG __stdcall Release() { LONG rc = InterlockedDecrement(&refcount); if (rc == 0) delete this; return rc; } // IShellExtInit virtual HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) override; // IShellPropSheetExt virtual HRESULT __stdcall AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) override; virtual HRESULT __stdcall ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplacePage, LPARAM lParam) override; void init_propsheet(HWND hwndDlg); void change_inode_flag(HWND hDlg, uint64_t flag, UINT state); void change_perm_flag(HWND hDlg, ULONG perm, UINT state); void change_uid(HWND hDlg, uint32_t uid); void change_gid(HWND hDlg, uint32_t gid); void apply_changes(HWND hDlg); void set_size_on_disk(HWND hwndDlg); DWORD search_list_thread(); void do_search(const wstring& fn); void update_size_details_dialog(HWND hDlg); void open_as_admin(HWND hwndDlg); void set_cmdline(const wstring& cmdline); bool readonly; bool can_change_perms; bool can_change_nocow; WCHAR size_format[255], cr_format[255], frag_format[255]; HANDLE thread; uint32_t min_mode, max_mode, mode, mode_set; uint64_t min_flags, max_flags, flags, flags_set; uint64_t subvol, inode, rdev; uint8_t type, min_compression_type, max_compression_type, compress_type; uint32_t uid, gid; bool various_subvols, various_inodes, various_types, various_uids, various_gids, compress_type_changed, has_subvols, ro_subvol, various_ro, ro_changed, show_admin_button; private: LONG refcount; bool ignore; STGMEDIUM stgm; bool stgm_set; bool flags_changed, perms_changed, uid_changed, gid_changed; uint64_t sizes[5], totalsize, allocsize, sparsesize, num_extents; deque search_list; wstring filename; uint32_t sector_size; void apply_changes_file(HWND hDlg, const wstring& fn); HRESULT check_file(const wstring& fn, UINT i, UINT num_files, UINT* sv); HRESULT load_file_list(); }; ================================================ FILE: src/shellext/recv.cpp ================================================ /* Copyright (c) Mark Harmstone 2017 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "shellext.h" #include #include #include #include #include #include "recv.h" #include "resource.h" #include "../crc32c.h" #ifndef _MSC_VER #include #else #include #endif static const string_view EA_NTACL = "security.NTACL"; static const string_view EA_DOSATTRIB = "user.DOSATTRIB"; static const string_view EA_REPARSE = "user.reparse"; static const string_view EA_EA = "user.EA"; static const string_view XATTR_USER = "user."; bool BtrfsRecv::find_tlv(uint8_t* data, ULONG datalen, uint16_t type, void** value, ULONG* len) { size_t off = 0; while (off < datalen) { btrfs_send_tlv* tlv = (btrfs_send_tlv*)(data + off); uint8_t* payload = data + off + sizeof(btrfs_send_tlv); if (off + sizeof(btrfs_send_tlv) + tlv->length > datalen) // file is truncated return false; if (tlv->type == type) { *value = payload; *len = tlv->length; return true; } off += sizeof(btrfs_send_tlv) + tlv->length; } return false; } void BtrfsRecv::cmd_subvol(btrfs_send_command* cmd, uint8_t* data, const win_handle& parent) { string name; BTRFS_UUID* uuid; uint64_t* gen; ULONG uuidlen, genlen; btrfs_create_subvol* bcs; NTSTATUS Status; IO_STATUS_BLOCK iosb; { char* namebuf; ULONG namelen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&namebuf, &namelen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); name = string(namebuf, namelen); } if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_UUID, (void**)&uuid, &uuidlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"uuid"); if (uuidlen < sizeof(BTRFS_UUID)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"uuid", uuidlen, sizeof(BTRFS_UUID)); if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_TRANSID, (void**)&gen, &genlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"transid"); if (genlen < sizeof(uint64_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"transid", genlen, sizeof(uint64_t)); this->subvol_uuid = *uuid; this->stransid = *gen; auto nameu = utf8_to_utf16(name); size_t bcslen = offsetof(btrfs_create_subvol, name[0]) + (nameu.length() * sizeof(WCHAR)); bcs = (btrfs_create_subvol*)malloc(bcslen); bcs->readonly = true; bcs->posix = true; bcs->namelen = (uint16_t)(nameu.length() * sizeof(WCHAR)); memcpy(bcs->name, nameu.c_str(), bcs->namelen); Status = NtFsControlFile(parent, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SUBVOL, bcs, (ULONG)bcslen, nullptr, 0); if (!NT_SUCCESS(Status)) throw string_error(IDS_RECV_CREATE_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str()); subvolpath = dirpath; subvolpath += L"\\"; subvolpath += nameu; if (dir != INVALID_HANDLE_VALUE) CloseHandle(dir); if (master != INVALID_HANDLE_VALUE) CloseHandle(master); master = CreateFileW(subvolpath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (master == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_PATH, subvolpath.c_str(), GetLastError(), format_message(GetLastError()).c_str()); Status = NtFsControlFile(master, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RESERVE_SUBVOL, bcs, (ULONG)bcslen, nullptr, 0); if (!NT_SUCCESS(Status)) throw string_error(IDS_RECV_RESERVE_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str()); dir = CreateFileW(subvolpath.c_str(), FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (dir == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_PATH, subvolpath.c_str(), GetLastError(), format_message(GetLastError()).c_str()); subvolpath += L"\\"; add_cache_entry(&this->subvol_uuid, this->stransid, subvolpath); num_received++; } void BtrfsRecv::add_cache_entry(BTRFS_UUID* uuid, uint64_t transid, const wstring& path) { subvol_cache sc; sc.uuid = *uuid; sc.transid = transid; sc.path = path; cache.push_back(sc); } void BtrfsRecv::cmd_snapshot(btrfs_send_command* cmd, uint8_t* data, const win_handle& parent) { string name; BTRFS_UUID *uuid, *parent_uuid; uint64_t *gen, *parent_transid; ULONG uuidlen, genlen, paruuidlen, partransidlen; btrfs_create_snapshot* bcs; NTSTATUS Status; IO_STATUS_BLOCK iosb; wstring parpath; btrfs_find_subvol bfs; WCHAR parpathw[MAX_PATH], volpathw[MAX_PATH]; size_t bcslen; { char* namebuf; ULONG namelen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&namebuf, &namelen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); name = string(namebuf, namelen); } if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_UUID, (void**)&uuid, &uuidlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"uuid"); if (uuidlen < sizeof(BTRFS_UUID)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"uuid", uuidlen, sizeof(BTRFS_UUID)); if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_TRANSID, (void**)&gen, &genlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"transid"); if (genlen < sizeof(uint64_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"transid", genlen, sizeof(uint64_t)); if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_UUID, (void**)&parent_uuid, &paruuidlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"clone_uuid"); if (paruuidlen < sizeof(BTRFS_UUID)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"clone_uuid", paruuidlen, sizeof(BTRFS_UUID)); if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_CTRANSID, (void**)&parent_transid, &partransidlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"clone_ctransid"); if (partransidlen < sizeof(uint64_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"clone_ctransid", partransidlen, sizeof(uint64_t)); this->subvol_uuid = *uuid; this->stransid = *gen; auto nameu = utf8_to_utf16(name); bfs.uuid = *parent_uuid; bfs.ctransid = *parent_transid; Status = NtFsControlFile(parent, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_FIND_SUBVOL, &bfs, sizeof(btrfs_find_subvol), parpathw, sizeof(parpathw)); if (Status == STATUS_NOT_FOUND) throw string_error(IDS_RECV_CANT_FIND_PARENT_SUBVOL); else if (!NT_SUCCESS(Status)) throw string_error(IDS_RECV_FIND_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str()); if (!GetVolumePathNameW(dirpath.c_str(), volpathw, (sizeof(volpathw) / sizeof(WCHAR)) - 1)) throw string_error(IDS_RECV_GETVOLUMEPATHNAME_FAILED, GetLastError(), format_message(GetLastError()).c_str()); parpath = volpathw; if (parpath.substr(parpath.length() - 1) == L"\\") parpath = parpath.substr(0, parpath.length() - 1); parpath += parpathw; { win_handle subvol = CreateFileW(parpath.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (subvol == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_PATH, parpath.c_str(), GetLastError(), format_message(GetLastError()).c_str()); bcslen = offsetof(btrfs_create_snapshot, name[0]) + (nameu.length() * sizeof(WCHAR)); bcs = (btrfs_create_snapshot*)malloc(bcslen); bcs->readonly = true; bcs->posix = true; bcs->subvol = subvol; bcs->namelen = (uint16_t)(nameu.length() * sizeof(WCHAR)); memcpy(bcs->name, nameu.c_str(), bcs->namelen); Status = NtFsControlFile(parent, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, (ULONG)bcslen, nullptr, 0); if (!NT_SUCCESS(Status)) throw string_error(IDS_RECV_CREATE_SNAPSHOT_FAILED, Status, format_ntstatus(Status).c_str()); } subvolpath = dirpath; subvolpath += L"\\"; subvolpath += nameu; if (dir != INVALID_HANDLE_VALUE) CloseHandle(dir); if (master != INVALID_HANDLE_VALUE) CloseHandle(master); master = CreateFileW(subvolpath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (master == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_PATH, subvolpath.c_str(), GetLastError(), format_message(GetLastError()).c_str()); Status = NtFsControlFile(master, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RESERVE_SUBVOL, bcs, (ULONG)bcslen, nullptr, 0); if (!NT_SUCCESS(Status)) throw string_error(IDS_RECV_RESERVE_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str()); dir = CreateFileW(subvolpath.c_str(), FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (dir == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_PATH, subvolpath.c_str(), GetLastError(), format_message(GetLastError()).c_str()); subvolpath += L"\\"; add_cache_entry(&this->subvol_uuid, this->stransid, subvolpath); num_received++; } void BtrfsRecv::cmd_mkfile(btrfs_send_command* cmd, uint8_t* data) { uint64_t *inode, *rdev = nullptr, *mode = nullptr; ULONG inodelen; NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_mknod* bmn; wstring nameu, pathlinku; { char* name; ULONG namelen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&name, &namelen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); nameu = utf8_to_utf16(string(name, namelen)); } if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_INODE, (void**)&inode, &inodelen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"inode"); if (inodelen < sizeof(uint64_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"inode", inodelen, sizeof(uint64_t)); if (cmd->cmd == BTRFS_SEND_CMD_MKNOD || cmd->cmd == BTRFS_SEND_CMD_MKFIFO || cmd->cmd == BTRFS_SEND_CMD_MKSOCK) { ULONG rdevlen, modelen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_RDEV, (void**)&rdev, &rdevlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"rdev"); if (rdevlen < sizeof(uint64_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"rdev", rdev, sizeof(uint64_t)); if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_MODE, (void**)&mode, &modelen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"mode"); if (modelen < sizeof(uint64_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"mode", modelen, sizeof(uint64_t)); } else if (cmd->cmd == BTRFS_SEND_CMD_SYMLINK) { char* pathlink; ULONG pathlinklen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH_LINK, (void**)&pathlink, &pathlinklen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path_link"); pathlinku = utf8_to_utf16(string(pathlink, pathlinklen)); } size_t bmnsize = sizeof(btrfs_mknod) - sizeof(WCHAR) + (nameu.length() * sizeof(WCHAR)); bmn = (btrfs_mknod*)malloc(bmnsize); bmn->inode = *inode; if (cmd->cmd == BTRFS_SEND_CMD_MKDIR) bmn->type = BTRFS_TYPE_DIRECTORY; else if (cmd->cmd == BTRFS_SEND_CMD_MKNOD) bmn->type = *mode & S_IFCHR ? BTRFS_TYPE_CHARDEV : BTRFS_TYPE_BLOCKDEV; else if (cmd->cmd == BTRFS_SEND_CMD_MKFIFO) bmn->type = BTRFS_TYPE_FIFO; else if (cmd->cmd == BTRFS_SEND_CMD_MKSOCK) bmn->type = BTRFS_TYPE_SOCKET; else bmn->type = BTRFS_TYPE_FILE; bmn->st_rdev = rdev ? *rdev : 0; bmn->namelen = (uint16_t)(nameu.length() * sizeof(WCHAR)); memcpy(bmn->name, nameu.c_str(), bmn->namelen); Status = NtFsControlFile(dir, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_MKNOD, bmn, (ULONG)bmnsize, nullptr, 0); if (!NT_SUCCESS(Status)) { free(bmn); throw string_error(IDS_RECV_MKNOD_FAILED, Status, format_ntstatus(Status).c_str()); } free(bmn); if (cmd->cmd == BTRFS_SEND_CMD_SYMLINK) { REPARSE_DATA_BUFFER* rdb; btrfs_set_inode_info bsii; size_t rdblen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer[0]) + (2 * pathlinku.length() * sizeof(WCHAR)); if (rdblen >= 0x10000) throw string_error(IDS_RECV_PATH_TOO_LONG, funcname); rdb = (REPARSE_DATA_BUFFER*)malloc(rdblen); rdb->ReparseTag = IO_REPARSE_TAG_SYMLINK; rdb->ReparseDataLength = (uint16_t)(rdblen - offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer)); rdb->Reserved = 0; rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0; rdb->SymbolicLinkReparseBuffer.SubstituteNameLength = (uint16_t)(pathlinku.length() * sizeof(WCHAR)); rdb->SymbolicLinkReparseBuffer.PrintNameOffset = (uint16_t)(pathlinku.length() * sizeof(WCHAR)); rdb->SymbolicLinkReparseBuffer.PrintNameLength = (uint16_t)(pathlinku.length() * sizeof(WCHAR)); rdb->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE; memcpy(rdb->SymbolicLinkReparseBuffer.PathBuffer, pathlinku.c_str(), rdb->SymbolicLinkReparseBuffer.SubstituteNameLength); memcpy(rdb->SymbolicLinkReparseBuffer.PathBuffer + (rdb->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)), pathlinku.c_str(), rdb->SymbolicLinkReparseBuffer.PrintNameLength); win_handle h = CreateFileW((subvolpath + nameu).c_str(), GENERIC_WRITE | WRITE_DAC, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (h == INVALID_HANDLE_VALUE) { free(rdb); throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, nameu.c_str(), GetLastError(), format_message(GetLastError()).c_str()); } Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_SET_REPARSE_POINT, rdb, (ULONG)rdblen, nullptr, 0); if (!NT_SUCCESS(Status)) { free(rdb); throw string_error(IDS_RECV_SET_REPARSE_POINT_FAILED, Status, format_ntstatus(Status).c_str()); } free(rdb); memset(&bsii, 0, sizeof(btrfs_set_inode_info)); bsii.mode_changed = true; bsii.st_mode = 0777; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0); if (!NT_SUCCESS(Status)) throw string_error(IDS_RECV_SETINODEINFO_FAILED, Status, format_ntstatus(Status).c_str()); } else if (cmd->cmd == BTRFS_SEND_CMD_MKNOD || cmd->cmd == BTRFS_SEND_CMD_MKFIFO || cmd->cmd == BTRFS_SEND_CMD_MKSOCK) { uint64_t* mode; ULONG modelen; if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_MODE, (void**)&mode, &modelen)) { btrfs_set_inode_info bsii; if (modelen < sizeof(uint64_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"mode", modelen, sizeof(uint64_t)); win_handle h = CreateFileW((subvolpath + nameu).c_str(), WRITE_DAC, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (h == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, nameu.c_str(), GetLastError(), format_message(GetLastError()).c_str()); memset(&bsii, 0, sizeof(btrfs_set_inode_info)); bsii.mode_changed = true; bsii.st_mode = (uint32_t)*mode; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0); if (!NT_SUCCESS(Status)) throw string_error(IDS_RECV_SETINODEINFO_FAILED, Status, format_ntstatus(Status).c_str()); } } } void BtrfsRecv::cmd_rename(btrfs_send_command* cmd, uint8_t* data) { wstring pathu, path_tou; { char* path; ULONG path_len; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &path_len)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); pathu = utf8_to_utf16(string(path, path_len)); } { char* path_to; ULONG path_to_len; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH_TO, (void**)&path_to, &path_to_len)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path_to"); path_tou = utf8_to_utf16(string(path_to, path_to_len)); } if (!MoveFileW((subvolpath + pathu).c_str(), (subvolpath + path_tou).c_str())) throw string_error(IDS_RECV_MOVEFILE_FAILED, pathu.c_str(), path_tou.c_str(), GetLastError(), format_message(GetLastError()).c_str()); } void BtrfsRecv::cmd_link(btrfs_send_command* cmd, uint8_t* data) { wstring pathu, path_linku; { char* path; ULONG path_len; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &path_len)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); pathu = utf8_to_utf16(string(path, path_len)); } { char* path_link; ULONG path_link_len; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH_LINK, (void**)&path_link, &path_link_len)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path_link"); path_linku = utf8_to_utf16(string(path_link, path_link_len)); } if (!CreateHardLinkW((subvolpath + pathu).c_str(), (subvolpath + path_linku).c_str(), nullptr)) throw string_error(IDS_RECV_CREATEHARDLINK_FAILED, pathu.c_str(), path_linku.c_str(), GetLastError(), format_message(GetLastError()).c_str()); } void BtrfsRecv::cmd_unlink(btrfs_send_command* cmd, uint8_t* data) { wstring pathu; ULONG att; { char* path; ULONG pathlen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); pathu = utf8_to_utf16(string(path, pathlen)); } att = GetFileAttributesW((subvolpath + pathu).c_str()); if (att == INVALID_FILE_ATTRIBUTES) throw string_error(IDS_RECV_GETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); if (att & FILE_ATTRIBUTE_READONLY) { if (!SetFileAttributesW((subvolpath + pathu).c_str(), att & ~FILE_ATTRIBUTE_READONLY)) throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } if (!DeleteFileW((subvolpath + pathu).c_str())) throw string_error(IDS_RECV_DELETEFILE_FAILED, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str()); } void BtrfsRecv::cmd_rmdir(btrfs_send_command* cmd, uint8_t* data) { wstring pathu; ULONG att; { char* path; ULONG pathlen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); pathu = utf8_to_utf16(string(path, pathlen)); } att = GetFileAttributesW((subvolpath + pathu).c_str()); if (att == INVALID_FILE_ATTRIBUTES) throw string_error(IDS_RECV_GETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); if (att & FILE_ATTRIBUTE_READONLY) { if (!SetFileAttributesW((subvolpath + pathu).c_str(), att & ~FILE_ATTRIBUTE_READONLY)) throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } if (!RemoveDirectoryW((subvolpath + pathu).c_str())) throw string_error(IDS_RECV_REMOVEDIRECTORY_FAILED, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str()); } void BtrfsRecv::cmd_setxattr(btrfs_send_command* cmd, uint8_t* data) { string xattrname; uint8_t* xattrdata; ULONG xattrdatalen; wstring pathu; { char* path; ULONG pathlen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); pathu = utf8_to_utf16(string(path, pathlen)); } { char* xattrnamebuf; ULONG xattrnamelen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_XATTR_NAME, (void**)&xattrnamebuf, &xattrnamelen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"xattr_name"); xattrname = string(xattrnamebuf, xattrnamelen); } if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_XATTR_DATA, (void**)&xattrdata, &xattrdatalen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"xattr_data"); if (xattrname.length() > XATTR_USER.length() && xattrname.substr(0, XATTR_USER.length()) == XATTR_USER && xattrname != EA_DOSATTRIB && xattrname != EA_EA && xattrname != EA_REPARSE) { ULONG att; auto streamname = utf8_to_utf16(xattrname); att = GetFileAttributesW((subvolpath + pathu).c_str()); if (att == INVALID_FILE_ATTRIBUTES) throw string_error(IDS_RECV_GETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); if (att & FILE_ATTRIBUTE_READONLY) { if (!SetFileAttributesW((subvolpath + pathu).c_str(), att & ~FILE_ATTRIBUTE_READONLY)) throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } streamname = streamname.substr(XATTR_USER.length()); win_handle h = CreateFileW((subvolpath + pathu + L":" + streamname).c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_FLAG_POSIX_SEMANTICS, nullptr); if (h == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_CREATE_FILE, (pathu + L":" + streamname).c_str(), GetLastError(), format_message(GetLastError()).c_str()); if (xattrdatalen > 0) { if (!WriteFile(h, xattrdata, xattrdatalen, nullptr, nullptr)) throw string_error(IDS_RECV_WRITEFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } if (att & FILE_ATTRIBUTE_READONLY) { if (!SetFileAttributesW((subvolpath + pathu).c_str(), att)) throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } } else { IO_STATUS_BLOCK iosb; NTSTATUS Status; ULONG perms = FILE_WRITE_ATTRIBUTES; btrfs_set_xattr* bsxa; if (xattrname == EA_NTACL) perms |= WRITE_DAC | WRITE_OWNER; else if (xattrname == EA_EA) perms |= FILE_WRITE_EA; win_handle h = CreateFileW((subvolpath + pathu).c_str(), perms, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS | FILE_OPEN_REPARSE_POINT, nullptr); if (h == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str()); size_t bsxalen = offsetof(btrfs_set_xattr, data[0]) + xattrname.length() + xattrdatalen; bsxa = (btrfs_set_xattr*)malloc(bsxalen); if (!bsxa) throw string_error(IDS_OUT_OF_MEMORY); bsxa->namelen = (uint16_t)xattrname.length(); bsxa->valuelen = (uint16_t)xattrdatalen; memcpy(bsxa->data, xattrname.c_str(), xattrname.length()); memcpy(&bsxa->data[xattrname.length()], xattrdata, xattrdatalen); Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, bsxa, (ULONG)bsxalen, nullptr, 0); if (!NT_SUCCESS(Status)) { free(bsxa); throw string_error(IDS_RECV_SETXATTR_FAILED, Status, format_ntstatus(Status).c_str()); } free(bsxa); } } void BtrfsRecv::cmd_removexattr(btrfs_send_command* cmd, uint8_t* data) { wstring pathu; string xattrname; { char* path; ULONG pathlen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); pathu = utf8_to_utf16(string(path, pathlen)); } { char* xattrnamebuf; ULONG xattrnamelen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_XATTR_NAME, (void**)&xattrnamebuf, &xattrnamelen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"xattr_name"); xattrname = string(xattrnamebuf, xattrnamelen); } if (xattrname.length() > XATTR_USER.length() && xattrname.substr(0, XATTR_USER.length()) == XATTR_USER && xattrname != EA_DOSATTRIB && xattrname != EA_EA) { // deleting stream ULONG att; auto streamname = utf8_to_utf16(xattrname); streamname = streamname.substr(XATTR_USER.length()); att = GetFileAttributesW((subvolpath + pathu).c_str()); if (att == INVALID_FILE_ATTRIBUTES) throw string_error(IDS_RECV_GETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); if (att & FILE_ATTRIBUTE_READONLY) { if (!SetFileAttributesW((subvolpath + pathu).c_str(), att & ~FILE_ATTRIBUTE_READONLY)) throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } if (!DeleteFileW((subvolpath + pathu + L":" + streamname).c_str())) throw string_error(IDS_RECV_DELETEFILE_FAILED, (pathu + L":" + streamname).c_str(), GetLastError(), format_message(GetLastError()).c_str()); if (att & FILE_ATTRIBUTE_READONLY) { if (!SetFileAttributesW((subvolpath + pathu).c_str(), att)) throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } } else { IO_STATUS_BLOCK iosb; NTSTATUS Status; ULONG perms = FILE_WRITE_ATTRIBUTES; btrfs_set_xattr* bsxa; if (xattrname == EA_NTACL) perms |= WRITE_DAC | WRITE_OWNER; else if (xattrname == EA_EA) perms |= FILE_WRITE_EA; win_handle h = CreateFileW((subvolpath + pathu).c_str(), perms, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS | FILE_OPEN_REPARSE_POINT, nullptr); if (h == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str()); size_t bsxalen = offsetof(btrfs_set_xattr, data[0]) + xattrname.length(); bsxa = (btrfs_set_xattr*)malloc(bsxalen); if (!bsxa) throw string_error(IDS_OUT_OF_MEMORY); bsxa->namelen = (uint16_t)(xattrname.length()); bsxa->valuelen = 0; memcpy(bsxa->data, xattrname.c_str(), xattrname.length()); Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, bsxa, (ULONG)bsxalen, nullptr, 0); if (!NT_SUCCESS(Status)) { free(bsxa); throw string_error(IDS_RECV_SETXATTR_FAILED, Status, format_ntstatus(Status).c_str()); } free(bsxa); } } void BtrfsRecv::cmd_write(btrfs_send_command* cmd, uint8_t* data) { uint64_t* offset; uint8_t* writedata; ULONG offsetlen, datalen; wstring pathu; HANDLE h; LARGE_INTEGER offli; NTSTATUS Status; IO_STATUS_BLOCK iosb; { char* path; ULONG pathlen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); pathu = utf8_to_utf16(string(path, pathlen)); } if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_OFFSET, (void**)&offset, &offsetlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"offset"); if (offsetlen < sizeof(uint64_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"offset", offsetlen, sizeof(uint64_t)); if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_DATA, (void**)&writedata, &datalen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"data"); if (lastwritepath != pathu) { FILE_BASIC_INFO fbi; if (lastwriteatt & FILE_ATTRIBUTE_READONLY) { if (!SetFileAttributesW((subvolpath + lastwritepath).c_str(), lastwriteatt)) throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } CloseHandle(lastwritefile); lastwriteatt = GetFileAttributesW((subvolpath + pathu).c_str()); if (lastwriteatt == INVALID_FILE_ATTRIBUTES) throw string_error(IDS_RECV_GETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); if (lastwriteatt & FILE_ATTRIBUTE_READONLY) { if (!SetFileAttributesW((subvolpath + pathu).c_str(), lastwriteatt & ~FILE_ATTRIBUTE_READONLY)) throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } h = CreateFileW((subvolpath + pathu).c_str(), FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (h == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str()); lastwritepath = pathu; lastwritefile = h; memset(&fbi, 0, sizeof(FILE_BASIC_INFO)); fbi.LastWriteTime.QuadPart = -1; Status = NtSetInformationFile(h, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } else h = lastwritefile; offli.QuadPart = *offset; if (SetFilePointer(h, offli.LowPart, &offli.HighPart, FILE_BEGIN) == INVALID_SET_FILE_POINTER) throw string_error(IDS_RECV_SETFILEPOINTER_FAILED, GetLastError(), format_message(GetLastError()).c_str()); if (!WriteFile(h, writedata, datalen, nullptr, nullptr)) throw string_error(IDS_RECV_WRITEFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } void BtrfsRecv::cmd_clone(btrfs_send_command* cmd, uint8_t* data) { uint64_t *offset, *cloneoffset, *clonetransid, *clonelen; BTRFS_UUID* cloneuuid; ULONG i, offsetlen, cloneoffsetlen, cloneuuidlen, clonetransidlen, clonelenlen; wstring pathu, clonepathu, clonepar; btrfs_find_subvol bfs; NTSTATUS Status; IO_STATUS_BLOCK iosb; WCHAR cloneparw[MAX_PATH]; DUPLICATE_EXTENTS_DATA ded; LARGE_INTEGER filesize; bool found = false; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_OFFSET, (void**)&offset, &offsetlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"offset"); if (offsetlen < sizeof(uint64_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"offset", offsetlen, sizeof(uint64_t)); if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_LENGTH, (void**)&clonelen, &clonelenlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"clone_len"); if (clonelenlen < sizeof(uint64_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"clone_len", clonelenlen, sizeof(uint64_t)); { char* path; ULONG pathlen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); pathu = utf8_to_utf16(string(path, pathlen)); } if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_UUID, (void**)&cloneuuid, &cloneuuidlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"clone_uuid"); if (cloneuuidlen < sizeof(BTRFS_UUID)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"clone_uuid", cloneuuidlen, sizeof(BTRFS_UUID)); if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_CTRANSID, (void**)&clonetransid, &clonetransidlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"clone_ctransid"); if (clonetransidlen < sizeof(uint64_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"clone_ctransid", clonetransidlen, sizeof(uint64_t)); { char* clonepath; ULONG clonepathlen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_PATH, (void**)&clonepath, &clonepathlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"clone_path"); clonepathu = utf8_to_utf16(string(clonepath, clonepathlen)); } if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_OFFSET, (void**)&cloneoffset, &cloneoffsetlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"clone_offset"); if (cloneoffsetlen < sizeof(uint64_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"clone_offset", cloneoffsetlen, sizeof(uint64_t)); for (i = 0; i < cache.size(); i++) { if (!memcmp(cloneuuid, &cache[i].uuid, sizeof(BTRFS_UUID)) && *clonetransid == cache[i].transid) { clonepar = cache[i].path; found = true; break; } } if (!found) { WCHAR volpathw[MAX_PATH]; bfs.uuid = *cloneuuid; bfs.ctransid = *clonetransid; Status = NtFsControlFile(dir, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_FIND_SUBVOL, &bfs, sizeof(btrfs_find_subvol), cloneparw, sizeof(cloneparw)); if (Status == STATUS_NOT_FOUND) throw string_error(IDS_RECV_CANT_FIND_CLONE_SUBVOL); else if (!NT_SUCCESS(Status)) throw string_error(IDS_RECV_FIND_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str()); if (!GetVolumePathNameW(dirpath.c_str(), volpathw, (sizeof(volpathw) / sizeof(WCHAR)) - 1)) throw string_error(IDS_RECV_GETVOLUMEPATHNAME_FAILED, GetLastError(), format_message(GetLastError()).c_str()); clonepar = volpathw; if (clonepar.substr(clonepar.length() - 1) == L"\\") clonepar = clonepar.substr(0, clonepar.length() - 1); clonepar += cloneparw; clonepar += L"\\"; add_cache_entry(cloneuuid, *clonetransid, clonepar); } { win_handle src = CreateFileW((clonepar + clonepathu).c_str(), FILE_READ_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (src == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, (clonepar + clonepathu).c_str(), GetLastError(), format_message(GetLastError()).c_str()); win_handle dest = CreateFileW((subvolpath + pathu).c_str(), FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (dest == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str()); if (!GetFileSizeEx(dest, &filesize)) throw string_error(IDS_RECV_GETFILESIZEEX_FAILED, GetLastError(), format_message(GetLastError()).c_str()); if ((uint64_t)filesize.QuadPart < *offset + *clonelen) { LARGE_INTEGER sizeli; sizeli.QuadPart = *offset + *clonelen; if (SetFilePointer(dest, sizeli.LowPart, &sizeli.HighPart, FILE_BEGIN) == INVALID_SET_FILE_POINTER) throw string_error(IDS_RECV_SETFILEPOINTER_FAILED, GetLastError(), format_message(GetLastError()).c_str()); if (!SetEndOfFile(dest)) throw string_error(IDS_RECV_SETENDOFFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } ded.FileHandle = src; ded.SourceFileOffset.QuadPart = *cloneoffset; ded.TargetFileOffset.QuadPart = *offset; ded.ByteCount.QuadPart = *clonelen; Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &ded, sizeof(DUPLICATE_EXTENTS_DATA), nullptr, 0); if (!NT_SUCCESS(Status)) throw string_error(IDS_RECV_DUPLICATE_EXTENTS_FAILED, Status, format_ntstatus(Status).c_str()); } } void BtrfsRecv::cmd_truncate(btrfs_send_command* cmd, uint8_t* data) { uint64_t* size; ULONG sizelen; wstring pathu; LARGE_INTEGER sizeli; DWORD att; { char* path; ULONG pathlen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); pathu = utf8_to_utf16(string(path, pathlen)); } if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_SIZE, (void**)&size, &sizelen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"size"); if (sizelen < sizeof(uint64_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"size", sizelen, sizeof(uint64_t)); att = GetFileAttributesW((subvolpath + pathu).c_str()); if (att == INVALID_FILE_ATTRIBUTES) throw string_error(IDS_RECV_GETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); if (att & FILE_ATTRIBUTE_READONLY) { if (!SetFileAttributesW((subvolpath + pathu).c_str(), att & ~FILE_ATTRIBUTE_READONLY)) throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } { win_handle h = CreateFileW((subvolpath + pathu).c_str(), FILE_WRITE_DATA, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (h == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str()); sizeli.QuadPart = *size; if (SetFilePointer(h, sizeli.LowPart, &sizeli.HighPart, FILE_BEGIN) == INVALID_SET_FILE_POINTER) throw string_error(IDS_RECV_SETFILEPOINTER_FAILED, GetLastError(), format_message(GetLastError()).c_str()); if (!SetEndOfFile(h)) throw string_error(IDS_RECV_SETENDOFFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } if (att & FILE_ATTRIBUTE_READONLY) { if (!SetFileAttributesW((subvolpath + pathu).c_str(), att)) throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } } void BtrfsRecv::cmd_chmod(btrfs_send_command* cmd, uint8_t* data) { win_handle h; uint32_t* mode; ULONG modelen; wstring pathu; btrfs_set_inode_info bsii; NTSTATUS Status; IO_STATUS_BLOCK iosb; { char* path; ULONG pathlen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); pathu = utf8_to_utf16(string(path, pathlen)); } if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_MODE, (void**)&mode, &modelen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"mode"); if (modelen < sizeof(uint32_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"mode", modelen, sizeof(uint32_t)); h = CreateFileW((subvolpath + pathu).c_str(), WRITE_DAC, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (h == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str()); memset(&bsii, 0, sizeof(btrfs_set_inode_info)); bsii.mode_changed = true; bsii.st_mode = *mode; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0); if (!NT_SUCCESS(Status)) throw string_error(IDS_RECV_SETINODEINFO_FAILED, Status, format_ntstatus(Status).c_str()); } void BtrfsRecv::cmd_chown(btrfs_send_command* cmd, uint8_t* data) { win_handle h; uint32_t *uid, *gid; ULONG uidlen, gidlen; wstring pathu; btrfs_set_inode_info bsii; { char* path; ULONG pathlen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); pathu = utf8_to_utf16(string(path, pathlen)); } h = CreateFileW((subvolpath + pathu).c_str(), FILE_WRITE_ATTRIBUTES | WRITE_OWNER | WRITE_DAC, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (h == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str()); memset(&bsii, 0, sizeof(btrfs_set_inode_info)); if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_UID, (void**)&uid, &uidlen)) { if (uidlen < sizeof(uint32_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"uid", uidlen, sizeof(uint32_t)); bsii.uid_changed = true; bsii.st_uid = *uid; } if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_GID, (void**)&gid, &gidlen)) { if (gidlen < sizeof(uint32_t)) throw string_error(IDS_RECV_SHORT_PARAM, funcname, L"gid", gidlen, sizeof(uint32_t)); bsii.gid_changed = true; bsii.st_gid = *gid; } if (bsii.uid_changed || bsii.gid_changed) { NTSTATUS Status; IO_STATUS_BLOCK iosb; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0); if (!NT_SUCCESS(Status)) throw string_error(IDS_RECV_SETINODEINFO_FAILED, Status, format_ntstatus(Status).c_str()); } } static __inline uint64_t unix_time_to_win(BTRFS_TIME* t) { return (t->seconds * 10000000) + (t->nanoseconds / 100) + 116444736000000000; } void BtrfsRecv::cmd_utimes(btrfs_send_command* cmd, uint8_t* data) { wstring pathu; win_handle h; FILE_BASIC_INFO fbi; BTRFS_TIME* time; ULONG timelen; IO_STATUS_BLOCK iosb; NTSTATUS Status; { char* path; ULONG pathlen; if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen)) throw string_error(IDS_RECV_MISSING_PARAM, funcname, L"path"); pathu = utf8_to_utf16(string(path, pathlen)); } h = CreateFileW((subvolpath + pathu).c_str(), FILE_WRITE_ATTRIBUTES, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (h == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str()); memset(&fbi, 0, sizeof(FILE_BASIC_INFO)); if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_OTIME, (void**)&time, &timelen) && timelen >= sizeof(BTRFS_TIME)) fbi.CreationTime.QuadPart = unix_time_to_win(time); if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_ATIME, (void**)&time, &timelen) && timelen >= sizeof(BTRFS_TIME)) fbi.LastAccessTime.QuadPart = unix_time_to_win(time); if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_MTIME, (void**)&time, &timelen) && timelen >= sizeof(BTRFS_TIME)) fbi.LastWriteTime.QuadPart = unix_time_to_win(time); if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_CTIME, (void**)&time, &timelen) && timelen >= sizeof(BTRFS_TIME)) fbi.ChangeTime.QuadPart = unix_time_to_win(time); Status = NtSetInformationFile(h, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } static void delete_directory(const wstring& dir) { WIN32_FIND_DATAW fff; fff_handle h = FindFirstFileW((dir + L"*").c_str(), &fff); if (h == INVALID_HANDLE_VALUE) return; do { wstring file; file = fff.cFileName; if (file != L"." && file != L"..") { if (fff.dwFileAttributes & FILE_ATTRIBUTE_READONLY) SetFileAttributesW((dir + file).c_str(), fff.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY); if (fff.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (!(fff.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) delete_directory(dir + file + L"\\"); else RemoveDirectoryW((dir + file).c_str()); } else DeleteFileW((dir + file).c_str()); } } while (FindNextFileW(h, &fff)); RemoveDirectoryW(dir.c_str()); } static bool check_csum(btrfs_send_command* cmd, uint8_t* data) { uint32_t crc32 = cmd->csum, calc; cmd->csum = 0; calc = calc_crc32c(0, (uint8_t*)cmd, sizeof(btrfs_send_command)); if (cmd->length > 0) calc = calc_crc32c(calc, data, cmd->length); return calc == crc32 ? true : false; } void BtrfsRecv::do_recv(const win_handle& f, uint64_t* pos, uint64_t size, const win_handle& parent) { try { btrfs_send_header header; bool ended = false; if (!ReadFile(f, &header, sizeof(btrfs_send_header), nullptr, nullptr)) throw string_error(IDS_RECV_READFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str()); *pos += sizeof(btrfs_send_header); if (memcmp(header.magic, BTRFS_SEND_MAGIC, sizeof(header.magic))) throw string_error(IDS_RECV_NOT_A_SEND_STREAM); if (header.version > 1) throw string_error(IDS_RECV_UNSUPPORTED_VERSION, header.version); SendMessageW(GetDlgItem(hwnd, IDC_RECV_PROGRESS), PBM_SETRANGE32, 0, (LPARAM)65536); lastwritefile = INVALID_HANDLE_VALUE; lastwritepath = L""; lastwriteatt = 0; while (true) { btrfs_send_command cmd; uint8_t* data = nullptr; ULONG progress; if (cancelling) break; progress = (ULONG)((float)*pos * 65536.0f / (float)size); SendMessageW(GetDlgItem(hwnd, IDC_RECV_PROGRESS), PBM_SETPOS, progress, 0); if (!ReadFile(f, &cmd, sizeof(btrfs_send_command), nullptr, nullptr)) { if (GetLastError() != ERROR_HANDLE_EOF) throw string_error(IDS_RECV_READFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str()); break; } *pos += sizeof(btrfs_send_command); if (cmd.length > 0) { if (*pos + cmd.length > size) throw string_error(IDS_RECV_FILE_TRUNCATED); data = (uint8_t*)malloc(cmd.length); if (!data) throw string_error(IDS_OUT_OF_MEMORY); } try { if (data) { if (!ReadFile(f, data, cmd.length, nullptr, nullptr)) throw string_error(IDS_RECV_READFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str()); *pos += cmd.length; } if (!check_csum(&cmd, data)) throw string_error(IDS_RECV_CSUM_ERROR); if (cmd.cmd == BTRFS_SEND_CMD_END) { ended = true; break; } if (lastwritefile != INVALID_HANDLE_VALUE && cmd.cmd != BTRFS_SEND_CMD_WRITE) { if (lastwriteatt & FILE_ATTRIBUTE_READONLY) { if (!SetFileAttributesW((subvolpath + lastwritepath).c_str(), lastwriteatt)) throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } CloseHandle(lastwritefile); lastwritefile = INVALID_HANDLE_VALUE; lastwritepath = L""; lastwriteatt = 0; } switch (cmd.cmd) { case BTRFS_SEND_CMD_SUBVOL: cmd_subvol(&cmd, data, parent); break; case BTRFS_SEND_CMD_SNAPSHOT: cmd_snapshot(&cmd, data, parent); break; case BTRFS_SEND_CMD_MKFILE: case BTRFS_SEND_CMD_MKDIR: case BTRFS_SEND_CMD_MKNOD: case BTRFS_SEND_CMD_MKFIFO: case BTRFS_SEND_CMD_MKSOCK: case BTRFS_SEND_CMD_SYMLINK: cmd_mkfile(&cmd, data); break; case BTRFS_SEND_CMD_RENAME: cmd_rename(&cmd, data); break; case BTRFS_SEND_CMD_LINK: cmd_link(&cmd, data); break; case BTRFS_SEND_CMD_UNLINK: cmd_unlink(&cmd, data); break; case BTRFS_SEND_CMD_RMDIR: cmd_rmdir(&cmd, data); break; case BTRFS_SEND_CMD_SET_XATTR: cmd_setxattr(&cmd, data); break; case BTRFS_SEND_CMD_REMOVE_XATTR: cmd_removexattr(&cmd, data); break; case BTRFS_SEND_CMD_WRITE: cmd_write(&cmd, data); break; case BTRFS_SEND_CMD_CLONE: cmd_clone(&cmd, data); break; case BTRFS_SEND_CMD_TRUNCATE: cmd_truncate(&cmd, data); break; case BTRFS_SEND_CMD_CHMOD: cmd_chmod(&cmd, data); break; case BTRFS_SEND_CMD_CHOWN: cmd_chown(&cmd, data); break; case BTRFS_SEND_CMD_UTIMES: cmd_utimes(&cmd, data); break; case BTRFS_SEND_CMD_UPDATE_EXTENT: // does nothing break; default: throw string_error(IDS_RECV_UNKNOWN_COMMAND, cmd.cmd); } } catch (...) { if (data) free(data); throw; } if (data) free(data); } if (lastwritefile != INVALID_HANDLE_VALUE) { if (lastwriteatt & FILE_ATTRIBUTE_READONLY) { if (!SetFileAttributesW((subvolpath + lastwritepath).c_str(), lastwriteatt)) throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } CloseHandle(lastwritefile); } if (!ended && !cancelling) throw string_error(IDS_RECV_FILE_TRUNCATED); if (!cancelling) { NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_received_subvol brs; brs.generation = stransid; brs.uuid = subvol_uuid; Status = NtFsControlFile(dir, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RECEIVED_SUBVOL, &brs, sizeof(btrfs_received_subvol), nullptr, 0); if (!NT_SUCCESS(Status)) throw string_error(IDS_RECV_RECEIVED_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str()); } CloseHandle(dir); if (master != INVALID_HANDLE_VALUE) CloseHandle(master); } catch (...) { if (subvolpath != L"") { ULONG attrib; attrib = GetFileAttributesW(subvolpath.c_str()); attrib &= ~FILE_ATTRIBUTE_READONLY; if (SetFileAttributesW(subvolpath.c_str(), attrib)) delete_directory(subvolpath); } throw; } } #if defined(_X86_) || defined(_AMD64_) static void check_cpu() { bool have_sse42 = false; #ifndef _MSC_VER { uint32_t eax, ebx, ecx, edx; __cpuid(1, eax, ebx, ecx, edx); if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) have_sse42 = ecx & bit_SSE4_2; } #else { int cpu_info[4]; __cpuid(cpu_info, 1); have_sse42 = (unsigned int)cpu_info[2] & (1 << 20); } #endif if (have_sse42) calc_crc32c = calc_crc32c_hw; } #endif DWORD BtrfsRecv::recv_thread() { LARGE_INTEGER size; uint64_t pos = 0; bool b = true; running = true; try { win_handle f = CreateFileW(streamfile.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr); if (f == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, streamfile.c_str(), GetLastError(), format_message(GetLastError()).c_str()); if (!GetFileSizeEx(f, &size)) throw string_error(IDS_RECV_GETFILESIZEEX_FAILED, GetLastError(), format_message(GetLastError()).c_str()); { win_handle parent = CreateFileW(dirpath.c_str(), FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr); if (parent == INVALID_HANDLE_VALUE) throw string_error(IDS_RECV_CANT_OPEN_PATH, dirpath.c_str(), GetLastError(), format_message(GetLastError()).c_str()); do { do_recv(f, &pos, size.QuadPart, parent); } while (pos < (uint64_t)size.QuadPart); } } catch (const exception& e) { auto msg = utf8_to_utf16(e.what()); SetDlgItemTextW(hwnd, IDC_RECV_MSG, msg.c_str()); SendMessageW(GetDlgItem(hwnd, IDC_RECV_PROGRESS), PBM_SETSTATE, PBST_ERROR, 0); b = false; } if (b && hwnd) { wstring s; SendMessageW(GetDlgItem(hwnd, IDC_RECV_PROGRESS), PBM_SETPOS, 65536, 0); if (num_received == 1) { load_string(module, IDS_RECV_SUCCESS, s); SetDlgItemTextW(hwnd, IDC_RECV_MSG, s.c_str()); } else { wstring t; load_string(module, IDS_RECV_SUCCESS_PLURAL, s); wstring_sprintf(t, s, num_received); SetDlgItemTextW(hwnd, IDC_RECV_MSG, t.c_str()); } load_string(module, IDS_RECV_BUTTON_OK, s); SetDlgItemTextW(hwnd, IDCANCEL, s.c_str()); } thread = nullptr; running = false; return 0; } static DWORD WINAPI global_recv_thread(LPVOID lpParameter) { BtrfsRecv* br = (BtrfsRecv*)lpParameter; return br->recv_thread(); } INT_PTR CALLBACK BtrfsRecv::RecvProgressDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) { switch (uMsg) { case WM_INITDIALOG: try { this->hwnd = hwndDlg; thread = CreateThread(nullptr, 0, global_recv_thread, this, 0, nullptr); if (!thread) throw string_error(IDS_RECV_CREATETHREAD_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } catch (const exception& e) { auto msg = utf8_to_utf16(e.what()); SetDlgItemTextW(hwnd, IDC_RECV_MSG, msg.c_str()); SendMessageW(GetDlgItem(hwnd, IDC_RECV_PROGRESS), PBM_SETSTATE, PBST_ERROR, 0); } break; case WM_COMMAND: switch (HIWORD(wParam)) { case BN_CLICKED: switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: if (running) { wstring s; cancelling = true; if (!load_string(module, IDS_RECV_CANCELLED, s)) throw last_error(GetLastError()); SetDlgItemTextW(hwnd, IDC_RECV_MSG, s.c_str()); SendMessageW(GetDlgItem(hwnd, IDC_RECV_PROGRESS), PBM_SETPOS, 0, 0); if (!load_string(module, IDS_RECV_BUTTON_OK, s)) throw last_error(GetLastError()); SetDlgItemTextW(hwnd, IDCANCEL, s.c_str()); } else EndDialog(hwndDlg, 1); return true; } break; } break; } return false; } static INT_PTR CALLBACK stub_RecvProgressDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { BtrfsRecv* br; if (uMsg == WM_INITDIALOG) { SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); br = (BtrfsRecv*)lParam; } else { br = (BtrfsRecv*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); } if (br) return br->RecvProgressDlgProc(hwndDlg, uMsg, wParam, lParam); else return false; } void BtrfsRecv::Open(HWND hwnd, const wstring& file, const wstring& path, bool quiet) { streamfile = file; dirpath = path; subvolpath = L""; #if defined(_X86_) || defined(_AMD64_) check_cpu(); #endif if (quiet) recv_thread(); else { if (DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_RECV_PROGRESS), hwnd, stub_RecvProgressDlgProc, (LPARAM)this) <= 0) throw last_error(GetLastError()); } } extern "C" void CALLBACK RecvSubvolGUIW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) { try { OPENFILENAMEW ofn; WCHAR file[MAX_PATH]; win_handle token; TOKEN_PRIVILEGES* tp; LUID luid; ULONG tplen; set_dpi_aware(); if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) throw last_error(GetLastError()); tplen = offsetof(TOKEN_PRIVILEGES, Privileges[0]) + (3 * sizeof(LUID_AND_ATTRIBUTES)); tp = (TOKEN_PRIVILEGES*)malloc(tplen); if (!tp) throw string_error(IDS_OUT_OF_MEMORY); tp->PrivilegeCount = 3; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warray-bounds" if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) { free(tp); throw last_error(GetLastError()); } tp->Privileges[0].Luid = luid; tp->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!LookupPrivilegeValueW(nullptr, L"SeSecurityPrivilege", &luid)) { free(tp); throw last_error(GetLastError()); } tp->Privileges[1].Luid = luid; tp->Privileges[1].Attributes = SE_PRIVILEGE_ENABLED; if (!LookupPrivilegeValueW(nullptr, L"SeRestorePrivilege", &luid)) { free(tp); throw last_error(GetLastError()); } tp->Privileges[2].Luid = luid; tp->Privileges[2].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, tp, tplen, nullptr, nullptr)) { free(tp); throw last_error(GetLastError()); } #pragma clang diagnostic pop file[0] = 0; memset(&ofn, 0, sizeof(OPENFILENAMEW)); ofn.lStructSize = sizeof(OPENFILENAMEW); ofn.hwndOwner = hwnd; ofn.hInstance = module; ofn.lpstrFile = file; ofn.nMaxFile = sizeof(file) / sizeof(WCHAR); ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; if (GetOpenFileNameW(&ofn)) { BtrfsRecv recv; recv.Open(hwnd, file, lpszCmdLine, false); } free(tp); } catch (const exception& e) { error_message(hwnd, e.what()); } } extern "C" void CALLBACK RecvSubvolW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) { try { vector args; command_line_to_args(lpszCmdLine, args); if (args.size() >= 2) { win_handle token; TOKEN_PRIVILEGES* tp; ULONG tplen; LUID luid; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) return; tplen = offsetof(TOKEN_PRIVILEGES, Privileges[0]) + (3 * sizeof(LUID_AND_ATTRIBUTES)); tp = (TOKEN_PRIVILEGES*)malloc(tplen); if (!tp) return; tp->PrivilegeCount = 3; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warray-bounds" if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) { free(tp); return; } tp->Privileges[0].Luid = luid; tp->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!LookupPrivilegeValueW(nullptr, L"SeSecurityPrivilege", &luid)) { free(tp); return; } tp->Privileges[1].Luid = luid; tp->Privileges[1].Attributes = SE_PRIVILEGE_ENABLED; if (!LookupPrivilegeValueW(nullptr, L"SeRestorePrivilege", &luid)) { free(tp); return; } tp->Privileges[2].Luid = luid; tp->Privileges[2].Attributes = SE_PRIVILEGE_ENABLED; #pragma clang diagnostic pop if (!AdjustTokenPrivileges(token, false, tp, tplen, nullptr, nullptr)) { free(tp); return; } free(tp); BtrfsRecv br; br.Open(nullptr, args[0], args[1], true); } } catch (const exception& e) { cerr << "Error: " << e.what() << endl; } } ================================================ FILE: src/shellext/recv.h ================================================ /* Copyright (c) Mark Harmstone 2017 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #pragma once #include #include "../btrfs.h" extern LONG objs_loaded; typedef struct { BTRFS_UUID uuid; uint64_t transid; wstring path; } subvol_cache; class BtrfsRecv { public: BtrfsRecv() { thread = nullptr; master = INVALID_HANDLE_VALUE; dir = INVALID_HANDLE_VALUE; running = false; cancelling = false; stransid = 0; num_received = 0; hwnd = nullptr; cache.clear(); } virtual ~BtrfsRecv() { cache.clear(); } void Open(HWND hwnd, const wstring& file, const wstring& path, bool quiet); DWORD recv_thread(); INT_PTR CALLBACK RecvProgressDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); private: void cmd_subvol(btrfs_send_command* cmd, uint8_t* data, const win_handle& parent); void cmd_snapshot(btrfs_send_command* cmd, uint8_t* data, const win_handle& parent); void cmd_mkfile(btrfs_send_command* cmd, uint8_t* data); void cmd_rename(btrfs_send_command* cmd, uint8_t* data); void cmd_link(btrfs_send_command* cmd, uint8_t* data); void cmd_unlink(btrfs_send_command* cmd, uint8_t* data); void cmd_rmdir(btrfs_send_command* cmd, uint8_t* data); void cmd_setxattr(btrfs_send_command* cmd, uint8_t* data); void cmd_removexattr(btrfs_send_command* cmd, uint8_t* data); void cmd_write(btrfs_send_command* cmd, uint8_t* data); void cmd_clone(btrfs_send_command* cmd, uint8_t* data); void cmd_truncate(btrfs_send_command* cmd, uint8_t* data); void cmd_chmod(btrfs_send_command* cmd, uint8_t* data); void cmd_chown(btrfs_send_command* cmd, uint8_t* data); void cmd_utimes(btrfs_send_command* cmd, uint8_t* data); void add_cache_entry(BTRFS_UUID* uuid, uint64_t transid, const wstring& path); bool find_tlv(uint8_t* data, ULONG datalen, uint16_t type, void** value, ULONG* len); void do_recv(const win_handle& f, uint64_t* pos, uint64_t size, const win_handle& parent); HANDLE dir, master, thread, lastwritefile; HWND hwnd; wstring streamfile, dirpath, subvolpath, lastwritepath; DWORD lastwriteatt; ULONG num_received; uint64_t stransid; BTRFS_UUID subvol_uuid; bool running, cancelling; vector cache; }; ================================================ FILE: src/shellext/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by shellbtrfs.rc // #define IDI_ICON1 101 #define IDS_NEW_SUBVOL_HELP_TEXT 102 #define IDS_NEW_SUBVOL 103 #define IDD_SIZE_DETAILS 103 #define IDS_NEW_SUBVOL_FILENAME 104 #define IDS_CREATE_SNAPSHOT 105 #define IDD_VOL_PROP_SHEET 105 #define IDS_CREATE_SNAPSHOT_HELP_TEXT 106 #define IDD_VOL_USAGE 106 #define IDS_SNAPSHOT_FILENAME 107 #define IDD_PROP_SHEET 107 #define IDS_PROP_SHEET_TITLE 108 #define IDD_BALANCE_OPTIONS 108 #define IDS_INODE_FILE 109 #define IDD_BALANCE 109 #define IDS_INODE_DIR 110 #define IDD_DEVICES 110 #define IDS_INODE_CHAR 111 #define IDS_INODE_BLOCK 112 #define IDS_INODE_FIFO 113 #define IDS_INODE_SOCKET 114 #define IDS_INODE_SYMLINK 115 #define IDS_INODE_UNKNOWN 116 #define IDS_SET_INODE_INFO_ERROR 117 #define IDS_CANNOT_FIND_DEVICE 117 #define IDS_SIZE_BYTE 118 #define IDS_SIZE_BYTES 119 #define IDS_SIZE_KB 120 #define IDS_SIZE_MB 121 #define IDS_SIZE_GB 122 #define IDS_SIZE_TB 123 #define IDS_SIZE_PB 124 #define IDS_SIZE_EB 125 #define IDS_VARIOUS 126 #define IDS_INODE_CHAR_SIMPLE 127 #define IDS_INODE_BLOCK_SIMPLE 128 #define IDS_VOL_PROP_SHEET_TITLE 129 #define IDS_SIZE_LARGE 130 #define IDS_SINGLE 131 #define IDS_DUP 132 #define IDS_RAID0 133 #define IDS_RAID1 134 #define IDS_RAID10 135 #define IDS_RAID5 136 #define IDS_RAID6 137 #define IDS_USAGE_DATA 138 #define IDS_USAGE_MIXED 139 #define IDS_USAGE_METADATA 140 #define IDS_USAGE_SYSTEM 141 #define IDS_USAGE_UNALLOC 142 #define IDS_UNKNOWN_DEVICE 143 #define IDS_USAGE_DEV_SIZE 144 #define IDS_USAGE_DEV_ALLOC 145 #define IDS_USAGE_DEV_UNALLOC 146 #define IDS_USAGE_DATA_RATIO 147 #define IDS_USAGE_METADATA_RATIO 148 #define IDS_NO_BALANCE 149 #define IDS_SINGLE2 150 #define IDS_DEVID_LIST 151 #define IDS_BALANCE_RUNNING 152 #define IDS_DRANGE_END_BEFORE_START 153 #define IDS_VRANGE_END_BEFORE_START 154 #define IDS_LIMIT_END_BEFORE_START 155 #define IDS_STRIPES_END_BEFORE_START 156 #define IDS_USAGE_END_BEFORE_START 157 #define IDS_ERROR 158 #define IDS_BALANCE_COMPLETE 159 #define IDS_BALANCE_PAUSED 160 #define IDS_BALANCE_CANCELLED 161 #define IDS_DEVLIST_ID 162 #define IDS_DEVLIST_DESC 163 #define IDS_DEVLIST_READONLY 164 #define IDS_DEVLIST_SIZE 165 #define IDS_DEVLIST_READONLY_YES 166 #define IDS_DEVLIST_READONLY_NO 167 #define IDD_DEVICE_ADD 167 #define IDS_DEVLIST_ALLOC 168 #define IDD_SCRUB 168 #define IDS_DEVLIST_ALLOC_PC 169 #define IDD_DEVICE_STATS 169 #define IDS_BALANCE_RUNNING_REMOVAL 170 #define IDD_RECV_PROGRESS 170 #define IDS_BALANCE_PAUSED_REMOVAL 171 #define IDD_SEND_SUBVOL 171 #define IDS_BALANCE_CANCELLED_REMOVAL 172 #define IDD_RESIZE 172 #define IDS_BALANCE_COMPLETE_REMOVAL 173 #define IDS_PARTITION 174 #define IDS_WHOLE_DISK 175 #define IDS_CANNOT_REMOVE_RAID 176 #define IDD_DRIVE_LETTER 176 #define IDS_REMOVE_DEVICE_CONFIRMATION 177 #define IDS_CONFIRMATION_TITLE 178 #define IDS_ADD_DEVICE_CONFIRMATION 179 #define IDS_ADD_DEVICE_CONFIRMATION_FS 180 #define IDS_BALANCE_FAILED 181 #define IDS_BALANCE_FAILED_REMOVAL 182 #define IDS_DISK_NUM 183 #define IDS_DISK_PART_NUM 184 #define IDS_NO_SCRUB 185 #define IDS_SCRUB_RUNNING 186 #define IDS_SCRUB_FINISHED 187 #define IDS_SCRUB_PAUSED 188 #define IDS_SCRUB_MSG_STARTED 189 #define IDS_SCRUB_MSG_RECOVERABLE_DATA 190 #define IDS_SCRUB_MSG_RECOVERABLE_METADATA 191 #define IDS_SCRUB_MSG_UNRECOVERABLE_DATA 192 #define IDS_SCRUB_MSG_UNRECOVERABLE_DATA_SUBVOL 193 #define IDS_SCRUB_MSG_UNRECOVERABLE_METADATA 194 #define IDS_SCRUB_MSG_UNRECOVERABLE_METADATA_FIRSTITEM 195 #define IDS_SCRUB_MSG_FINISHED 196 #define IDS_SCRUB_MSG_SUMMARY 197 #define IDS_BALANCE_SCRUB_RUNNING 198 #define IDS_SCRUB_BALANCE_RUNNING 199 #define IDS_SCRUB_MSG_SUMMARY_ERRORS_RECOVERABLE 200 #define IDS_SCRUB_MSG_SUMMARY_ERRORS_UNRECOVERABLE 201 #define IDS_SCRUB_FAILED 202 #define IDS_LOCK_FAILED 203 #define IDS_SCRUB_MSG_RECOVERABLE_PARITY 204 #define IDS_COMPRESS_ANY 205 #define IDS_COMPRESS_ZLIB 206 #define IDS_COMPRESS_LZO 207 #define IDS_STANDALONE_PROPSHEET_TITLE 208 #define IDS_REFLINK_PASTE 209 #define IDS_REFLINK_PASTE_HELP 210 #define IDS_RECV_SUBVOL 211 #define IDS_RECV_SUBVOL_HELP 212 #define IDS_RECV_CANT_OPEN_FILE 213 #define IDS_RECV_READFILE_FAILED 214 #define IDS_OUT_OF_MEMORY 215 #define IDS_RECV_UNKNOWN_COMMAND 216 #define IDS_RECV_CANT_OPEN_PATH 217 #define IDS_RAID1C3 218 #define IDS_RECV_CREATE_SUBVOL_FAILED 219 #define IDS_RECV_MISSING_PARAM 220 #define IDS_RECV_SHORT_PARAM 221 #define IDS_RECV_MKNOD_FAILED 222 #define IDS_RECV_SET_REPARSE_POINT_FAILED 223 #define IDS_RECV_MOVEFILE_FAILED 224 #define IDS_RECV_SETFILEPOINTER_FAILED 225 #define IDS_RECV_WRITEFILE_FAILED 226 #define IDS_RECV_CREATEHARDLINK_FAILED 227 #define IDS_RECV_SETENDOFFILE_FAILED 228 #define IDS_RECV_CANT_CREATE_FILE 229 #define IDS_RAID1C4 230 #define IDS_RECV_SETINODEINFO_FAILED 231 #define IDS_RECV_SUCCESS 232 #define IDS_RECV_BUTTON_OK 233 #define IDS_RECV_SETFILEATTRIBUTES_FAILED 234 #define IDS_RECV_GETFILEATTRIBUTES_FAILED 235 #define IDS_RECV_CSUM_ERROR 236 #define IDS_RECV_NOT_A_SEND_STREAM 237 #define IDS_RECV_UNSUPPORTED_VERSION 238 #define IDS_RECV_SETEAFILE_FAILED 239 #define IDS_RECV_RECEIVED_SUBVOL_FAILED 240 #define IDS_RECV_SETSECURITYOBJECT_FAILED 241 #define IDS_RECV_SETXATTR_FAILED 242 #define IDS_RECV_CREATETHREAD_FAILED 243 #define IDS_RECV_FILE_TRUNCATED 244 #define IDS_RECV_RESERVE_SUBVOL_FAILED 245 #define IDS_RECV_CANCELLED 246 #define IDS_RECV_CANT_FIND_PARENT_SUBVOL 247 #define IDS_RECV_FIND_SUBVOL_FAILED 248 #define IDS_RECV_CREATE_SNAPSHOT_FAILED 249 #define IDS_RECV_GETVOLUMEPATHNAME_FAILED 250 #define IDS_RECV_DELETEFILE_FAILED 251 #define IDS_RECV_REMOVEDIRECTORY_FAILED 252 #define IDS_RECV_CANT_FIND_CLONE_SUBVOL 253 #define IDS_RECV_GETFILESIZEEX_FAILED 254 #define IDS_RECV_DUPLICATE_EXTENTS_FAILED 255 #define IDS_RECV_SUCCESS_PLURAL 256 #define IDS_SEND_SUBVOL 257 #define IDS_SEND_SUBVOL_HELP 258 #define IDS_SEND_CANT_OPEN_FILE 259 #define IDS_SEND_CANT_OPEN_DIR 260 #define IDS_SEND_FSCTL_BTRFS_SEND_SUBVOL_FAILED 261 #define IDS_SEND_FSCTL_BTRFS_READ_SEND_BUFFER_FAILED 262 #define IDS_SEND_SUCCESS 263 #define IDS_SEND_WRITEFILE_FAILED 264 #define IDS_SEND_GET_FILE_INFO_FAILED 265 #define IDS_SEND_NOT_READONLY 266 #define IDS_NOT_SUBVOL 267 #define IDS_GET_FILE_IDS_FAILED 268 #define IDS_SHPARSEDISPLAYNAME_FAILED 269 #define IDS_SHGETPATHFROMIDLIST_FAILED 270 #define IDS_SEND_PARENT_NOT_READONLY 271 #define IDS_SEND_CANCEL 272 #define IDS_SEND_WRITING 273 #define IDS_MISSING 274 #define IDS_RESIZE_SUCCESSFUL 275 #define IDS_BALANCE_RUNNING_SHRINK 276 #define IDS_BALANCE_PAUSED_SHRINK 277 #define IDS_BALANCE_CANCELLED_SHRINK 278 #define IDS_BALANCE_COMPLETE_SHRINK 279 #define IDS_BALANCE_FAILED_SHRINK 280 #define IDS_COMPRESS_ZSTD 281 #define IDS_REGCREATEKEY_FAILED 283 #define IDS_REGSETVALUEEX_FAILED 284 #define IDS_REGCLOSEKEY_FAILED 285 #define IDS_CANT_REFLINK_DIFFERENT_FS 287 #define IDS_INITCOMMONCONTROLSEX_FAILED 288 #define IDS_CANT_OPEN_MOUNTMGR 289 #define IDS_TVM_INSERTITEM_FAILED 290 #define IDS_RECV_PATH_TOO_LONG 291 #define IDD_MAPPINGS 292 #define IDS_MAPPINGS_UID_MAPPINGS 293 #define IDS_MAPPINGS_GID_MAPPINGS 294 #define IDS_MAPPINGS_PRINCIPAL 295 #define IDS_MAPPINGS_UID 296 #define IDS_MAPPINGS_GID 297 #define IDC_UID 1001 #define IDC_GID 1002 #define IDC_USERR 1003 #define IDC_GROUPR 1004 #define IDC_OTHERR 1005 #define IDC_USERW 1006 #define IDC_GROUPW 1007 #define IDC_OTHERW 1008 #define IDC_USERX 1009 #define IDC_GROUPX 1010 #define IDC_OTHERX 1011 #define IDC_NODATACOW 1012 #define IDC_SUBVOL 1013 #define IDC_INODE 1014 #define IDC_TYPE 1015 #define IDC_COMPRESS 1016 #define IDC_SIZE_ON_DISK 1017 #define IDC_GROUP_INFORMATION 1018 #define IDC_SIZE_INLINE 1019 #define IDC_SETUID 1019 #define IDC_SIZE_UNCOMPRESSED 1020 #define IDC_USAGE_BOX 1020 #define IDC_SETGID 1020 #define IDC_SIZE_ZLIB 1021 #define IDC_USAGE_REFRESH 1021 #define IDC_STICKY 1021 #define IDC_SIZE_ZLIB2 1022 #define IDC_SIZE_LZO 1022 #define IDC_VOL_SHOW_USAGE 1022 #define IDC_VOL_BALANCE 1023 #define IDC_SIZE_ZSTD 1023 #define IDC_COMPRESSION_RATIO 1023 #define IDC_PROFILES 1024 #define IDC_FRAGMENTATION 1024 #define IDC_PROFILES_SINGLE 1025 #define IDC_PROFILES_DUP 1026 #define IDC_VOL_DEVICES 1026 #define IDC_PROFILES_RAID0 1027 #define IDC_VOL_DEVICES2 1027 #define IDC_VOL_SCRUB 1027 #define IDC_PROFILES_RAID1 1028 #define IDC_PROFILES_RAID10 1029 #define IDC_PROFILES_RAID5 1030 #define IDC_PROFILES_RAID6 1031 #define IDC_USAGE 1032 #define IDC_USAGE_START_SPINNER 1033 #define IDC_USAGE_START 1034 #define IDC_USAGE_END_SPINNER 1035 #define IDC_USAGE_END 1036 #define IDC_DEVID 1037 #define IDC_DEVID_COMBO 1038 #define IDC_DRANGE_START 1039 #define IDC_DRANGE 1040 #define IDC_PROGRESS1 1040 #define IDC_BALANCE_PROGRESS 1040 #define IDC_RECV_PROGRESS 1040 #define IDC_DRANGE_END 1041 #define IDC_DATA 1041 #define IDC_LIMIT 1042 #define IDC_METADATA 1042 #define IDC_VRANGE 1043 #define IDC_SYSTEM 1043 #define IDC_LIMIT_START_SPINNER 1044 #define IDC_BUTTON1 1044 #define IDC_DATA_OPTIONS 1044 #define IDC_DEVICE_ADD 1044 #define IDC_RESET_STATS 1044 #define IDC_OPEN_ADMIN 1044 #define IDC_BROWSE 1044 #define IDC_LIMIT_START 1045 #define IDC_BUTTON2 1045 #define IDC_METADATA_OPTIONS 1045 #define IDC_DEVICE_REFRESH 1045 #define IDC_PARENT_BROWSE 1045 #define IDC_VRANGE_END 1046 #define IDC_BUTTON3 1046 #define IDC_SYSTEM_OPTIONS 1046 #define IDC_DEVICE_REMOVE 1046 #define IDC_CLONE_ADD 1046 #define IDC_VRANGE_START 1047 #define IDC_BALANCE_STATUS 1047 #define IDC_DEVICE_SHOW_STATS 1047 #define IDC_CLONE_REMOVE 1047 #define IDC_LIMIT_END_SPINNER 1048 #define IDC_START_BALANCE 1048 #define IDC_DEVICE_RESIZE 1048 #define IDC_LIMIT_END 1049 #define IDC_PAUSE_BALANCE 1049 #define IDC_STRIPES 1050 #define IDC_BUTTON6 1050 #define IDC_CANCEL_BALANCE 1050 #define IDC_STRIPES_START_SPINNER 1051 #define IDC_STRIPES_START 1052 #define IDC_DEVLIST 1052 #define IDC_STRIPES_END_SPINNER 1053 #define IDC_DEVICE_TREE 1053 #define IDC_STRIPES_END 1054 #define IDC_UUID 1054 #define IDC_CONVERT 1055 #define IDC_SCRUB_INFO 1055 #define IDC_CONVERT_COMBO 1056 #define IDC_START_SCRUB 1056 #define IDC_SOFT 1057 #define IDC_PAUSE_SCRUB 1057 #define IDC_CANCEL_SCRUB 1058 #define IDC_PROFILES_RAID1C3 1058 #define IDC_SCRUB_PROGRESS 1059 #define IDC_PROFILES_RAID1C4 1059 #define IDC_SCRUB_STATUS 1060 #define IDC_COMPRESS_TYPE 1061 #define IDC_CHECK1 1062 #define IDC_SUBVOL_RO 1062 #define IDC_INCREMENTAL 1062 #define IDC_DEVICE_ID 1063 #define IDC_WRITE_ERRS 1064 #define IDC_READ_ERRS 1065 #define IDC_RECV_MSG 1065 #define IDC_FLUSH_ERRS 1066 #define IDC_STREAM_DEST 1066 #define IDC_CORRUPTION_ERRS 1067 #define IDC_SEND_STATUS 1067 #define IDC_GENERATION_ERRS 1068 #define IDC_PARENT_SUBVOL 1068 #define IDC_CLONE_LIST 1069 #define IDC_RESIZE_DEVICE_ID 1070 #define IDC_RESIZE_CURSIZE 1071 #define IDC_RESIZE_SLIDER 1072 #define IDC_RESIZE_NEWSIZE 1073 #define IDC_VOL_CHANGE_DRIVE_LETTER 1073 #define IDC_DRIVE_LETTER_COMBO 1074 #define IDC_MAPPINGS_TAB 1075 #define IDC_MAPPINGS_LIST 1076 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 179 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1075 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: src/shellext/scrub.cpp ================================================ /* Copyright (c) Mark Harmstone 2017 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "shellext.h" #include "scrub.h" #include "resource.h" #include "../btrfsioctl.h" #include #include #include #include #include #define NO_SHLWAPI_STRFCNS #include #include static wstring format_duration(uint64_t dur, const wchar_t* fmt) { int len = GetDurationFormatEx(LOCALE_NAME_USER_DEFAULT, 0, nullptr, dur, fmt, nullptr, 0); if (len == 0) throw last_error(GetLastError()); wstring s; s.resize(len); if (GetDurationFormatEx(LOCALE_NAME_USER_DEFAULT, 0, nullptr, dur, fmt, s.data(), s.size()) == 0) throw last_error(GetLastError()); return s; } void BtrfsScrub::UpdateTextBox(HWND hwndDlg, btrfs_query_scrub* bqs) { btrfs_query_scrub* bqs2 = nullptr; bool alloc_bqs2 = false; NTSTATUS Status; wstring s, t, u; WCHAR dt[255], tm[255]; FILETIME filetime; SYSTEMTIME systime; uint64_t recoverable_errors = 0, unrecoverable_errors = 0; try { if (bqs->num_errors > 0) { win_handle h; IO_STATUS_BLOCK iosb; ULONG len; h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); len = 0; try { do { len += 1024; if (bqs2) { free(bqs2); bqs2 = nullptr; } bqs2 = (btrfs_query_scrub*)malloc(len); Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_SCRUB, nullptr, 0, bqs2, len); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) throw ntstatus_error(Status); } while (Status == STATUS_BUFFER_OVERFLOW); } catch (...) { if (bqs2) free(bqs2); throw; } alloc_bqs2 = true; } else bqs2 = bqs; // "scrub started" if (bqs2->start_time.QuadPart > 0) { filetime.dwLowDateTime = bqs2->start_time.LowPart; filetime.dwHighDateTime = bqs2->start_time.HighPart; if (!FileTimeToSystemTime(&filetime, &systime)) throw last_error(GetLastError()); if (!SystemTimeToTzSpecificLocalTime(nullptr, &systime, &systime)) throw last_error(GetLastError()); if (!load_string(module, IDS_SCRUB_MSG_STARTED, t)) throw last_error(GetLastError()); if (!GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime, nullptr, dt, sizeof(dt) / sizeof(WCHAR))) throw last_error(GetLastError()); if (!GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &systime, nullptr, tm, sizeof(tm) / sizeof(WCHAR))) throw last_error(GetLastError()); wstring_sprintf(u, t, dt, tm); s += u; s += L"\r\n"; } // errors if (bqs2->num_errors > 0) { btrfs_scrub_error* bse = &bqs2->errors; do { if (bse->recovered) recoverable_errors++; else unrecoverable_errors++; if (bse->parity) { if (!load_string(module, IDS_SCRUB_MSG_RECOVERABLE_PARITY, t)) throw last_error(GetLastError()); wstring_sprintf(u, t, bse->address, bse->device); } else if (bse->is_metadata) { int message; if (bse->recovered) message = IDS_SCRUB_MSG_RECOVERABLE_METADATA; else if (bse->metadata.firstitem.obj_id == 0 && bse->metadata.firstitem.obj_type == 0 && bse->metadata.firstitem.offset == 0) message = IDS_SCRUB_MSG_UNRECOVERABLE_METADATA; else message = IDS_SCRUB_MSG_UNRECOVERABLE_METADATA_FIRSTITEM; if (!load_string(module, message, t)) throw last_error(GetLastError()); if (bse->recovered) wstring_sprintf(u, t, bse->address, bse->device); else if (bse->metadata.firstitem.obj_id == 0 && bse->metadata.firstitem.obj_type == 0 && bse->metadata.firstitem.offset == 0) wstring_sprintf(u, t, bse->address, bse->device, bse->metadata.root, bse->metadata.level); else wstring_sprintf(u, t, bse->address, bse->device, bse->metadata.root, bse->metadata.level, bse->metadata.firstitem.obj_id, bse->metadata.firstitem.obj_type, bse->metadata.firstitem.offset); } else { int message; if (bse->recovered) message = IDS_SCRUB_MSG_RECOVERABLE_DATA; else if (bse->data.subvol != 0) message = IDS_SCRUB_MSG_UNRECOVERABLE_DATA_SUBVOL; else message = IDS_SCRUB_MSG_UNRECOVERABLE_DATA; if (!load_string(module, message, t)) throw last_error(GetLastError()); if (bse->recovered) wstring_sprintf(u, t, bse->address, bse->device); else if (bse->data.subvol != 0) wstring_sprintf(u, t, bse->address, bse->device, bse->data.subvol, bse->data.filename_length / sizeof(WCHAR), bse->data.filename, bse->data.offset); else wstring_sprintf(u, t, bse->address, bse->device, bse->data.filename_length / sizeof(WCHAR), bse->data.filename, bse->data.offset); } s += u; s += L"\r\n"; if (bse->next_entry == 0) break; else bse = (btrfs_scrub_error*)((uint8_t*)bse + bse->next_entry); } while (true); } if (bqs2->finish_time.QuadPart > 0) { wstring d1, d2; float speed; // "scrub finished" filetime.dwLowDateTime = bqs2->finish_time.LowPart; filetime.dwHighDateTime = bqs2->finish_time.HighPart; if (!FileTimeToSystemTime(&filetime, &systime)) throw last_error(GetLastError()); if (!SystemTimeToTzSpecificLocalTime(nullptr, &systime, &systime)) throw last_error(GetLastError()); if (!load_string(module, IDS_SCRUB_MSG_FINISHED, t)) throw last_error(GetLastError()); if (!GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime, nullptr, dt, sizeof(dt) / sizeof(WCHAR))) throw last_error(GetLastError()); if (!GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &systime, nullptr, tm, sizeof(tm) / sizeof(WCHAR))) throw last_error(GetLastError()); wstring_sprintf(u, t, dt, tm); s += u; s += L"\r\n"; // summary if (!load_string(module, IDS_SCRUB_MSG_SUMMARY, t)) throw last_error(GetLastError()); format_size(bqs2->data_scrubbed, d1, false); speed = (float)bqs2->data_scrubbed / ((float)bqs2->duration / 10000000.0f); format_size((uint64_t)speed, d2, false); wstring dur; if (bqs2->duration >= 36000000000) dur = format_duration(bqs2->duration, L"h:mm:ss"); else dur = format_duration(bqs2->duration, L"m:ss"); wstring_sprintf(u, t, d1.c_str(), dur.c_str(), d2.c_str()); s += u; s += L"\r\n"; // recoverable errors if (!load_string(module, IDS_SCRUB_MSG_SUMMARY_ERRORS_RECOVERABLE, t)) throw last_error(GetLastError()); wstring_sprintf(u, t, recoverable_errors); s += u; s += L"\r\n"; // unrecoverable errors if (!load_string(module, IDS_SCRUB_MSG_SUMMARY_ERRORS_UNRECOVERABLE, t)) throw last_error(GetLastError()); wstring_sprintf(u, t, unrecoverable_errors); s += u; s += L"\r\n"; } SetWindowTextW(GetDlgItem(hwndDlg, IDC_SCRUB_INFO), s.c_str()); } catch (...) { if (alloc_bqs2) free(bqs2); throw; } if (alloc_bqs2) free(bqs2); } void BtrfsScrub::RefreshScrubDlg(HWND hwndDlg, bool first_time) { btrfs_query_scrub bqs; { win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_SCRUB, nullptr, 0, &bqs, sizeof(btrfs_query_scrub)); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) throw ntstatus_error(Status); } else throw last_error(GetLastError()); } if (first_time || status != bqs.status || chunks_left != bqs.chunks_left) { wstring s; if (bqs.status == BTRFS_SCRUB_STOPPED) { EnableWindow(GetDlgItem(hwndDlg, IDC_START_SCRUB), true); EnableWindow(GetDlgItem(hwndDlg, IDC_PAUSE_SCRUB), false); EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_SCRUB), false); if (bqs.error != STATUS_SUCCESS) { wstring t; if (!load_string(module, IDS_SCRUB_FAILED, t)) throw last_error(GetLastError()); wstring_sprintf(s, t, bqs.error); } else { if (!load_string(module, bqs.total_chunks == 0 ? IDS_NO_SCRUB : IDS_SCRUB_FINISHED, s)) throw last_error(GetLastError()); } } else { wstring t; float pc; EnableWindow(GetDlgItem(hwndDlg, IDC_START_SCRUB), false); EnableWindow(GetDlgItem(hwndDlg, IDC_PAUSE_SCRUB), true); EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_SCRUB), true); if (!load_string(module, bqs.status == BTRFS_SCRUB_PAUSED ? IDS_SCRUB_PAUSED : IDS_SCRUB_RUNNING, t)) throw last_error(GetLastError()); pc = ((float)(bqs.total_chunks - bqs.chunks_left) / (float)bqs.total_chunks) * 100.0f; wstring_sprintf(s, t, bqs.total_chunks - bqs.chunks_left, bqs.total_chunks, pc); } SetDlgItemTextW(hwndDlg, IDC_SCRUB_STATUS, s.c_str()); if (first_time || status != bqs.status) { EnableWindow(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), bqs.status != BTRFS_SCRUB_STOPPED); if (bqs.status != BTRFS_SCRUB_STOPPED) { SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETRANGE32, 0, (LPARAM)bqs.total_chunks); SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETPOS, (WPARAM)(bqs.total_chunks - bqs.chunks_left), 0); if (bqs.status == BTRFS_SCRUB_PAUSED) SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETSTATE, PBST_PAUSED, 0); else SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETSTATE, PBST_NORMAL, 0); } else { SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETRANGE32, 0, 0); SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETPOS, 0, 0); } chunks_left = bqs.chunks_left; } } if (bqs.status != BTRFS_SCRUB_STOPPED && chunks_left != bqs.chunks_left) { SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETPOS, (WPARAM)(bqs.total_chunks - bqs.chunks_left), 0); chunks_left = bqs.chunks_left; } if (first_time || status != bqs.status || num_errors != bqs.num_errors) { UpdateTextBox(hwndDlg, &bqs); num_errors = bqs.num_errors; } status = bqs.status; } void BtrfsScrub::StartScrub(HWND hwndDlg) { win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_START_SCRUB, nullptr, 0, nullptr, 0); if (Status == STATUS_DEVICE_NOT_READY) { btrfs_query_balance bqb; NTSTATUS Status2; Status2 = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_BALANCE, nullptr, 0, &bqb, sizeof(btrfs_query_balance)); if (NT_SUCCESS(Status2) && bqb.status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED)) throw string_error(IDS_SCRUB_BALANCE_RUNNING); } if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); RefreshScrubDlg(hwndDlg, true); } else throw last_error(GetLastError()); } void BtrfsScrub::PauseScrub(HWND) { btrfs_query_scrub bqs; win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_SCRUB, nullptr, 0, &bqs, sizeof(btrfs_query_scrub)); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) throw ntstatus_error(Status); if (bqs.status == BTRFS_SCRUB_PAUSED) Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RESUME_SCRUB, nullptr, 0, nullptr, 0); else Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_PAUSE_SCRUB, nullptr, 0, nullptr, 0); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } else throw last_error(GetLastError()); } void BtrfsScrub::StopScrub(HWND) { win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_STOP_SCRUB, nullptr, 0, nullptr, 0); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } else throw last_error(GetLastError()); } INT_PTR CALLBACK BtrfsScrub::ScrubDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) { try { switch (uMsg) { case WM_INITDIALOG: RefreshScrubDlg(hwndDlg, true); SetTimer(hwndDlg, 1, 1000, nullptr); break; case WM_COMMAND: switch (HIWORD(wParam)) { case BN_CLICKED: switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: EndDialog(hwndDlg, 0); return true; case IDC_START_SCRUB: StartScrub(hwndDlg); return true; case IDC_PAUSE_SCRUB: PauseScrub(hwndDlg); return true; case IDC_CANCEL_SCRUB: StopScrub(hwndDlg); return true; } break; } break; case WM_TIMER: RefreshScrubDlg(hwndDlg, false); break; } } catch (const exception& e) { error_message(hwndDlg, e.what()); } return false; } static INT_PTR CALLBACK stub_ScrubDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { BtrfsScrub* bs; if (uMsg == WM_INITDIALOG) { SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); bs = (BtrfsScrub*)lParam; } else { bs = (BtrfsScrub*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); } if (bs) return bs->ScrubDlgProc(hwndDlg, uMsg, wParam, lParam); else return false; } extern "C" void CALLBACK ShowScrubW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) { try { win_handle token; TOKEN_PRIVILEGES tp; LUID luid; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) throw last_error(GetLastError()); if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) throw last_error(GetLastError()); tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) throw last_error(GetLastError()); set_dpi_aware(); BtrfsScrub scrub(lpszCmdLine); DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_SCRUB), hwnd, stub_ScrubDlgProc, (LPARAM)&scrub); } catch (const exception& e) { error_message(hwnd, e.what()); } } extern "C" void CALLBACK StartScrubW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) { vector args; command_line_to_args(lpszCmdLine, args); if (args.size() >= 1) { LUID luid; TOKEN_PRIVILEGES tp; { win_handle token; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) return; if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) return; tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) return; } win_handle h = CreateFileW(args[0].c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { IO_STATUS_BLOCK iosb; NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_START_SCRUB, nullptr, 0, nullptr, 0); } } } extern "C" void CALLBACK StopScrubW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) { vector args; command_line_to_args(lpszCmdLine, args); if (args.size() >= 1) { LUID luid; TOKEN_PRIVILEGES tp; { win_handle token; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) return; if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) return; tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) return; } win_handle h = CreateFileW(args[0].c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { IO_STATUS_BLOCK iosb; NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_STOP_SCRUB, nullptr, 0, nullptr, 0); } } } ================================================ FILE: src/shellext/scrub.h ================================================ /* Copyright (c) Mark Harmstone 2017 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #pragma once #include #include "../btrfs.h" #include "../btrfsioctl.h" class BtrfsScrub { public: BtrfsScrub(const wstring& drive) { fn = drive; } INT_PTR CALLBACK ScrubDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); private: void RefreshScrubDlg(HWND hwndDlg, bool first_time); void UpdateTextBox(HWND hwndDlg, btrfs_query_scrub* bqs); void StartScrub(HWND hwndDlg); void PauseScrub(HWND hwndDlg); void StopScrub(HWND hwndDlg); wstring fn; uint32_t status; uint64_t chunks_left; uint32_t num_errors; }; ================================================ FILE: src/shellext/send.cpp ================================================ /* Copyright (c) Mark Harmstone 2017 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "shellext.h" #include "send.h" #include "resource.h" #include #include #include #define SEND_BUFFER_LEN 1048576 DWORD BtrfsSend::Thread() { try { NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_send_subvol* bss; btrfs_send_header header; btrfs_send_command end; ULONG i; buf = (char*)malloc(SEND_BUFFER_LEN); try { 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); if (dirh == INVALID_HANDLE_VALUE) throw string_error(IDS_SEND_CANT_OPEN_DIR, subvol.c_str(), GetLastError(), format_message(GetLastError()).c_str()); try { size_t bss_size = offsetof(btrfs_send_subvol, clones[0]) + (clones.size() * sizeof(HANDLE)); bss = (btrfs_send_subvol*)malloc(bss_size); memset(bss, 0, bss_size); if (incremental) { WCHAR parent[MAX_PATH]; HANDLE parenth; parent[0] = 0; GetDlgItemTextW(hwnd, IDC_PARENT_SUBVOL, parent, sizeof(parent) / sizeof(WCHAR)); parenth = CreateFileW(parent, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (parenth == INVALID_HANDLE_VALUE) throw string_error(IDS_SEND_CANT_OPEN_DIR, parent, GetLastError(), format_message(GetLastError()).c_str()); bss->parent = parenth; } else bss->parent = nullptr; bss->num_clones = (ULONG)clones.size(); for (i = 0; i < bss->num_clones; i++) { HANDLE h; 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); if (h == INVALID_HANDLE_VALUE) { auto le = GetLastError(); ULONG j; for (j = 0; j < i; j++) { CloseHandle(bss->clones[j]); } if (bss->parent) CloseHandle(bss->parent); throw string_error(IDS_SEND_CANT_OPEN_DIR, clones[i].c_str(), le, format_message(le).c_str()); } bss->clones[i] = h; } Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SEND_SUBVOL, bss, (ULONG)bss_size, nullptr, 0); for (i = 0; i < bss->num_clones; i++) { CloseHandle(bss->clones[i]); } if (!NT_SUCCESS(Status)) { if (Status == (NTSTATUS)STATUS_INVALID_PARAMETER) { BY_HANDLE_FILE_INFORMATION fileinfo; if (!GetFileInformationByHandle(dirh, &fileinfo)) { auto le = GetLastError(); if (bss->parent) CloseHandle(bss->parent); throw string_error(IDS_SEND_GET_FILE_INFO_FAILED, le, format_message(le).c_str()); } if (!(fileinfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) { if (bss->parent) CloseHandle(bss->parent); throw string_error(IDS_SEND_NOT_READONLY); } if (bss->parent) { if (!GetFileInformationByHandle(bss->parent, &fileinfo)) { auto le = GetLastError(); CloseHandle(bss->parent); throw string_error(IDS_SEND_GET_FILE_INFO_FAILED, le, format_message(le).c_str()); } if (!(fileinfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) { CloseHandle(bss->parent); throw string_error(IDS_SEND_PARENT_NOT_READONLY); } } } if (bss->parent) CloseHandle(bss->parent); throw string_error(IDS_SEND_FSCTL_BTRFS_SEND_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str()); } if (bss->parent) CloseHandle(bss->parent); stream = CreateFileW(file, FILE_WRITE_DATA | DELETE, 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (stream == INVALID_HANDLE_VALUE) throw string_error(IDS_SEND_CANT_OPEN_FILE, file, GetLastError(), format_message(GetLastError()).c_str()); try { memcpy(header.magic, BTRFS_SEND_MAGIC, sizeof(header.magic)); header.version = 1; if (!WriteFile(stream, &header, sizeof(header), nullptr, nullptr)) throw string_error(IDS_SEND_WRITEFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str()); do { Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_READ_SEND_BUFFER, nullptr, 0, buf, SEND_BUFFER_LEN); if (NT_SUCCESS(Status)) { if (!WriteFile(stream, buf, (DWORD)iosb.Information, nullptr, nullptr)) throw string_error(IDS_SEND_WRITEFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str()); } } while (NT_SUCCESS(Status)); if (Status != STATUS_END_OF_FILE) throw string_error(IDS_SEND_FSCTL_BTRFS_READ_SEND_BUFFER_FAILED, Status, format_ntstatus(Status).c_str()); end.length = 0; end.cmd = BTRFS_SEND_CMD_END; end.csum = 0x9dc96c50; if (!WriteFile(stream, &end, sizeof(end), nullptr, nullptr)) throw string_error(IDS_SEND_WRITEFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str()); SetEndOfFile(stream); } catch (...) { FILE_DISPOSITION_INFO fdi; fdi.DeleteFile = true; Status = NtSetInformationFile(stream, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation); CloseHandle(stream); stream = INVALID_HANDLE_VALUE; if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); throw; } CloseHandle(stream); stream = INVALID_HANDLE_VALUE; } catch (...) { CloseHandle(dirh); dirh = INVALID_HANDLE_VALUE; throw; } CloseHandle(dirh); dirh = INVALID_HANDLE_VALUE; } catch (...) { free(buf); buf = nullptr; started = false; SetDlgItemTextW(hwnd, IDCANCEL, closetext); EnableWindow(GetDlgItem(hwnd, IDOK), true); EnableWindow(GetDlgItem(hwnd, IDC_STREAM_DEST), true); EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), true); throw; } } catch (const exception& e) { auto msg = utf8_to_utf16(e.what()); SetDlgItemTextW(hwnd, IDC_SEND_STATUS, msg.c_str()); return 0; } free(buf); buf = nullptr; started = false; SetDlgItemTextW(hwnd, IDCANCEL, closetext); EnableWindow(GetDlgItem(hwnd, IDOK), true); EnableWindow(GetDlgItem(hwnd, IDC_STREAM_DEST), true); EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), true); wstring success; load_string(module, IDS_SEND_SUCCESS, success); SetDlgItemTextW(hwnd, IDC_SEND_STATUS, success.c_str()); return 0; } static DWORD WINAPI send_thread(LPVOID lpParameter) { BtrfsSend* bs = (BtrfsSend*)lpParameter; return bs->Thread(); } void BtrfsSend::StartSend(HWND hwnd) { wstring s; HWND cl; if (started) return; GetDlgItemTextW(hwnd, IDC_STREAM_DEST, file, sizeof(file) / sizeof(WCHAR)); if (file[0] == 0) return; if (incremental) { WCHAR parent[MAX_PATH]; GetDlgItemTextW(hwnd, IDC_PARENT_SUBVOL, parent, sizeof(parent) / sizeof(WCHAR)); if (parent[0] == 0) return; } started = true; wstring writing; load_string(module, IDS_SEND_WRITING, writing); SetDlgItemTextW(hwnd, IDC_SEND_STATUS, writing.c_str()); load_string(module, IDS_SEND_CANCEL, s); SetDlgItemTextW(hwnd, IDCANCEL, s.c_str()); EnableWindow(GetDlgItem(hwnd, IDOK), false); EnableWindow(GetDlgItem(hwnd, IDC_STREAM_DEST), false); EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), false); clones.clear(); cl = GetDlgItem(hwnd, IDC_CLONE_LIST); auto num_clones = SendMessageW(cl, LB_GETCOUNT, 0, 0); if (num_clones != LB_ERR) { for (unsigned int i = 0; i < (unsigned int)num_clones; i++) { WCHAR* t; auto len = SendMessageW(cl, LB_GETTEXTLEN, i, 0); t = (WCHAR*)malloc((len + 1) * sizeof(WCHAR)); SendMessageW(cl, LB_GETTEXT, i, (LPARAM)t); clones.push_back(t); free(t); } } thread = CreateThread(nullptr, 0, send_thread, this, 0, nullptr); if (!thread) throw last_error(GetLastError()); } void BtrfsSend::Browse(HWND hwnd) { OPENFILENAMEW ofn; file[0] = 0; memset(&ofn, 0, sizeof(OPENFILENAMEW)); ofn.lStructSize = sizeof(OPENFILENAMEW); ofn.hwndOwner = hwnd; ofn.hInstance = module; ofn.lpstrFile = file; ofn.nMaxFile = sizeof(file) / sizeof(WCHAR); if (!GetSaveFileNameW(&ofn)) return; SetDlgItemTextW(hwnd, IDC_STREAM_DEST, file); } void BtrfsSend::BrowseParent(HWND hwnd) { BROWSEINFOW bi; PIDLIST_ABSOLUTE root, pidl; HRESULT hr; WCHAR parent[MAX_PATH], volpathw[MAX_PATH]; NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_get_file_ids bgfi; if (!GetVolumePathNameW(subvol.c_str(), volpathw, (sizeof(volpathw) / sizeof(WCHAR)) - 1)) throw string_error(IDS_RECV_GETVOLUMEPATHNAME_FAILED, GetLastError(), format_message(GetLastError()).c_str()); hr = SHParseDisplayName(volpathw, 0, &root, 0, 0); if (FAILED(hr)) throw string_error(IDS_SHPARSEDISPLAYNAME_FAILED); memset(&bi, 0, sizeof(BROWSEINFOW)); bi.hwndOwner = hwnd; bi.pidlRoot = root; bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI | BIF_NONEWFOLDERBUTTON; pidl = SHBrowseForFolderW(&bi); if (!pidl) return; if (!SHGetPathFromIDListW(pidl, parent)) throw string_error(IDS_SHGETPATHFROMIDLIST_FAILED); { win_handle h = CreateFileW(parent, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (h == INVALID_HANDLE_VALUE) throw string_error(IDS_SEND_CANT_OPEN_DIR, parent, GetLastError(), format_message(GetLastError()).c_str()); Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids)); if (!NT_SUCCESS(Status)) throw string_error(IDS_GET_FILE_IDS_FAILED, Status, format_ntstatus(Status).c_str()); } if (bgfi.inode != 0x100 || bgfi.top) throw string_error(IDS_NOT_SUBVOL); SetDlgItemTextW(hwnd, IDC_PARENT_SUBVOL, parent); } void BtrfsSend::AddClone(HWND hwnd) { BROWSEINFOW bi; PIDLIST_ABSOLUTE root, pidl; HRESULT hr; WCHAR path[MAX_PATH], volpathw[MAX_PATH]; NTSTATUS Status; IO_STATUS_BLOCK iosb; btrfs_get_file_ids bgfi; if (!GetVolumePathNameW(subvol.c_str(), volpathw, (sizeof(volpathw) / sizeof(WCHAR)) - 1)) throw string_error(IDS_RECV_GETVOLUMEPATHNAME_FAILED, GetLastError(), format_message(GetLastError()).c_str()); hr = SHParseDisplayName(volpathw, 0, &root, 0, 0); if (FAILED(hr)) throw string_error(IDS_SHPARSEDISPLAYNAME_FAILED); memset(&bi, 0, sizeof(BROWSEINFOW)); bi.hwndOwner = hwnd; bi.pidlRoot = root; bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI | BIF_NONEWFOLDERBUTTON; pidl = SHBrowseForFolderW(&bi); if (!pidl) return; if (!SHGetPathFromIDListW(pidl, path)) throw string_error(IDS_SHGETPATHFROMIDLIST_FAILED); { win_handle h = CreateFileW(path, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (h == INVALID_HANDLE_VALUE) throw string_error(IDS_SEND_CANT_OPEN_DIR, path, GetLastError(), format_message(GetLastError()).c_str()); Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids)); if (!NT_SUCCESS(Status)) throw string_error(IDS_GET_FILE_IDS_FAILED, Status, format_ntstatus(Status).c_str()); } if (bgfi.inode != 0x100 || bgfi.top) throw string_error(IDS_NOT_SUBVOL); SendMessageW(GetDlgItem(hwnd, IDC_CLONE_LIST), LB_ADDSTRING, 0, (LPARAM)path); } void BtrfsSend::RemoveClone(HWND hwnd) { LRESULT sel; HWND cl = GetDlgItem(hwnd, IDC_CLONE_LIST); sel = SendMessageW(cl, LB_GETCURSEL, 0, 0); if (sel == LB_ERR) return; SendMessageW(cl, LB_DELETESTRING, sel, 0); if (SendMessageW(cl, LB_GETCURSEL, 0, 0) == LB_ERR) EnableWindow(GetDlgItem(hwnd, IDC_CLONE_REMOVE), false); } INT_PTR BtrfsSend::SendDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) { try { switch (uMsg) { case WM_INITDIALOG: this->hwnd = hwndDlg; GetDlgItemTextW(hwndDlg, IDCANCEL, closetext, sizeof(closetext) / sizeof(WCHAR)); break; case WM_COMMAND: switch (HIWORD(wParam)) { case BN_CLICKED: switch (LOWORD(wParam)) { case IDOK: StartSend(hwndDlg); return true; case IDCANCEL: if (started) { TerminateThread(thread, 0); if (stream != INVALID_HANDLE_VALUE) { NTSTATUS Status; FILE_DISPOSITION_INFO fdi; IO_STATUS_BLOCK iosb; fdi.DeleteFile = true; Status = NtSetInformationFile(stream, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation); CloseHandle(stream); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } if (dirh != INVALID_HANDLE_VALUE) CloseHandle(dirh); started = false; SetDlgItemTextW(hwndDlg, IDCANCEL, closetext); EnableWindow(GetDlgItem(hwnd, IDOK), true); EnableWindow(GetDlgItem(hwnd, IDC_STREAM_DEST), true); EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), true); } else EndDialog(hwndDlg, 1); return true; case IDC_BROWSE: Browse(hwndDlg); return true; case IDC_INCREMENTAL: incremental = IsDlgButtonChecked(hwndDlg, LOWORD(wParam)); EnableWindow(GetDlgItem(hwnd, IDC_PARENT_SUBVOL), incremental); EnableWindow(GetDlgItem(hwnd, IDC_PARENT_BROWSE), incremental); return true; case IDC_PARENT_BROWSE: BrowseParent(hwndDlg); return true; case IDC_CLONE_ADD: AddClone(hwndDlg); return true; case IDC_CLONE_REMOVE: RemoveClone(hwndDlg); return true; } break; case LBN_SELCHANGE: switch (LOWORD(wParam)) { case IDC_CLONE_LIST: EnableWindow(GetDlgItem(hwnd, IDC_CLONE_REMOVE), true); return true; } break; } break; } } catch (const exception& e) { error_message(hwnd, e.what()); } return false; } static INT_PTR CALLBACK stub_SendDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { BtrfsSend* bs; if (uMsg == WM_INITDIALOG) { SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); bs = (BtrfsSend*)lParam; } else bs = (BtrfsSend*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); if (bs) return bs->SendDlgProc(hwndDlg, uMsg, wParam, lParam); else return false; } void BtrfsSend::Open(HWND hwnd, LPWSTR path) { subvol = path; if (DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_SEND_SUBVOL), hwnd, stub_SendDlgProc, (LPARAM)this) <= 0) throw last_error(GetLastError()); } extern "C" void CALLBACK SendSubvolGUIW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) { try { win_handle token; TOKEN_PRIVILEGES tp; LUID luid; set_dpi_aware(); if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) throw last_error(GetLastError()); if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) throw last_error(GetLastError()); tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) throw last_error(GetLastError()); BtrfsSend bs; bs.Open(hwnd, lpszCmdLine); } catch (const exception& e) { error_message(hwnd, e.what()); } } static void send_subvol(const wstring& subvol, const wstring& file, const wstring& parent, const vector& clones) { char* buf; win_handle dirh, stream; ULONG i; btrfs_send_subvol* bss; IO_STATUS_BLOCK iosb; NTSTATUS Status; btrfs_send_header header; btrfs_send_command end; buf = (char*)malloc(SEND_BUFFER_LEN); try { 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); if (dirh == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); stream = CreateFileW(file.c_str(), FILE_WRITE_DATA | DELETE, 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (stream == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); try { size_t bss_size = offsetof(btrfs_send_subvol, clones[0]) + (clones.size() * sizeof(HANDLE)); bss = (btrfs_send_subvol*)malloc(bss_size); memset(bss, 0, bss_size); if (parent != L"") { HANDLE parenth; 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); if (parenth == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); bss->parent = parenth; } else bss->parent = nullptr; bss->num_clones = (ULONG)clones.size(); for (i = 0; i < bss->num_clones; i++) { HANDLE h; 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); if (h == INVALID_HANDLE_VALUE) { auto le = GetLastError(); ULONG j; for (j = 0; j < i; j++) { CloseHandle(bss->clones[j]); } if (bss->parent) CloseHandle(bss->parent); throw last_error(le); } bss->clones[i] = h; } Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SEND_SUBVOL, bss, (ULONG)bss_size, nullptr, 0); for (i = 0; i < bss->num_clones; i++) { CloseHandle(bss->clones[i]); } if (bss->parent) CloseHandle(bss->parent); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); memcpy(header.magic, BTRFS_SEND_MAGIC, sizeof(header.magic)); header.version = 1; if (!WriteFile(stream, &header, sizeof(header), nullptr, nullptr)) throw last_error(GetLastError()); do { Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_READ_SEND_BUFFER, nullptr, 0, buf, SEND_BUFFER_LEN); if (NT_SUCCESS(Status)) WriteFile(stream, buf, (DWORD)iosb.Information, nullptr, nullptr); } while (NT_SUCCESS(Status)); if (Status != STATUS_END_OF_FILE) throw ntstatus_error(Status); end.length = 0; end.cmd = BTRFS_SEND_CMD_END; end.csum = 0x9dc96c50; if (!WriteFile(stream, &end, sizeof(end), nullptr, nullptr)) throw last_error(GetLastError()); SetEndOfFile(stream); } catch (...) { FILE_DISPOSITION_INFO fdi; fdi.DeleteFile = true; Status = NtSetInformationFile(stream, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); throw; } } catch (...) { free(buf); throw; } free(buf); } extern "C" void CALLBACK SendSubvolW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) { vector args; wstring subvol, parent, file; vector clones; command_line_to_args(lpszCmdLine, args); if (args.size() >= 2) { TOKEN_PRIVILEGES tp; LUID luid; { win_handle token; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) return; if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) return; tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) return; } for (unsigned int i = 0; i < args.size(); i++) { if (args[i][0] == '-') { if (args[i][2] == 0 && i < args.size() - 1) { if (args[i][1] == 'p') { parent = args[i+1]; i++; } else if (args[i][1] == 'c') { clones.push_back(args[i+1]); i++; } } } else { if (subvol == L"") subvol = args[i]; else if (file == L"") file = args[i]; } } if (subvol != L"" && file != L"") { try { send_subvol(subvol, file, parent, clones); } catch (const exception& e) { cerr << "Error: " << e.what() << endl; } } } } ================================================ FILE: src/shellext/send.h ================================================ /* Copyright (c) Mark Harmstone 2017 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #pragma once #include "../btrfs.h" class BtrfsSend { public: BtrfsSend() { started = false; file[0] = 0; dirh = INVALID_HANDLE_VALUE; stream = INVALID_HANDLE_VALUE; subvol = L""; buf = nullptr; incremental = false; } ~BtrfsSend() { if (buf) free(buf); } void Open(HWND hwnd, WCHAR* path); INT_PTR SendDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); DWORD Thread(); private: void StartSend(HWND hwnd); void Browse(HWND hwnd); void BrowseParent(HWND hwnd); void AddClone(HWND hwnd); void RemoveClone(HWND hwnd); bool started; bool incremental; WCHAR file[MAX_PATH], closetext[255]; HANDLE thread, dirh, stream; HWND hwnd; wstring subvol; char* buf; vector clones; }; ================================================ FILE: src/shellext/shellbtrfs.def ================================================ EXPORTS DllCanUnloadNow PRIVATE DllGetClassObject PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE DllInstall PRIVATE AddDeviceW PRIVATE RemoveDeviceW PRIVATE StartBalanceW PRIVATE PauseBalanceW PRIVATE StopBalanceW PRIVATE ShowScrubW PRIVATE ResetStatsW PRIVATE ShowPropSheetW PRIVATE RecvSubvolGUIW PRIVATE SendSubvolGUIW PRIVATE CreateSubvolW PRIVATE CreateSnapshotW PRIVATE ReflinkCopyW PRIVATE StartScrubW PRIVATE StopScrubW PRIVATE SendSubvolW PRIVATE RecvSubvolW PRIVATE ResizeDeviceW PRIVATE ShowChangeDriveLetterW PRIVATE MappingsTest PRIVATE ================================================ FILE: src/shellext/shellbtrfs.manifest ================================================ WinBtrfs shell extension ================================================ FILE: src/shellext/shellbtrfs.rc.in ================================================ // Microsoft Visual C++ generated resource script. // #include "@CMAKE_CURRENT_SOURCE_DIR@/src/shellext/resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include #ifndef IDC_STATIC #define IDC_STATIC (-1) #endif ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United Kingdom) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK #pragma code_page(1252) #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ICON1 ICON "@CMAKE_CURRENT_SOURCE_DIR@/src/shellext/subvol.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "080904b0" BEGIN VALUE "FileDescription", "WinBtrfs shell extension" VALUE "FileVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" VALUE "InternalName", "btrfs" VALUE "LegalCopyright", "Copyright (c) Mark Harmstone 2016-24" VALUE "OriginalFilename", "shellbtrfs.dll" VALUE "ProductName", "WinBtrfs" VALUE "ProductVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x809, 1200 END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_PROP_SHEET DIALOGEX 0, 0, 235, 271 STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_DISABLED | WS_CAPTION CAPTION "Inode property sheet" FONT 8, "MS Shell Dlg", 400, 0, 0x0 BEGIN LTEXT "Subvolume:",IDC_STATIC,14,21,38,8 LTEXT "Inode:",IDC_STATIC,14,35,21,8 GROUPBOX "Information",IDC_GROUP_INFORMATION,7,7,221,99 LTEXT "Type:",IDC_STATIC,14,49,18,8 GROUPBOX "POSIX permissions",IDC_STATIC,7,110,221,102 LTEXT "User:",IDC_STATIC,14,125,17,8 LTEXT "Group:",IDC_STATIC,14,141,22,8 EDITTEXT IDC_UID,94,123,40,14,ES_AUTOHSCROLL | ES_NUMBER EDITTEXT IDC_GID,94,139,40,14,ES_AUTOHSCROLL | ES_NUMBER LTEXT "User",IDC_STATIC,14,175,15,8 LTEXT "Group",IDC_STATIC,14,186,20,8 LTEXT "Others",IDC_STATIC,14,196,22,8 LTEXT "Read",IDC_STATIC,50,162,17,8 LTEXT "Write",IDC_STATIC,89,162,18,8 LTEXT "Execute",IDC_STATIC,129,162,30,8 CONTROL "",IDC_USERR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,54,175,16,10 CONTROL "",IDC_GROUPR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,54,186,16,10 CONTROL "",IDC_OTHERR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,54,196,16,10 CONTROL "",IDC_USERW,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,93,175,16,10 CONTROL "",IDC_GROUPW,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,93,186,16,10 CONTROL "",IDC_OTHERW,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,93,196,16,10 CONTROL "",IDC_USERX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,135,175,16,10 CONTROL "",IDC_GROUPX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,135,186,16,10 CONTROL "",IDC_OTHERX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,135,196,16,10 GROUPBOX "Flags",IDC_STATIC,7,218,221,48 CONTROL "Disable Copy-on-Write",IDC_NODATACOW,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,232,86,10 LTEXT "(blank)",IDC_INODE,78,35,70,8 LTEXT "(blank)",IDC_TYPE,78,49,116,8 CONTROL "Compress",IDC_COMPRESS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,246,46,10 LTEXT "Size on disk:",IDC_STATIC,14,63,61,8 CONTROL "%s (Details)",IDC_SIZE_ON_DISK,"SysLink",WS_TABSTOP,78,63,142,8 COMBOBOX IDC_COMPRESS_TYPE,63,245,48,13,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP CONTROL "Readonly subvolume",IDC_SUBVOL_RO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,124,232,80,10 LTEXT "(blank)",IDC_SUBVOL,78,21,70,8 PUSHBUTTON "&Open as Admin",IDC_OPEN_ADMIN,151,21,70,14 CONTROL "Set UID",IDC_SETUID,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,177,175,40,10 CONTROL "Set GID",IDC_SETGID,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,177,186,40,10 CONTROL "Sticky",IDC_STICKY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,177,196,34,10 LTEXT "Compression ratio:",IDC_STATIC,14,77,61,8 LTEXT "%1.1f%%",IDC_COMPRESSION_RATIO,78,77,116,8 LTEXT "Fragmentation:",IDC_STATIC,14,91,61,8 LTEXT "%1.1f%%",IDC_FRAGMENTATION,78,91,116,8 END IDD_SIZE_DETAILS DIALOGEX 0, 0, 212, 98 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Size details" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "OK",IDOK,81,77,50,14 LTEXT "Inline:",IDC_STATIC,7,7,21,8 LTEXT "Uncompressed:",IDC_STATIC,7,20,49,8 LTEXT "ZLIB:",IDC_STATIC,7,33,18,8 LTEXT "LZO:",IDC_STATIC,7,46,16,8 LTEXT "(blank)",IDC_SIZE_INLINE,63,7,142,8 LTEXT "(blank)",IDC_SIZE_UNCOMPRESSED,63,20,142,8 LTEXT "(blank)",IDC_SIZE_ZLIB,63,33,142,8 LTEXT "(blank)",IDC_SIZE_LZO,63,46,142,8 LTEXT "Zstd:",IDC_STATIC,7,59,16,8 LTEXT "(blank)",IDC_SIZE_ZSTD,63,59,142,8 END IDD_VOL_PROP_SHEET DIALOGEX 0, 0, 235, 273 STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_DISABLED | WS_CAPTION CAPTION "s" FONT 8, "MS Shell Dlg", 400, 0, 0x0 BEGIN LTEXT "UUID:",IDC_STATIC,7,15,20,8 LTEXT "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",IDC_UUID,32,15,294,8 PUSHBUTTON "Change drive &letter...",IDC_VOL_CHANGE_DRIVE_LETTER,7,30,101,19 PUSHBUTTON "Show &usage...",IDC_VOL_SHOW_USAGE,154,69,67,19 PUSHBUTTON "&Balance...",IDC_VOL_BALANCE,154,127,67,19 PUSHBUTTON "&Devices...",IDC_VOL_DEVICES,154,184,67,19 GROUPBOX "Usage",IDC_STATIC,7,53,221,53 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 GROUPBOX "Balance",IDC_STATIC,7,109,221,53 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 GROUPBOX "Devices",IDC_STATIC,7,168,221,45 LTEXT "Allows you to add disks or partitions to this filesystem, or remove those already present.",IDC_STATIC,14,181,131,30 GROUPBOX "Scrub",IDC_STATIC,7,221,221,45 LTEXT "Scrubbing verifies the data and metadata of a filesystem, and where possible will correct any errors.",IDC_STATIC,15,234,131,27 PUSHBUTTON "&Scrub...",IDC_VOL_SCRUB,154,237,67,19 END IDD_VOL_USAGE DIALOGEX 0, 0, 235, 242 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Volume usage" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "OK",IDOK,62,221,50,14 PUSHBUTTON "&Refresh",IDC_USAGE_REFRESH,124,221,50,14 EDITTEXT IDC_USAGE_BOX,7,7,221,208,ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL END IDD_BALANCE_OPTIONS DIALOGEX 0, 0, 303, 138 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Balance options" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "OK",IDOK,91,117,50,14 PUSHBUTTON "Cancel",IDCANCEL,161,117,50,14 CONTROL "&Profiles:",IDC_PROFILES,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,6,41,10 CONTROL "Single",IDC_PROFILES_SINGLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,19,35,10 CONTROL "DUP",IDC_PROFILES_DUP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,30,29,10 CONTROL "RAID0",IDC_PROFILES_RAID0,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,41,36,10 CONTROL "RAID1",IDC_PROFILES_RAID1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,52,36,10 CONTROL "RAID10",IDC_PROFILES_RAID10,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,63,39,10 CONTROL "RAID5",IDC_PROFILES_RAID5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,52,19,36,10 CONTROL "RAID6",IDC_PROFILES_RAID6,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,52,30,36,10 CONTROL "RAID1C3",IDC_PROFILES_RAID1C3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,52,41,44,10 CONTROL "RAID1C4",IDC_PROFILES_RAID1C4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,52,52,44,10 CONTROL "&Usage:",IDC_USAGE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,80,37,10 EDITTEXT IDC_USAGE_START,7,94,19,14,ES_AUTOHSCROLL | ES_NUMBER CONTROL "",IDC_USAGE_START_SPINNER,"msctls_updown32",UDS_SETBUDDYINT | UDS_AUTOBUDDY | UDS_ARROWKEYS,25,94,11,14 EDITTEXT IDC_USAGE_END,58,94,19,14,ES_AUTOHSCROLL | ES_NUMBER CONTROL "",IDC_USAGE_END_SPINNER,"msctls_updown32",UDS_SETBUDDYINT | UDS_AUTOBUDDY | UDS_ARROWKEYS,77,94,11,14 LTEXT "% to",IDC_STATIC,39,97,16,8 CONTROL "&Device:",IDC_DEVID,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,6,34,10 COMBOBOX IDC_DEVID_COMBO,141,6,155,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP LTEXT "%",IDC_STATIC,91,97,8,8 CONTROL "Device &range:",IDC_DRANGE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,19,59,10 EDITTEXT IDC_DRANGE_END,159,32,40,14,ES_AUTOHSCROLL | ES_NUMBER LTEXT "to",IDC_STATIC,148,34,8,8 EDITTEXT IDC_DRANGE_START,104,32,40,14,ES_AUTOHSCROLL | ES_NUMBER CONTROL "&Virtual range:",IDC_VRANGE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,49,59,10 EDITTEXT IDC_VRANGE_END,160,62,40,14,ES_AUTOHSCROLL | ES_NUMBER LTEXT "to",IDC_STATIC,148,64,8,8 EDITTEXT IDC_VRANGE_START,104,62,40,14,ES_AUTOHSCROLL | ES_NUMBER CONTROL "&Limit:",IDC_LIMIT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,104,80,33,10 EDITTEXT IDC_LIMIT_START,104,94,19,14,ES_AUTOHSCROLL | ES_NUMBER CONTROL "",IDC_LIMIT_START_SPINNER,"msctls_updown32",UDS_SETBUDDYINT | UDS_AUTOBUDDY | UDS_ARROWKEYS,123,94,11,14 EDITTEXT IDC_LIMIT_END,150,94,19,14,ES_AUTOHSCROLL | ES_NUMBER CONTROL "",IDC_LIMIT_END_SPINNER,"msctls_updown32",UDS_SETBUDDYINT | UDS_AUTOBUDDY | UDS_ARROWKEYS,169,94,11,14 LTEXT "to",IDC_STATIC,139,97,8,8 CONTROL "&Stripes:",IDC_STRIPES,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,210,19,40,10 EDITTEXT IDC_STRIPES_START,210,32,19,14,ES_AUTOHSCROLL | ES_NUMBER CONTROL "",IDC_STRIPES_START_SPINNER,"msctls_updown32",UDS_SETBUDDYINT | UDS_AUTOBUDDY | UDS_ARROWKEYS,228,32,11,14 EDITTEXT IDC_STRIPES_END,253,32,19,14,ES_AUTOHSCROLL | ES_NUMBER CONTROL "",IDC_STRIPES_END_SPINNER,"msctls_updown32",UDS_SETBUDDYINT | UDS_AUTOBUDDY | UDS_ARROWKEYS,272,32,11,14 LTEXT "to",IDC_STATIC,242,35,8,8 CONTROL "&Convert:",IDC_CONVERT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,210,49,36,10 COMBOBOX IDC_CONVERT_COMBO,248,49,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP CONTROL "So&ft",IDC_SOFT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,210,64,28,10 END IDD_BALANCE DIALOGEX 0, 0, 254, 167 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Balance" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "OK",IDOK,102,146,50,14 CONTROL "",IDC_BALANCE_PROGRESS,"msctls_progress32",WS_BORDER,7,95,240,14 CONTROL "&Data",IDC_DATA,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,30,10 CONTROL "&Metadata",IDC_METADATA,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,28,45,10 CONTROL "&System",IDC_SYSTEM,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,51,38,10 PUSHBUTTON "Options...",IDC_DATA_OPTIONS,70,6,50,14 PUSHBUTTON "Options...",IDC_METADATA_OPTIONS,70,26,50,14 PUSHBUTTON "Options...",IDC_SYSTEM_OPTIONS,70,47,50,14 LTEXT "Status",IDC_BALANCE_STATUS,8,80,239,8 PUSHBUTTON "&Start balance",IDC_START_BALANCE,13,117,69,14 PUSHBUTTON "&Pause / resume",IDC_PAUSE_BALANCE,93,117,69,14 PUSHBUTTON "&Cancel balance",IDC_CANCEL_BALANCE,173,117,69,14 END IDD_DEVICES DIALOGEX 0, 0, 318, 203 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Devices" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "OK",IDOK,207,182,50,14 PUSHBUTTON "Cancel",IDCANCEL,261,182,50,14 CONTROL "",IDC_DEVLIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,304,144 PUSHBUTTON "&Add device...",IDC_DEVICE_ADD,7,182,76,14 PUSHBUTTON "&Refresh",IDC_DEVICE_REFRESH,7,159,76,14 PUSHBUTTON "Remove &device",IDC_DEVICE_REMOVE,93,182,76,14 PUSHBUTTON "Show &stats...",IDC_DEVICE_SHOW_STATS,235,159,76,14,WS_DISABLED PUSHBUTTON "Re&size...",IDC_DEVICE_RESIZE,149,159,76,14,WS_DISABLED END IDD_DEVICE_ADD DIALOGEX 0, 0, 261, 185 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Add device" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "OK",IDOK,66,164,50,14 PUSHBUTTON "Cancel",IDCANCEL,145,164,50,14 CONTROL "",IDC_DEVICE_TREE,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_SHOWSELALWAYS | WS_BORDER | WS_HSCROLL | WS_TABSTOP,7,7,247,148 END IDD_SCRUB DIALOGEX 0, 0, 254, 162 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Scrub" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "OK",IDOK,102,141,50,14 CONTROL "",IDC_SCRUB_PROGRESS,"msctls_progress32",WS_BORDER,7,95,240,14 LTEXT "Status",IDC_SCRUB_STATUS,8,81,239,8 PUSHBUTTON "&Start scrub",IDC_START_SCRUB,13,117,69,14 PUSHBUTTON "&Pause / resume",IDC_PAUSE_SCRUB,93,117,69,14 PUSHBUTTON "&Cancel scrub",IDC_CANCEL_SCRUB,173,117,69,14 EDITTEXT IDC_SCRUB_INFO,7,7,240,69,ES_MULTILINE | ES_READONLY | WS_VSCROLL END IDD_DEVICE_STATS DIALOGEX 0, 0, 159, 113 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Device stats" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "OK",IDOK,24,91,50,14 LTEXT "Device %llx:",IDC_DEVICE_ID,7,7,40,8 LTEXT "Write errors:",IDC_STATIC,7,21,79,8 LTEXT "Read errors:",IDC_STATIC,7,34,79,8 LTEXT "Flush errors:",IDC_STATIC,7,47,79,8 LTEXT "Corruption errors:",IDC_STATIC,7,60,79,8 LTEXT "Generation errors:",IDC_STATIC,7,73,79,8 RTEXT "%llu",IDC_WRITE_ERRS,87,21,65,8 RTEXT "%llu",IDC_READ_ERRS,87,34,65,8 RTEXT "%llu",IDC_FLUSH_ERRS,87,47,65,8 RTEXT "%llu",IDC_CORRUPTION_ERRS,87,60,65,8 RTEXT "%llu",IDC_GENERATION_ERRS,87,73,65,8 PUSHBUTTON "&Reset",IDC_RESET_STATS,85,91,50,14 END IDD_RECV_PROGRESS DIALOGEX 0, 0, 311, 83 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Receiving subvolume" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN PUSHBUTTON "Cancel",IDCANCEL,130,62,50,14 CONTROL "",IDC_RECV_PROGRESS,"msctls_progress32",WS_BORDER,7,33,297,24 LTEXT "Receiving subvolume...",IDC_RECV_MSG,7,7,297,18 END IDD_SEND_SUBVOL DIALOGEX 0, 0, 288, 149 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Send subvolume" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "&Write",IDOK,83,128,50,14 PUSHBUTTON "&Close",IDCANCEL,156,128,50,14 EDITTEXT IDC_STREAM_DEST,57,7,166,14,ES_AUTOHSCROLL LTEXT "Stream:",IDC_STATIC,7,11,26,8 PUSHBUTTON "&Browse...",IDC_BROWSE,231,7,50,14 LTEXT "Select a destination for the subvolume stream.",IDC_SEND_STATUS,7,93,274,22 CONTROL "Incremental",IDC_INCREMENTAL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,32,54,10 EDITTEXT IDC_PARENT_SUBVOL,69,29,154,14,ES_AUTOHSCROLL | WS_DISABLED PUSHBUTTON "&Browse...",IDC_PARENT_BROWSE,231,29,50,14,WS_DISABLED LTEXT "Clone sources:",IDC_STATIC,7,52,46,8 LISTBOX IDC_CLONE_LIST,69,50,154,36,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP PUSHBUTTON "&Add...",IDC_CLONE_ADD,231,50,50,14 PUSHBUTTON "&Remove",IDC_CLONE_REMOVE,231,69,50,14,WS_DISABLED END IDD_RESIZE DIALOGEX 0, 0, 279, 133 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Resize device" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "OK",IDOK,81,112,50,14 PUSHBUTTON "Cancel",IDCANCEL,148,112,50,14 LTEXT "Device %llx:",IDC_RESIZE_DEVICE_ID,18,21,238,8 LTEXT "Current size: %s",IDC_RESIZE_CURSIZE,18,37,238,8 CONTROL "",IDC_RESIZE_SLIDER,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,7,74,265,24 LTEXT "New size: %s",IDC_RESIZE_NEWSIZE,18,53,238,8 END IDD_DRIVE_LETTER DIALOGEX 0, 0, 131, 61 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Change drive letter" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "OK",IDOK,7,40,50,14 PUSHBUTTON "Cancel",IDCANCEL,74,40,50,14 COMBOBOX IDC_DRIVE_LETTER_COMBO,64,17,60,30,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP LTEXT "Drive letter:",IDC_STATIC,15,19,45,8 END IDD_MAPPINGS DIALOGEX 0, 0, 200, 163 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Mappings" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "OK",IDOK,75,145,50,14 CONTROL "",IDC_MAPPINGS_TAB,"SysTabControl32",0x0,0,0,200,142 CONTROL "",IDC_MAPPINGS_LIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,3,15,193,123 END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO BEGIN IDD_PROP_SHEET, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 228 TOPMARGIN, 7 BOTTOMMARGIN, 266 END IDD_SIZE_DETAILS, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 205 TOPMARGIN, 7 BOTTOMMARGIN, 93 END IDD_VOL_PROP_SHEET, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 228 TOPMARGIN, 7 BOTTOMMARGIN, 266 END IDD_VOL_USAGE, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 228 TOPMARGIN, 7 BOTTOMMARGIN, 235 END IDD_BALANCE_OPTIONS, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 296 TOPMARGIN, 7 BOTTOMMARGIN, 131 END IDD_BALANCE, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 247 TOPMARGIN, 7 BOTTOMMARGIN, 160 END IDD_DEVICES, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 311 TOPMARGIN, 7 BOTTOMMARGIN, 196 END IDD_DEVICE_ADD, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 254 TOPMARGIN, 7 BOTTOMMARGIN, 178 END IDD_SCRUB, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 247 TOPMARGIN, 7 BOTTOMMARGIN, 155 END IDD_DEVICE_STATS, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 152 TOPMARGIN, 7 BOTTOMMARGIN, 105 END IDD_RECV_PROGRESS, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 304 TOPMARGIN, 7 BOTTOMMARGIN, 76 END IDD_SEND_SUBVOL, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 281 TOPMARGIN, 7 BOTTOMMARGIN, 142 END IDD_RESIZE, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 272 TOPMARGIN, 7 BOTTOMMARGIN, 126 END IDD_DRIVE_LETTER, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 124 TOPMARGIN, 7 BOTTOMMARGIN, 54 END END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // RT_MANIFEST // 2 RT_MANIFEST "@CMAKE_CURRENT_SOURCE_DIR@/src/shellext/shellbtrfs.manifest" ///////////////////////////////////////////////////////////////////////////// // // AFX_DIALOG_LAYOUT // IDD_SIZE_DETAILS AFX_DIALOG_LAYOUT BEGIN 0 END IDD_PROP_SHEET AFX_DIALOG_LAYOUT BEGIN 0 END IDD_VOL_PROP_SHEET AFX_DIALOG_LAYOUT BEGIN 0 END IDD_DRIVE_LETTER AFX_DIALOG_LAYOUT BEGIN 0 END IDD_BALANCE_OPTIONS AFX_DIALOG_LAYOUT BEGIN 0 END ///////////////////////////////////////////////////////////////////////////// // // String Table // STRINGTABLE BEGIN IDS_NEW_SUBVOL_HELP_TEXT "Creates a new Btrfs subvolume." IDS_NEW_SUBVOL "&New subvolume" IDS_NEW_SUBVOL_FILENAME "New subvolume" IDS_CREATE_SNAPSHOT "Create snapshot" IDS_CREATE_SNAPSHOT_HELP_TEXT "Creates a snapshot of a Btrfs subvolume." IDS_SNAPSHOT_FILENAME "Snapshot of %s (%04u-%02u-%02u)" IDS_PROP_SHEET_TITLE "Btrfs properties" IDS_INODE_FILE "File" IDS_INODE_DIR "Directory" IDS_INODE_CHAR "Character device (major %llu, minor %llu)" END STRINGTABLE BEGIN IDS_INODE_BLOCK "Block device (major %llu, minor %llu)" IDS_INODE_FIFO "FIFO" IDS_INODE_SOCKET "Socket" IDS_INODE_SYMLINK "Symbolic link" IDS_INODE_UNKNOWN "Unknown inode type %x" IDS_CANNOT_FIND_DEVICE "Cannot find device." IDS_SIZE_BYTE "%s byte" IDS_SIZE_BYTES "%s bytes" IDS_SIZE_KB "%1.1f KB" IDS_SIZE_MB "%1.1f MB" IDS_SIZE_GB "%1.1f GB" IDS_SIZE_TB "%1.1f TB" IDS_SIZE_PB "%1.1f PB" IDS_SIZE_EB "%1.1f EB" IDS_VARIOUS "(various)" IDS_INODE_CHAR_SIMPLE "Character device" END STRINGTABLE BEGIN IDS_INODE_BLOCK_SIMPLE "Block device" IDS_VOL_PROP_SHEET_TITLE "Btrfs" IDS_SIZE_LARGE "%s (%s)" IDS_SINGLE "single" IDS_DUP "DUP" IDS_RAID0 "RAID0" IDS_RAID1 "RAID1" IDS_RAID10 "RAID10" IDS_RAID5 "RAID5" IDS_RAID6 "RAID6" IDS_USAGE_DATA "Data, %s: size: %s, used: %s" IDS_USAGE_MIXED "Data / metadata, %s: size: %s, used: %s" IDS_USAGE_METADATA "Metadata, %s: size: %s, used: %s" IDS_USAGE_SYSTEM "System, %s: size: %s, used: %s" IDS_USAGE_UNALLOC "Unallocated:" IDS_UNKNOWN_DEVICE "(unknown device %llu)" END STRINGTABLE BEGIN IDS_USAGE_DEV_SIZE "Device size:\t\t%s" IDS_USAGE_DEV_ALLOC "Device allocated:\t\t%s" IDS_USAGE_DEV_UNALLOC "Device unallocated:\t\t%s" IDS_USAGE_DATA_RATIO "Data ratio:\t\t%1.2f" IDS_USAGE_METADATA_RATIO "Metadata ratio:\t\t%1.2f" IDS_NO_BALANCE "No balance is currently running." IDS_SINGLE2 "Single" IDS_DEVID_LIST "%llu: %s" IDS_BALANCE_RUNNING "Balance is currently running (%llu out of %llu chunks processed, %1.1f%%)" IDS_DRANGE_END_BEFORE_START "Device range end is before start." IDS_VRANGE_END_BEFORE_START "Virtual range end is before start." IDS_LIMIT_END_BEFORE_START "Limit end is before start." IDS_STRIPES_END_BEFORE_START "Stripes end is before start." IDS_USAGE_END_BEFORE_START "Usage end is before start." IDS_ERROR "Error" IDS_BALANCE_COMPLETE "Balance completed successfully." END STRINGTABLE BEGIN IDS_BALANCE_PAUSED "Balance is currently paused (%llu out of %llu chunks processed, %1.1f%%)" IDS_BALANCE_CANCELLED "Balance cancelled." IDS_DEVLIST_ID "ID" IDS_DEVLIST_DESC "Description" IDS_DEVLIST_READONLY "Read-only" IDS_DEVLIST_SIZE "Size" IDS_DEVLIST_READONLY_YES "Yes" IDS_DEVLIST_READONLY_NO "No" IDS_DEVLIST_ALLOC "Allocated" IDS_DEVLIST_ALLOC_PC "%" IDS_BALANCE_RUNNING_REMOVAL "Currently removing device %llu (%llu out of %llu chunks processed, %1.1f%%)" IDS_BALANCE_PAUSED_REMOVAL "Removal of device %llu paused (%llu out of %llu chunks processed, %1.1f%%)" IDS_BALANCE_CANCELLED_REMOVAL "Device removal cancelled." IDS_BALANCE_COMPLETE_REMOVAL "Device removal completed successfully." IDS_PARTITION "Partition %u" IDS_WHOLE_DISK "Whole disk" END STRINGTABLE BEGIN 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." IDS_REMOVE_DEVICE_CONFIRMATION "Are you sure that you want to remove device %s, %s?" IDS_CONFIRMATION_TITLE "Confirmation" IDS_ADD_DEVICE_CONFIRMATION "Are you sure that you want to add this device?" IDS_ADD_DEVICE_CONFIRMATION_FS "Are you sure that you want to add this device? It already appears to contain a filesystem (%s)." IDS_BALANCE_FAILED "Balance failed (error %08x, %s)" IDS_BALANCE_FAILED_REMOVAL "Device removal failed (error %08x, %s)" IDS_DISK_NUM "Disk %u" IDS_DISK_PART_NUM "Disk %u, partition %u" IDS_NO_SCRUB "Scrub not running." IDS_SCRUB_RUNNING "Scrub currently running (%llu out of %llu chunks processed, %1.1f%%)" IDS_SCRUB_FINISHED "Scrub finished." IDS_SCRUB_PAUSED "Scrub paused (%llu out of %llu chunks processed, %1.1f%%)" IDS_SCRUB_MSG_STARTED "Scrub started at %s %s." IDS_SCRUB_MSG_RECOVERABLE_DATA "Recovered from data checksum error at %llx on device %llx." IDS_SCRUB_MSG_RECOVERABLE_METADATA "Recovered from metadata checksum error at %llx on device %llx." END STRINGTABLE BEGIN IDS_SCRUB_MSG_UNRECOVERABLE_DATA "Unrecoverable data checksum error at %llx on device %llx (%.*s, offset %llx)" IDS_SCRUB_MSG_UNRECOVERABLE_DATA_SUBVOL "Unrecoverable data checksum error at %llx on device %llx (subvol %llx, %.*s, offset %llx)" IDS_SCRUB_MSG_UNRECOVERABLE_METADATA "Unrecoverable metadata checksum error at %llx on device %llx (root %llx, level %x)" IDS_SCRUB_MSG_UNRECOVERABLE_METADATA_FIRSTITEM "Unrecoverable metadata checksum error at %llx on device %llx (root %llx, level %x, first item %llx,%x,%llx)" IDS_SCRUB_MSG_FINISHED "Scrub finished at %s %s." IDS_SCRUB_MSG_SUMMARY "Scrubbed %s in %s (%s/s)." IDS_BALANCE_SCRUB_RUNNING "Cannot start balance while scrub running." IDS_SCRUB_BALANCE_RUNNING "Cannot start scrub while balance running." IDS_SCRUB_MSG_SUMMARY_ERRORS_RECOVERABLE "Recovered from %llu error(s)." IDS_SCRUB_MSG_SUMMARY_ERRORS_UNRECOVERABLE "%llu unrecoverable error(s) found." IDS_SCRUB_FAILED "Scrub failed with error %08x." 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." IDS_SCRUB_MSG_RECOVERABLE_PARITY "Recovered from parity error at %llx on device %llx." IDS_COMPRESS_ANY "(any)" IDS_COMPRESS_ZLIB "Zlib" IDS_COMPRESS_LZO "LZO" END STRINGTABLE BEGIN IDS_STANDALONE_PROPSHEET_TITLE "Inode property sheet" IDS_REFLINK_PASTE "Ref&link Paste" IDS_REFLINK_PASTE_HELP "Do a lightweight copy of files using reference counting." IDS_RECV_SUBVOL "Re&ceive subvolume..." IDS_RECV_SUBVOL_HELP "Recreate a previously exported subvolume." IDS_RECV_CANT_OPEN_FILE "%S: Couldn't open %s (error %u, %s)." IDS_RECV_READFILE_FAILED "ReadFile failed (error %u, %s)." IDS_OUT_OF_MEMORY "Out of memory." IDS_RECV_UNKNOWN_COMMAND "Unrecognized command %u encountered." IDS_RECV_CANT_OPEN_PATH "Couldn't open path %s (error %u, %s)." IDS_RAID1C3 "RAID1C3" IDS_RECV_CREATE_SUBVOL_FAILED "FSCTL_BTRFS_CREATE_SUBVOL returned %08x (%s)." IDS_RECV_MISSING_PARAM "%S: could not find %s parameter." IDS_RECV_SHORT_PARAM "%S: length of parameter %s was %u, expected %u." IDS_RECV_MKNOD_FAILED "FSCTL_BTRFS_MKNOD returned %08x (%s)." IDS_RECV_SET_REPARSE_POINT_FAILED "FSCTL_SET_REPARSE_POINT returned %08x (%s)." END STRINGTABLE BEGIN IDS_RECV_MOVEFILE_FAILED "MoveFile (%s -> %s) failed (error %u, %s)." IDS_RECV_SETFILEPOINTER_FAILED "SetFilePointer failed (error %u, %s)." IDS_RECV_WRITEFILE_FAILED "WriteFile failed (error %u, %s)." IDS_RECV_CREATEHARDLINK_FAILED "CreateHardLink (%s -> %s) failed (error %u, %s)." IDS_RECV_SETENDOFFILE_FAILED "SetEndOfFile failed (error %u, %s)." IDS_RECV_CANT_CREATE_FILE "Couldn't create %s (error %u, %s)." IDS_RAID1C4 "RAID1C4" IDS_RECV_SETINODEINFO_FAILED "FSCTL_BTRFS_SET_INODE_INFO returned %08x (%s)." IDS_RECV_SUCCESS "Received 1 subvolume successfully." IDS_RECV_BUTTON_OK "OK" IDS_RECV_SETFILEATTRIBUTES_FAILED "SetFileAttributes failed (error %u, %s)." IDS_RECV_GETFILEATTRIBUTES_FAILED "GetFileAttributes failed (error %u, %s)." IDS_RECV_CSUM_ERROR "Checksum error." IDS_RECV_NOT_A_SEND_STREAM "File was not a send stream." IDS_RECV_UNSUPPORTED_VERSION "Unsupported version %u." IDS_RECV_SETEAFILE_FAILED "NtSetEaFile returned %08x (%s)." END STRINGTABLE BEGIN IDS_RECV_RECEIVED_SUBVOL_FAILED "FSCTL_BTRFS_RECEIVED_SUBVOL returned %08x (%s)." IDS_RECV_SETSECURITYOBJECT_FAILED "NtSetSecurityObject returned %08x (%s)." IDS_RECV_SETXATTR_FAILED "FSCTL_BTRFS_SET_XATTR returned %08x (%s)." IDS_RECV_CREATETHREAD_FAILED "CreateThread failed (error %u, %s)." IDS_RECV_FILE_TRUNCATED "File was truncated." IDS_RECV_RESERVE_SUBVOL_FAILED "FSCTL_BTRFS_RESERVE_SUBVOL returned %08x (%s)." IDS_RECV_CANCELLED "Receiving cancelled." IDS_RECV_CANT_FIND_PARENT_SUBVOL "Could not find parent subvolume." IDS_RECV_FIND_SUBVOL_FAILED "FSCTL_BTRFS_FIND_SUBVOL returned %08x (%s)." IDS_RECV_CREATE_SNAPSHOT_FAILED "FSCTL_BTRFS_CREATE_SNAPSHOT returned %08x (%s)." IDS_RECV_GETVOLUMEPATHNAME_FAILED "GetVolumePathName failed (error %u, %s)." IDS_RECV_DELETEFILE_FAILED "DeleteFile failed for %s (error %u, %s)." IDS_RECV_REMOVEDIRECTORY_FAILED "RemoveDirectory failed for %s (error %u, %s)." IDS_RECV_CANT_FIND_CLONE_SUBVOL "Could not find clone subvolume." IDS_RECV_GETFILESIZEEX_FAILED "GetFileSizeEx failed (error %u, %s)." IDS_RECV_DUPLICATE_EXTENTS_FAILED "FSCTL_DUPLICATE_EXTENTS_TO_FILE returned %08x (%s)." END STRINGTABLE BEGIN IDS_RECV_SUCCESS_PLURAL "Received %u subvolumes successfully." IDS_SEND_SUBVOL "&Send subvolume..." IDS_SEND_SUBVOL_HELP "Exports a subvolume so that it can be recreated on another volume." IDS_SEND_CANT_OPEN_FILE "Error opening file %s (error %u, %s)." IDS_SEND_CANT_OPEN_DIR "Error opening directory %s (error %u, %s)." IDS_SEND_FSCTL_BTRFS_SEND_SUBVOL_FAILED "FSCTL_BTRFS_SEND_SUBVOL returned error %08x (%s)." IDS_SEND_FSCTL_BTRFS_READ_SEND_BUFFER_FAILED "FSCTL_BTRFS_READ_SEND_BUFFER returned error %08x (%s)." IDS_SEND_SUCCESS "Stream written successfully." IDS_SEND_WRITEFILE_FAILED "Writing to file failed (error %u, %s)." IDS_SEND_GET_FILE_INFO_FAILED "GetFileInformationByHandle failed (error %u, %s)." IDS_SEND_NOT_READONLY "Subvolume not readonly." IDS_NOT_SUBVOL "Directory was not a subvolume." IDS_GET_FILE_IDS_FAILED "FSCTL_BTRFS_GET_FILE_IDS returned error %08x (%s)." IDS_SHPARSEDISPLAYNAME_FAILED "SHParseDisplayName failed." IDS_SHGETPATHFROMIDLIST_FAILED "SHGetPathFromIDList failed." IDS_SEND_PARENT_NOT_READONLY "Parent subvolume not readonly." END STRINGTABLE BEGIN IDS_SEND_CANCEL "&Cancel" IDS_SEND_WRITING "Writing..." IDS_MISSING "(missing)" IDS_RESIZE_SUCCESSFUL "Device %llx successfully resized to %s." IDS_BALANCE_RUNNING_SHRINK "Currently shrinking device %llu (%llu out of %llu chunks processed, %1.1f%%)" IDS_BALANCE_PAUSED_SHRINK "Shrinking of device %llu paused (%llu out of %llu chunks processed, %1.1f%%)" IDS_BALANCE_CANCELLED_SHRINK "Device shrinking cancelled." IDS_BALANCE_COMPLETE_SHRINK "Device successfully shrunk." IDS_BALANCE_FAILED_SHRINK "Device shrinking failed (error %08x, %s)" IDS_COMPRESS_ZSTD "Zstd" IDS_REGCREATEKEY_FAILED "RegCreateKey returned %08x" IDS_REGSETVALUEEX_FAILED "RegSetValueEx returned %08x" IDS_REGCLOSEKEY_FAILED "RegCloseKey returned %08x" IDS_CANT_REFLINK_DIFFERENT_FS "Cannot create a reflink between two different filesystems." END STRINGTABLE BEGIN IDS_INITCOMMONCONTROLSEX_FAILED "InitCommonControlsEx failed." IDS_CANT_OPEN_MOUNTMGR "Could not get a handle to mount manager." IDS_TVM_INSERTITEM_FAILED "TVM_INSERTITEM failed." IDS_RECV_PATH_TOO_LONG "%S: path was too long." IDS_MAPPINGS_UID_MAPPINGS "UID mappings" IDS_MAPPINGS_GID_MAPPINGS "GID mappings" IDS_MAPPINGS_PRINCIPAL "Principal" IDS_MAPPINGS_UID "UID" IDS_MAPPINGS_GID "GID" END #endif // English (United Kingdom) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: src/shellext/shellext.h ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #pragma once #define ISOLATION_AWARE_ENABLED 1 #define STRSAFE_NO_DEPRECATE #define WINVER 0x0A00 // Windows 10 #define _WIN32_WINNT 0x0A00 #include #include #include #include #include #include "../btrfs.h" #include "../btrfsioctl.h" using namespace std; #define STATUS_SUCCESS (NTSTATUS)0x00000000 #define STATUS_BUFFER_OVERFLOW (NTSTATUS)0x80000005 #define STATUS_END_OF_FILE (NTSTATUS)0xc0000011 #define STATUS_MORE_PROCESSING_REQUIRED (NTSTATUS)0xc0000016 #define STATUS_BUFFER_TOO_SMALL (NTSTATUS)0xc0000023 #define STATUS_DEVICE_NOT_READY (NTSTATUS)0xc00000a3 #define STATUS_CANNOT_DELETE (NTSTATUS)0xc0000121 #define STATUS_NOT_FOUND (NTSTATUS)0xc0000225 #define BLOCK_FLAG_DATA 0x001 #define BLOCK_FLAG_SYSTEM 0x002 #define BLOCK_FLAG_METADATA 0x004 #define BLOCK_FLAG_RAID0 0x008 #define BLOCK_FLAG_RAID1 0x010 #define BLOCK_FLAG_DUPLICATE 0x020 #define BLOCK_FLAG_RAID10 0x040 #define BLOCK_FLAG_RAID5 0x080 #define BLOCK_FLAG_RAID6 0x100 #define BTRFS_TYPE_FILE 1 #define BTRFS_TYPE_DIRECTORY 2 #define BTRFS_TYPE_CHARDEV 3 #define BTRFS_TYPE_BLOCKDEV 4 #define BTRFS_TYPE_FIFO 5 #define BTRFS_TYPE_SOCKET 6 #define BTRFS_TYPE_SYMLINK 7 #ifdef _MSC_VER #define funcname __FUNCTION__ #else #define funcname __func__ #endif #ifdef _MSC_VER #pragma warning(disable: 4800) #endif extern "C" { NTSTATUS NTAPI NtReadFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key); NTSTATUS WINAPI NtSetEaFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length); NTSTATUS WINAPI NtSetSecurityObject(HANDLE Handle, SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR SecurityDescriptor); #ifdef _MSC_VER NTSYSCALLAPI NTSTATUS NTAPI NtFsControlFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, ULONG FsControlCode, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength); NTSTATUS NTAPI NtQueryInformationFile(HANDLE hFile, PIO_STATUS_BLOCK io, PVOID ptr, ULONG len, FILE_INFORMATION_CLASS FileInformationClass); NTSTATUS NTAPI NtSetInformationFile(HANDLE hFile, PIO_STATUS_BLOCK io, PVOID ptr, ULONG len, FILE_INFORMATION_CLASS FileInformationClass); #define FileBasicInformation (FILE_INFORMATION_CLASS)4 #define FileStandardInformation (FILE_INFORMATION_CLASS)5 #define FileDispositionInformation (FILE_INFORMATION_CLASS)13 #define FileEndOfFileInformation (FILE_INFORMATION_CLASS)20 #define FileStreamInformation (FILE_INFORMATION_CLASS)22 typedef enum _FSINFOCLASS { FileFsVolumeInformation = 1, FileFsLabelInformation, FileFsSizeInformation, FileFsDeviceInformation, FileFsAttributeInformation, FileFsControlInformation, FileFsFullSizeInformation, FileFsObjectIdInformation, FileFsDriverPathInformation, FileFsVolumeFlagsInformation, FileFsSectorSizeInformation, FileFsDataCopyInformation, FileFsMetadataSizeInformation, FileFsFullSizeInformationEx, FileFsMaximumInformation } FS_INFORMATION_CLASS, *PFS_INFORMATION_CLASS; typedef struct _FILE_STREAM_INFORMATION { ULONG NextEntryOffset; ULONG StreamNameLength; LARGE_INTEGER StreamSize; LARGE_INTEGER StreamAllocationSize; WCHAR StreamName[1]; } FILE_STREAM_INFORMATION, *PFILE_STREAM_INFORMATION; #endif NTSTATUS NTAPI NtQueryVolumeInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FsInformation, ULONG Length, FS_INFORMATION_CLASS FsInformationClass); } typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; }; } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; #define SYMLINK_FLAG_RELATIVE 1 #ifndef _MSC_VER typedef struct _DUPLICATE_EXTENTS_DATA { HANDLE FileHandle; LARGE_INTEGER SourceFileOffset; LARGE_INTEGER TargetFileOffset; LARGE_INTEGER ByteCount; } DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA; typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER { WORD ChecksumAlgorithm; WORD Reserved; DWORD Flags; DWORD ChecksumChunkSizeInBytes; DWORD ClusterSizeInBytes; } FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER; typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER { WORD ChecksumAlgorithm; WORD Reserved; DWORD Flags; } FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER; #endif class win_handle { public: win_handle() { } win_handle(HANDLE nh) { h = nh; } ~win_handle() { if (h != INVALID_HANDLE_VALUE) CloseHandle(h); } operator HANDLE() const { return h; } win_handle& operator=(const HANDLE nh) { if (h != INVALID_HANDLE_VALUE) CloseHandle(h); h = nh; return *this; } HANDLE* operator&() { return &h; } private: HANDLE h = INVALID_HANDLE_VALUE; }; class fff_handle { public: fff_handle() { } fff_handle(HANDLE nh) { h = nh; } ~fff_handle() { if (h != INVALID_HANDLE_VALUE) FindClose(h); } operator HANDLE() const { return h; } fff_handle& operator=(const HANDLE nh) { if (h != INVALID_HANDLE_VALUE) FindClose(h); h = nh; return *this; } HANDLE* operator&() { return &h; } private: HANDLE h = INVALID_HANDLE_VALUE; }; class nt_handle { public: nt_handle() { } nt_handle(HANDLE nh) { h = nh; } ~nt_handle() { if (h != INVALID_HANDLE_VALUE) NtClose(h); } operator HANDLE() const { return h; } nt_handle& operator=(const HANDLE nh) { if (h != INVALID_HANDLE_VALUE) NtClose(h); h = nh; return *this; } HANDLE* operator&() { return &h; } private: HANDLE h = INVALID_HANDLE_VALUE; }; class string_error : public exception { public: string_error(int resno, ...); const char* what() const noexcept { return msg.c_str(); } private: string msg; }; class last_error : public exception { public: last_error(DWORD errnum); const char* what() const noexcept { return msg.c_str(); } private: string msg; }; class ntstatus_error : public exception { public: ntstatus_error(NTSTATUS Status); const char* what() const noexcept { return msg.c_str(); } NTSTATUS Status; private: string msg; }; class global_lock { public: global_lock(HGLOBAL mem) : mem(mem) { ptr = GlobalLock(mem); } ~global_lock() { if (ptr) GlobalUnlock(mem); } void* ptr; private: HGLOBAL mem; }; extern HMODULE module; void format_size(uint64_t size, wstring& s, bool show_bytes); void set_dpi_aware(); wstring format_message(ULONG last_error); wstring format_ntstatus(NTSTATUS Status); bool load_string(HMODULE module, UINT id, wstring& s); void wstring_sprintf(wstring& s, wstring fmt, ...); void command_line_to_args(LPWSTR cmdline, vector& args); wstring utf8_to_utf16(string_view utf8); void error_message(HWND hwnd, const char* msg); ================================================ FILE: src/shellext/volpropsheet.cpp ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #define ISOLATION_AWARE_ENABLED 1 #define STRSAFE_NO_DEPRECATE #include "shellext.h" #include #include #include #define NO_SHLWAPI_STRFCNS #include #include #include "volpropsheet.h" #include "resource.h" #include "mountmgr.h" static const NTSTATUS STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034; HRESULT __stdcall BtrfsVolPropSheet::QueryInterface(REFIID riid, void **ppObj) { if (riid == IID_IUnknown || riid == IID_IShellPropSheetExt) { *ppObj = static_cast(this); AddRef(); return S_OK; } else if (riid == IID_IShellExtInit) { *ppObj = static_cast(this); AddRef(); return S_OK; } *ppObj = nullptr; return E_NOINTERFACE; } HRESULT __stdcall BtrfsVolPropSheet::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY) { ULONG num_files; FORMATETC format = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; WCHAR fnbuf[MAX_PATH]; if (pidlFolder) return E_FAIL; if (!pdtobj) return E_FAIL; stgm.tymed = TYMED_HGLOBAL; if (FAILED(pdtobj->GetData(&format, &stgm))) return E_INVALIDARG; stgm_set = true; global_lock gl(stgm.hGlobal); if (!gl.ptr) { ReleaseStgMedium(&stgm); stgm_set = false; return E_INVALIDARG; } num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0); if (num_files > 1) return E_FAIL; if (DragQueryFileW((HDROP)stgm.hGlobal, 0, fnbuf, sizeof(fnbuf) / sizeof(WCHAR))) { fn = fnbuf; win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; ULONG devsize, i; i = 0; devsize = 1024; devices = (btrfs_device*)malloc(devsize); while (true) { Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_DEVICES, nullptr, 0, devices, devsize); if (Status == STATUS_BUFFER_OVERFLOW) { if (i < 8) { devsize += 1024; free(devices); devices = (btrfs_device*)malloc(devsize); i++; } else return E_FAIL; } else break; } if (!NT_SUCCESS(Status)) return E_FAIL; Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_UUID, nullptr, 0, &uuid, sizeof(BTRFS_UUID)); uuid_set = NT_SUCCESS(Status); ignore = false; balance = new BtrfsBalance(fn); } else return E_FAIL; } else return E_FAIL; return S_OK; } typedef struct { uint64_t dev_id; wstring name; uint64_t alloc; uint64_t size; } dev; void BtrfsVolPropSheet::FormatUsage(HWND, wstring& s, btrfs_usage* usage) { uint8_t i, j; uint64_t num_devs, dev_size, dev_alloc, data_size, data_alloc, metadata_size, metadata_alloc; btrfs_device* bd; vector devs; btrfs_usage* bue; wstring t, u, v; static const uint64_t types[] = { BLOCK_FLAG_DATA, BLOCK_FLAG_DATA | BLOCK_FLAG_METADATA, BLOCK_FLAG_METADATA, BLOCK_FLAG_SYSTEM }; static const ULONG typestrings[] = { IDS_USAGE_DATA, IDS_USAGE_MIXED, IDS_USAGE_METADATA, IDS_USAGE_SYSTEM }; static const uint64_t duptypes[] = { 0, BLOCK_FLAG_DUPLICATE, BLOCK_FLAG_RAID0, BLOCK_FLAG_RAID1, BLOCK_FLAG_RAID10, BLOCK_FLAG_RAID5, BLOCK_FLAG_RAID6, BLOCK_FLAG_RAID1C3, BLOCK_FLAG_RAID1C4 }; static const ULONG dupstrings[] = { IDS_SINGLE, IDS_DUP, IDS_RAID0, IDS_RAID1, IDS_RAID10, IDS_RAID5, IDS_RAID6, IDS_RAID1C3, IDS_RAID1C4 }; static const uint64_t raid_types = BLOCK_FLAG_DUPLICATE | BLOCK_FLAG_RAID0 | BLOCK_FLAG_RAID1 | BLOCK_FLAG_RAID10 | BLOCK_FLAG_RAID5 | BLOCK_FLAG_RAID6 | BLOCK_FLAG_RAID1C3 | BLOCK_FLAG_RAID1C4; s = L""; num_devs = 0; bd = devices; while (true) { num_devs++; if (bd->next_entry > 0) bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry); else break; } bd = devices; dev_size = 0; while (true) { dev d; if (bd->missing) { if (!load_string(module, IDS_MISSING, d.name)) throw last_error(GetLastError()); } else if (bd->device_number == 0xffffffff) d.name = wstring(bd->name, bd->namelen / sizeof(WCHAR)); else if (bd->partition_number == 0) { if (!load_string(module, IDS_DISK_NUM, u)) throw last_error(GetLastError()); wstring_sprintf(d.name, u, bd->device_number); } else { if (!load_string(module, IDS_DISK_PART_NUM, u)) throw last_error(GetLastError()); wstring_sprintf(d.name, u, bd->device_number, bd->partition_number); } d.dev_id = bd->dev_id; d.alloc = 0; d.size = bd->size; devs.push_back(d); dev_size += bd->size; if (bd->next_entry > 0) bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry); else break; } dev_alloc = 0; data_size = data_alloc = 0; metadata_size = metadata_alloc = 0; bue = usage; while (true) { for (uint64_t k = 0; k < bue->num_devices; k++) { dev_alloc += bue->devices[k].alloc; if (bue->type & BLOCK_FLAG_DATA) { data_alloc += bue->devices[k].alloc; } if (bue->type & BLOCK_FLAG_METADATA) { metadata_alloc += bue->devices[k].alloc; } } if (bue->type & BLOCK_FLAG_DATA) data_size += bue->size; if (bue->type & BLOCK_FLAG_METADATA) metadata_size += bue->size; if (bue->next_entry > 0) bue = (btrfs_usage*)((uint8_t*)bue + bue->next_entry); else break; } // device size if (!load_string(module, IDS_USAGE_DEV_SIZE, u)) throw last_error(GetLastError()); format_size(dev_size, v, false); wstring_sprintf(t, u, v.c_str()); s += t + L"\r\n"; // device allocated if (!load_string(module, IDS_USAGE_DEV_ALLOC, u)) throw last_error(GetLastError()); format_size(dev_alloc, v, false); wstring_sprintf(t, u, v.c_str()); s += t + L"\r\n"s; // device unallocated if (!load_string(module, IDS_USAGE_DEV_UNALLOC, u)) throw last_error(GetLastError()); format_size(dev_size - dev_alloc, v, false); wstring_sprintf(t, u, v.c_str()); s += t + L"\r\n"s; // data ratio if (data_alloc > 0) { if (!load_string(module, IDS_USAGE_DATA_RATIO, u)) throw last_error(GetLastError()); wstring_sprintf(t, u, (float)data_alloc / (float)data_size); s += t + L"\r\n"s; } // metadata ratio if (!load_string(module, IDS_USAGE_METADATA_RATIO, u)) throw last_error(GetLastError()); wstring_sprintf(t, u, (float)metadata_alloc / (float)metadata_size); s += t + L"\r\n\r\n"; for (i = 0; i < sizeof(types) / sizeof(types[0]); i++) { for (j = 0; j < sizeof(duptypes) / sizeof(duptypes[0]); j++) { bue = usage; while (true) { if ((bue->type & types[i]) == types[i] && ((duptypes[j] == 0 && (bue->type & raid_types) == 0) || bue->type & duptypes[j])) { wstring sizestring, usedstring, typestring, dupstring; if (bue->type & BLOCK_FLAG_DATA && bue->type & BLOCK_FLAG_METADATA && (types[i] == BLOCK_FLAG_DATA || types[i] == BLOCK_FLAG_METADATA)) break; if (!load_string(module, typestrings[i], typestring)) throw last_error(GetLastError()); if (!load_string(module, dupstrings[j], dupstring)) throw last_error(GetLastError()); format_size(bue->size, sizestring, false); format_size(bue->used, usedstring, false); wstring_sprintf(t, typestring, dupstring.c_str(), sizestring.c_str(), usedstring.c_str()); s += t + L"\r\n"; for (uint64_t k = 0; k < bue->num_devices; k++) { bool found = false; format_size(bue->devices[k].alloc, sizestring, false); for (size_t l = 0; l < min((uint64_t)SIZE_MAX, num_devs); l++) { if (devs[l].dev_id == bue->devices[k].dev_id) { s += devs[l].name + L"\t" + sizestring + L"\r\n"; devs[l].alloc += bue->devices[k].alloc; found = true; break; } } if (!found) { if (!load_string(module, IDS_UNKNOWN_DEVICE, typestring)) throw last_error(GetLastError()); wstring_sprintf(t, typestring, bue->devices[k].dev_id); s += t + L"\t"s + sizestring + L"\r\n"s; } } s += L"\r\n"; break; } if (bue->next_entry > 0) bue = (btrfs_usage*)((uint8_t*)bue + bue->next_entry); else break; } } } if (!load_string(module, IDS_USAGE_UNALLOC, t)) throw last_error(GetLastError()); s += t + L"\r\n"s; for (size_t k = 0; k < min((uint64_t)SIZE_MAX, num_devs); k++) { wstring sizestring; format_size(devs[k].size - devs[k].alloc, sizestring, false); s += devs[k].name + L"\t" + sizestring + L"\r\n"; } } void BtrfsVolPropSheet::RefreshUsage(HWND hwndDlg) { wstring s; btrfs_usage* usage; win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; ULONG devsize, usagesize, i; i = 0; devsize = 1024; devices = (btrfs_device*)malloc(devsize); while (true) { Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_DEVICES, nullptr, 0, devices, devsize); if (Status == STATUS_BUFFER_OVERFLOW) { if (i < 8) { devsize += 1024; free(devices); devices = (btrfs_device*)malloc(devsize); i++; } else return; } else break; } if (!NT_SUCCESS(Status)) return; i = 0; usagesize = 1024; usage = (btrfs_usage*)malloc(usagesize); while (true) { Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_USAGE, nullptr, 0, usage, usagesize); if (Status == STATUS_BUFFER_OVERFLOW) { if (i < 8) { usagesize += 1024; free(usage); usage = (btrfs_usage*)malloc(usagesize); i++; } else return; } else break; } if (!NT_SUCCESS(Status)) { free(usage); return; } ignore = false; } else return; FormatUsage(hwndDlg, s, usage); SetDlgItemTextW(hwndDlg, IDC_USAGE_BOX, s.c_str()); free(usage); } INT_PTR CALLBACK BtrfsVolPropSheet::UsageDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) { try { switch (uMsg) { case WM_INITDIALOG: { wstring s; int i; ULONG usagesize; NTSTATUS Status; IO_STATUS_BLOCK iosb; EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { btrfs_usage* usage; i = 0; usagesize = 1024; usage = (btrfs_usage*)malloc(usagesize); while (true) { Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_USAGE, nullptr, 0, usage, usagesize); if (Status == STATUS_BUFFER_OVERFLOW) { if (i < 8) { usagesize += 1024; free(usage); usage = (btrfs_usage*)malloc(usagesize); i++; } else break; } else break; } if (!NT_SUCCESS(Status)) { free(usage); break; } FormatUsage(hwndDlg, s, usage); SetDlgItemTextW(hwndDlg, IDC_USAGE_BOX, s.c_str()); free(usage); } break; } case WM_COMMAND: switch (HIWORD(wParam)) { case BN_CLICKED: switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: EndDialog(hwndDlg, 0); return true; case IDC_USAGE_REFRESH: RefreshUsage(hwndDlg); return true; } break; } break; } } catch (const exception& e) { error_message(hwndDlg, e.what()); } return false; } static INT_PTR CALLBACK stub_UsageDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { BtrfsVolPropSheet* bvps; if (uMsg == WM_INITDIALOG) { SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); bvps = (BtrfsVolPropSheet*)lParam; } else { bvps = (BtrfsVolPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); } if (bvps) return bvps->UsageDlgProc(hwndDlg, uMsg, wParam, lParam); else return false; } void BtrfsVolPropSheet::ShowUsage(HWND hwndDlg) { DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_VOL_USAGE), hwndDlg, stub_UsageDlgProc, (LPARAM)this); } static void add_lv_column(HWND list, int string, int cx) { LVCOLUMNW lvc; wstring s; if (!load_string(module, string, s)) throw last_error(GetLastError()); lvc.mask = LVCF_TEXT|LVCF_WIDTH; lvc.pszText = (WCHAR*)s.c_str(); lvc.cx = cx; SendMessageW(list, LVM_INSERTCOLUMNW, 0, (LPARAM)&lvc); } static int CALLBACK lv_sort(LPARAM lParam1, LPARAM lParam2, LPARAM) { if (lParam1 < lParam2) return -1; else if (lParam1 > lParam2) return 1; else return 0; } static uint64_t find_dev_alloc(uint64_t dev_id, btrfs_usage* usage) { btrfs_usage* bue; uint64_t alloc; alloc = 0; bue = usage; while (true) { uint64_t k; for (k = 0; k < bue->num_devices; k++) { if (bue->devices[k].dev_id == dev_id) alloc += bue->devices[k].alloc; } if (bue->next_entry > 0) bue = (btrfs_usage*)((uint8_t*)bue + bue->next_entry); else break; } return alloc; } void BtrfsVolPropSheet::RefreshDevList(HWND devlist) { NTSTATUS Status; IO_STATUS_BLOCK iosb; ULONG usagesize, devsize; btrfs_usage* usage; btrfs_device* bd; int i; uint64_t num_rw_devices; { win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); i = 0; devsize = 1024; if (devices) free(devices); devices = (btrfs_device*)malloc(devsize); while (true) { Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_DEVICES, nullptr, 0, devices, devsize); if (Status == STATUS_BUFFER_OVERFLOW) { if (i < 8) { devsize += 1024; free(devices); devices = (btrfs_device*)malloc(devsize); i++; } else return; } else break; } if (!NT_SUCCESS(Status)) return; bd = devices; i = 0; usagesize = 1024; usage = (btrfs_usage*)malloc(usagesize); while (true) { Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_USAGE, nullptr, 0, usage, usagesize); if (Status == STATUS_BUFFER_OVERFLOW) { if (i < 8) { usagesize += 1024; free(usage); usage = (btrfs_usage*)malloc(usagesize); i++; } else { free(usage); return; } } else break; } if (!NT_SUCCESS(Status)) { free(usage); return; } } SendMessageW(devlist, LVM_DELETEALLITEMS, 0, 0); num_rw_devices = 0; i = 0; while (true) { LVITEMW lvi; wstring s, u; uint64_t alloc; // ID RtlZeroMemory(&lvi, sizeof(LVITEMW)); lvi.mask = LVIF_TEXT | LVIF_PARAM; lvi.iItem = (int)SendMessageW(devlist, LVM_GETITEMCOUNT, 0, 0); lvi.lParam = (LPARAM)bd->dev_id; s = to_wstring(bd->dev_id); lvi.pszText = (LPWSTR)s.c_str(); SendMessageW(devlist, LVM_INSERTITEMW, 0, (LPARAM)&lvi); // description lvi.mask = LVIF_TEXT; lvi.iSubItem = 1; if (bd->missing) { if (!load_string(module, IDS_MISSING, s)) throw last_error(GetLastError()); } else if (bd->device_number == 0xffffffff) s = wstring(bd->name, bd->namelen / sizeof(WCHAR)); else if (bd->partition_number == 0) { if (!load_string(module, IDS_DISK_NUM, u)) throw last_error(GetLastError()); wstring_sprintf(s, u, bd->device_number); } else { if (!load_string(module, IDS_DISK_PART_NUM, u)) throw last_error(GetLastError()); wstring_sprintf(s, u, bd->device_number, bd->partition_number); } lvi.pszText = (LPWSTR)s.c_str(); SendMessageW(devlist, LVM_SETITEMW, 0, (LPARAM)&lvi); // readonly lvi.iSubItem = 2; load_string(module, bd->readonly ? IDS_DEVLIST_READONLY_YES : IDS_DEVLIST_READONLY_NO, s); lvi.pszText = (LPWSTR)s.c_str(); SendMessageW(devlist, LVM_SETITEMW, 0, (LPARAM)&lvi); if (!bd->readonly) num_rw_devices++; // size lvi.iSubItem = 3; format_size(bd->size, s, false); lvi.pszText = (LPWSTR)s.c_str(); SendMessageW(devlist, LVM_SETITEMW, 0, (LPARAM)&lvi); // alloc alloc = find_dev_alloc(bd->dev_id, usage); lvi.iSubItem = 4; format_size(alloc, s, false); lvi.pszText = (LPWSTR)s.c_str(); SendMessageW(devlist, LVM_SETITEMW, 0, (LPARAM)&lvi); // alloc % wstring_sprintf(s, L"%1.1f%%", (float)alloc * 100.0f / (float)bd->size); lvi.iSubItem = 5; lvi.pszText = (LPWSTR)s.c_str(); SendMessageW(devlist, LVM_SETITEMW, 0, (LPARAM)&lvi); i++; if (bd->next_entry > 0) bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry); else break; } free(usage); SendMessageW(devlist, LVM_SORTITEMS, 0, (LPARAM)lv_sort); EnableWindow(GetDlgItem(GetParent(devlist), IDC_DEVICE_ADD), num_rw_devices > 0); EnableWindow(GetDlgItem(GetParent(devlist), IDC_DEVICE_REMOVE), num_rw_devices > 1); } void BtrfsVolPropSheet::ResetStats(HWND hwndDlg) { wstring t, sel; WCHAR modfn[MAX_PATH]; SHELLEXECUTEINFOW sei; sel = to_wstring(stats_dev); GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR)); t = L"\""s + modfn + L"\",ResetStats " + fn + L"|" + sel; RtlZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); sei.hwnd = hwndDlg; sei.lpVerb = L"runas"; sei.lpFile = L"rundll32.exe"; sei.lpParameters = t.c_str(); sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExW(&sei)) throw last_error(GetLastError()); WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h != INVALID_HANDLE_VALUE) { NTSTATUS Status; IO_STATUS_BLOCK iosb; ULONG devsize, i; i = 0; devsize = 1024; free(devices); devices = (btrfs_device*)malloc(devsize); while (true) { Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_DEVICES, nullptr, 0, devices, devsize); if (Status == STATUS_BUFFER_OVERFLOW) { if (i < 8) { devsize += 1024; free(devices); devices = (btrfs_device*)malloc(devsize); i++; } else break; } else break; } } EndDialog(hwndDlg, 0); } INT_PTR CALLBACK BtrfsVolPropSheet::StatsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) { try { switch (uMsg) { case WM_INITDIALOG: { WCHAR s[255]; wstring t; btrfs_device *bd, *dev = nullptr; int i; static int stat_ids[] = { IDC_WRITE_ERRS, IDC_READ_ERRS, IDC_FLUSH_ERRS, IDC_CORRUPTION_ERRS, IDC_GENERATION_ERRS }; bd = devices; while (true) { if (bd->dev_id == stats_dev) { dev = bd; break; } if (bd->next_entry > 0) bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry); else break; } if (!dev) { EndDialog(hwndDlg, 0); throw string_error(IDS_CANNOT_FIND_DEVICE); } GetDlgItemTextW(hwndDlg, IDC_DEVICE_ID, s, sizeof(s) / sizeof(WCHAR)); wstring_sprintf(t, s, dev->dev_id); SetDlgItemTextW(hwndDlg, IDC_DEVICE_ID, t.c_str()); for (i = 0; i < 5; i++) { GetDlgItemTextW(hwndDlg, stat_ids[i], s, sizeof(s) / sizeof(WCHAR)); wstring_sprintf(t, s, dev->stats[i]); SetDlgItemTextW(hwndDlg, stat_ids[i], t.c_str()); } SendMessageW(GetDlgItem(hwndDlg, IDC_RESET_STATS), BCM_SETSHIELD, 0, true); EnableWindow(GetDlgItem(hwndDlg, IDC_RESET_STATS), !readonly); break; } case WM_COMMAND: switch (HIWORD(wParam)) { case BN_CLICKED: switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: EndDialog(hwndDlg, 0); return true; case IDC_RESET_STATS: ResetStats(hwndDlg); return true; } break; } break; } } catch (const exception& e) { error_message(hwndDlg, e.what()); } return false; } static INT_PTR CALLBACK stub_StatsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { BtrfsVolPropSheet* bvps; if (uMsg == WM_INITDIALOG) { SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); bvps = (BtrfsVolPropSheet*)lParam; } else { bvps = (BtrfsVolPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); } if (bvps) return bvps->StatsDlgProc(hwndDlg, uMsg, wParam, lParam); else return false; } void BtrfsVolPropSheet::ShowStats(HWND hwndDlg, uint64_t devid) { stats_dev = devid; DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_DEVICE_STATS), hwndDlg, stub_StatsDlgProc, (LPARAM)this); } INT_PTR CALLBACK BtrfsVolPropSheet::DeviceDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { try { switch (uMsg) { case WM_INITDIALOG: { HWND devlist; RECT rect; ULONG w; EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); devlist = GetDlgItem(hwndDlg, IDC_DEVLIST); ListView_SetExtendedListViewStyleEx(devlist, ListView_GetExtendedListViewStyle(devlist), LVS_EX_FULLROWSELECT); GetClientRect(devlist, &rect); w = rect.right - rect.left; add_lv_column(devlist, IDS_DEVLIST_ALLOC_PC, w * 5 / 44); add_lv_column(devlist, IDS_DEVLIST_ALLOC, w * 6 / 44); add_lv_column(devlist, IDS_DEVLIST_SIZE, w * 6 / 44); add_lv_column(devlist, IDS_DEVLIST_READONLY, w * 7 / 44); add_lv_column(devlist, IDS_DEVLIST_DESC, w * 16 / 44); add_lv_column(devlist, IDS_DEVLIST_ID, w * 4 / 44); SendMessageW(GetDlgItem(hwndDlg, IDC_DEVICE_ADD), BCM_SETSHIELD, 0, true); SendMessageW(GetDlgItem(hwndDlg, IDC_DEVICE_REMOVE), BCM_SETSHIELD, 0, true); SendMessageW(GetDlgItem(hwndDlg, IDC_DEVICE_RESIZE), BCM_SETSHIELD, 0, true); RefreshDevList(devlist); break; } case WM_COMMAND: switch (HIWORD(wParam)) { case BN_CLICKED: switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: KillTimer(hwndDlg, 1); EndDialog(hwndDlg, 0); return true; case IDC_DEVICE_ADD: { wstring t; WCHAR modfn[MAX_PATH]; SHELLEXECUTEINFOW sei; GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR)); t = L"\""s + modfn + L"\",AddDevice "s + fn; RtlZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); sei.hwnd = hwndDlg; sei.lpVerb = L"runas"; sei.lpFile = L"rundll32.exe"; sei.lpParameters = t.c_str(); sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExW(&sei)) throw last_error(GetLastError()); WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); RefreshDevList(GetDlgItem(hwndDlg, IDC_DEVLIST)); return true; } case IDC_DEVICE_REFRESH: RefreshDevList(GetDlgItem(hwndDlg, IDC_DEVLIST)); return true; case IDC_DEVICE_SHOW_STATS: { WCHAR sel[MAX_PATH]; HWND devlist; LVITEMW lvi; devlist = GetDlgItem(hwndDlg, IDC_DEVLIST); auto index = SendMessageW(devlist, LVM_GETNEXTITEM, -1, LVNI_SELECTED); if (index == -1) return true; RtlZeroMemory(&lvi, sizeof(LVITEMW)); lvi.mask = LVIF_TEXT; lvi.iItem = (int)index; lvi.iSubItem = 0; lvi.pszText = sel; lvi.cchTextMax = sizeof(sel) / sizeof(WCHAR); SendMessageW(devlist, LVM_GETITEMW, 0, (LPARAM)&lvi); ShowStats(hwndDlg, _wtoi(sel)); return true; } case IDC_DEVICE_REMOVE: { wstring t, mess, mess2, title; WCHAR modfn[MAX_PATH], sel[MAX_PATH], sel2[MAX_PATH]; HWND devlist; SHELLEXECUTEINFOW sei; LVITEMW lvi; devlist = GetDlgItem(hwndDlg, IDC_DEVLIST); auto index = SendMessageW(devlist, LVM_GETNEXTITEM, -1, LVNI_SELECTED); if (index == -1) return true; RtlZeroMemory(&lvi, sizeof(LVITEMW)); lvi.mask = LVIF_TEXT; lvi.iItem = (int)index; lvi.iSubItem = 0; lvi.pszText = sel; lvi.cchTextMax = sizeof(sel) / sizeof(WCHAR); SendMessageW(devlist, LVM_GETITEMW, 0, (LPARAM)&lvi); lvi.iSubItem = 1; lvi.pszText = sel2; lvi.cchTextMax = sizeof(sel2) / sizeof(WCHAR); SendMessageW(devlist, LVM_GETITEMW, 0, (LPARAM)&lvi); if (!load_string(module, IDS_REMOVE_DEVICE_CONFIRMATION, mess)) throw last_error(GetLastError()); wstring_sprintf(mess2, mess, sel, sel2); if (!load_string(module, IDS_CONFIRMATION_TITLE, title)) throw last_error(GetLastError()); if (MessageBoxW(hwndDlg, mess2.c_str(), title.c_str(), MB_YESNO) != IDYES) return true; GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR)); t = L"\""s + modfn + L"\",RemoveDevice "s + fn + L"|"s + sel; RtlZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); sei.hwnd = hwndDlg; sei.lpVerb = L"runas"; sei.lpFile = L"rundll32.exe"; sei.lpParameters = t.c_str(); sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExW(&sei)) throw last_error(GetLastError()); WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); RefreshDevList(GetDlgItem(hwndDlg, IDC_DEVLIST)); return true; } case IDC_DEVICE_RESIZE: { HWND devlist; LVITEMW lvi; wstring t; WCHAR modfn[MAX_PATH], sel[100]; SHELLEXECUTEINFOW sei; devlist = GetDlgItem(hwndDlg, IDC_DEVLIST); auto index = SendMessageW(devlist, LVM_GETNEXTITEM, -1, LVNI_SELECTED); if (index == -1) return true; RtlZeroMemory(&lvi, sizeof(LVITEMW)); lvi.mask = LVIF_TEXT; lvi.iItem = (int)index; lvi.iSubItem = 0; lvi.pszText = sel; lvi.cchTextMax = sizeof(sel) / sizeof(WCHAR); SendMessageW(devlist, LVM_GETITEMW, 0, (LPARAM)&lvi); GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR)); t = L"\""s + modfn + L"\",ResizeDevice "s + fn + L"|"s + sel; RtlZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); sei.hwnd = hwndDlg; sei.lpVerb = L"runas"; sei.lpFile = L"rundll32.exe"; sei.lpParameters = t.c_str(); sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExW(&sei)) throw last_error(GetLastError()); WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); RefreshDevList(GetDlgItem(hwndDlg, IDC_DEVLIST)); } } break; } break; case WM_NOTIFY: switch (((LPNMHDR)lParam)->code) { case LVN_ITEMCHANGED: { NMLISTVIEW* nmv = (NMLISTVIEW*)lParam; EnableWindow(GetDlgItem(hwndDlg, IDC_DEVICE_SHOW_STATS), nmv->uNewState & LVIS_SELECTED); if (nmv->uNewState & LVIS_SELECTED && !readonly) { HWND devlist; btrfs_device* bd; bool device_readonly = false; LVITEMW lvi; WCHAR sel[MAX_PATH]; uint64_t devid; devlist = GetDlgItem(hwndDlg, IDC_DEVLIST); RtlZeroMemory(&lvi, sizeof(LVITEMW)); lvi.mask = LVIF_TEXT; lvi.iItem = nmv->iItem; lvi.iSubItem = 0; lvi.pszText = sel; lvi.cchTextMax = sizeof(sel) / sizeof(WCHAR); SendMessageW(devlist, LVM_GETITEMW, 0, (LPARAM)&lvi); devid = _wtoi(sel); bd = devices; while (true) { if (bd->dev_id == devid) { device_readonly = bd->readonly; break; } if (bd->next_entry > 0) bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry); else break; } EnableWindow(GetDlgItem(hwndDlg, IDC_DEVICE_RESIZE), !device_readonly); } else EnableWindow(GetDlgItem(hwndDlg, IDC_DEVICE_RESIZE), false); break; } } break; } } catch (const exception& e) { error_message(hwndDlg, e.what()); } return false; } static INT_PTR CALLBACK stub_DeviceDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { BtrfsVolPropSheet* bvps; if (uMsg == WM_INITDIALOG) { SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); bvps = (BtrfsVolPropSheet*)lParam; } else { bvps = (BtrfsVolPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); } if (bvps) return bvps->DeviceDlgProc(hwndDlg, uMsg, wParam, lParam); else return false; } void BtrfsVolPropSheet::ShowDevices(HWND hwndDlg) { DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_DEVICES), hwndDlg, stub_DeviceDlgProc, (LPARAM)this); } void BtrfsVolPropSheet::ShowScrub(HWND hwndDlg) { wstring t; WCHAR modfn[MAX_PATH]; SHELLEXECUTEINFOW sei; GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR)); t = L"\""s + modfn + L"\",ShowScrub "s + fn; RtlZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); sei.hwnd = hwndDlg; sei.lpVerb = L"runas"; sei.lpFile = L"rundll32.exe"; sei.lpParameters = t.c_str(); sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExW(&sei)) throw last_error(GetLastError()); WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); } void BtrfsVolPropSheet::ShowChangeDriveLetter(HWND hwndDlg) { wstring t; WCHAR modfn[MAX_PATH]; SHELLEXECUTEINFOW sei; GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR)); t = L"\""s + modfn + L"\",ShowChangeDriveLetter "s + fn; RtlZeroMemory(&sei, sizeof(sei)); sei.cbSize = sizeof(sei); sei.hwnd = hwndDlg; sei.lpVerb = L"runas"; sei.lpFile = L"rundll32.exe"; sei.lpParameters = t.c_str(); sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExW(&sei)) throw last_error(GetLastError()); WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); } static INT_PTR CALLBACK PropSheetDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { try { switch (uMsg) { case WM_INITDIALOG: { PROPSHEETPAGEW* psp = (PROPSHEETPAGEW*)lParam; BtrfsVolPropSheet* bps = (BtrfsVolPropSheet*)psp->lParam; btrfs_device* bd; EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)bps); bps->readonly = true; bd = bps->devices; while (true) { if (!bd->readonly) { bps->readonly = false; break; } if (bd->next_entry > 0) bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry); else break; } if (bps->uuid_set) { WCHAR s[255]; wstring t; GetDlgItemTextW(hwndDlg, IDC_UUID, s, sizeof(s) / sizeof(WCHAR)); 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], bps->uuid.uuid[6], bps->uuid.uuid[7], bps->uuid.uuid[8], bps->uuid.uuid[9], bps->uuid.uuid[10], bps->uuid.uuid[11], bps->uuid.uuid[12], bps->uuid.uuid[13], bps->uuid.uuid[14], bps->uuid.uuid[15]); SetDlgItemTextW(hwndDlg, IDC_UUID, t.c_str()); } else SetDlgItemTextW(hwndDlg, IDC_UUID, L""); SendMessageW(GetDlgItem(hwndDlg, IDC_VOL_SCRUB), BCM_SETSHIELD, 0, true); SendMessageW(GetDlgItem(hwndDlg, IDC_VOL_CHANGE_DRIVE_LETTER), BCM_SETSHIELD, 0, true); return false; } case WM_NOTIFY: { switch (((LPNMHDR)lParam)->code) { case PSN_KILLACTIVE: SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, false); break; } break; } case WM_COMMAND: { BtrfsVolPropSheet* bps = (BtrfsVolPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); if (bps) { switch (HIWORD(wParam)) { case BN_CLICKED: { switch (LOWORD(wParam)) { case IDC_VOL_SHOW_USAGE: bps->ShowUsage(hwndDlg); break; case IDC_VOL_BALANCE: bps->balance->ShowBalance(hwndDlg); break; case IDC_VOL_DEVICES: bps->ShowDevices(hwndDlg); break; case IDC_VOL_SCRUB: bps->ShowScrub(hwndDlg); break; case IDC_VOL_CHANGE_DRIVE_LETTER: bps->ShowChangeDriveLetter(hwndDlg); break; } } } } break; } } } catch (const exception& e) { error_message(hwndDlg, e.what()); } return false; } HRESULT __stdcall BtrfsVolPropSheet::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) { try { PROPSHEETPAGEW psp; HPROPSHEETPAGE hPage; INITCOMMONCONTROLSEX icex; if (ignore) return S_OK; icex.dwSize = sizeof(icex); icex.dwICC = ICC_LINK_CLASS; if (!InitCommonControlsEx(&icex)) throw string_error(IDS_INITCOMMONCONTROLSEX_FAILED); psp.dwSize = sizeof(psp); psp.dwFlags = PSP_USEREFPARENT | PSP_USETITLE; psp.hInstance = module; psp.pszTemplate = MAKEINTRESOURCEW(IDD_VOL_PROP_SHEET); psp.hIcon = 0; psp.pszTitle = MAKEINTRESOURCEW(IDS_VOL_PROP_SHEET_TITLE); psp.pfnDlgProc = (DLGPROC)PropSheetDlgProc; psp.pcRefParent = (UINT*)&objs_loaded; psp.pfnCallback = nullptr; psp.lParam = (LPARAM)this; hPage = CreatePropertySheetPageW(&psp); if (hPage) { if (pfnAddPage(hPage, lParam)) { this->AddRef(); return S_OK; } else DestroyPropertySheetPage(hPage); } else return E_OUTOFMEMORY; } catch (const exception& e) { error_message(nullptr, e.what()); } return E_FAIL; } HRESULT __stdcall BtrfsVolPropSheet::ReplacePage(UINT, LPFNADDPROPSHEETPAGE, LPARAM) { return S_OK; } void BtrfsChangeDriveLetter::do_change(HWND hwndDlg) { unsigned int sel = (unsigned int)SendDlgItemMessageW(hwndDlg, IDC_DRIVE_LETTER_COMBO, CB_GETCURSEL, 0, 0); if (sel < letters.size()) { wstring dd; if (fn.length() == 3 && fn[1] == L':' && fn[2] == L'\\') { dd = L"\\DosDevices\\?:"; dd[12] = fn[0]; } else throw runtime_error("Volume path was not root of drive."); mountmgr mm; wstring dev_name; { auto v = mm.query_points(dd); if (v.empty()) throw runtime_error("Error finding device name."); dev_name = v[0].device_name; } wstring new_dd = L"\\DosDevices\\?:"; new_dd[12] = letters[sel]; mm.delete_points(dd); try { mm.create_point(new_dd, dev_name); } catch (...) { // if fails, try to recreate old symlink, so we're not left with no drive letter at all mm.create_point(dd, dev_name); throw; } } EndDialog(hwndDlg, 1); } INT_PTR BtrfsChangeDriveLetter::DlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) { try { switch (uMsg) { case WM_INITDIALOG: { HWND cb = GetDlgItem(hwndDlg, IDC_DRIVE_LETTER_COMBO); SendMessageW(cb, CB_RESETCONTENT, 0, 0); mountmgr mm; wstring drv; drv = L"\\DosDevices\\?:"; for (wchar_t l = 'A'; l <= 'Z'; l++) { bool found = true; drv[12] = l; try { auto v = mm.query_points(drv); if (v.empty()) found = false; } catch (const ntstatus_error& ntstatus) { if (ntstatus.Status == STATUS_OBJECT_NAME_NOT_FOUND) found = false; else throw; } if (!found) { wstring str = L"?:"; str[0] = l; letters.push_back(l); SendMessageW(cb, CB_ADDSTRING, 0, reinterpret_cast(str.c_str())); } } break; } case WM_COMMAND: switch (HIWORD(wParam)) { case BN_CLICKED: switch (LOWORD(wParam)) { case IDOK: do_change(hwndDlg); return true; case IDCANCEL: EndDialog(hwndDlg, 0); return true; } break; } break; } } catch (const exception& e) { error_message(hwndDlg, e.what()); } return false; } static INT_PTR __stdcall dlg_proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { BtrfsChangeDriveLetter* bcdl; if (uMsg == WM_INITDIALOG) { SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); bcdl = (BtrfsChangeDriveLetter*)lParam; } else bcdl = (BtrfsChangeDriveLetter*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA); return bcdl->DlgProc(hwndDlg, uMsg, wParam, lParam); } void BtrfsChangeDriveLetter::show() { DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_DRIVE_LETTER), hwnd, dlg_proc, (LPARAM)this); } extern "C" { void CALLBACK ResetStatsW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) { try { win_handle token; NTSTATUS Status; TOKEN_PRIVILEGES tp; LUID luid; uint64_t devid; wstring cmdline, vol, dev; size_t pipe; IO_STATUS_BLOCK iosb; set_dpi_aware(); cmdline = lpszCmdLine; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) throw last_error(GetLastError()); if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid)) throw last_error(GetLastError()); tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) throw last_error(GetLastError()); pipe = cmdline.find(L"|"); if (pipe == string::npos) return; vol = cmdline.substr(0, pipe); dev = cmdline.substr(pipe + 1); devid = _wtoi(dev.c_str()); if (devid == 0) return; win_handle h = CreateFileW(vol.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (h == INVALID_HANDLE_VALUE) throw last_error(GetLastError()); Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RESET_STATS, &devid, sizeof(uint64_t), nullptr, 0); if (!NT_SUCCESS(Status)) throw ntstatus_error(Status); } catch (const exception& e) { error_message(hwnd, e.what()); } } void CALLBACK ShowChangeDriveLetterW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) { BtrfsChangeDriveLetter bcdl(hwnd, lpszCmdLine); bcdl.show(); } } ================================================ FILE: src/shellext/volpropsheet.h ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #pragma once #include #include "../btrfsioctl.h" #include "../btrfs.h" #include "balance.h" #include "scrub.h" extern LONG objs_loaded; class BtrfsVolPropSheet : public IShellExtInit, IShellPropSheetExt { public: BtrfsVolPropSheet() { refcount = 0; ignore = true; stgm_set = false; devices = nullptr; InterlockedIncrement(&objs_loaded); balance = nullptr; } virtual ~BtrfsVolPropSheet() { if (stgm_set) ReleaseStgMedium(&stgm); if (devices) free(devices); InterlockedDecrement(&objs_loaded); if (balance) delete balance; } // IUnknown HRESULT __stdcall QueryInterface(REFIID riid, void **ppObj); ULONG __stdcall AddRef() { return InterlockedIncrement(&refcount); } ULONG __stdcall Release() { LONG rc = InterlockedDecrement(&refcount); if (rc == 0) delete this; return rc; } // IShellExtInit virtual HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) override; // IShellPropSheetExt virtual HRESULT __stdcall AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) override; virtual HRESULT __stdcall ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplacePage, LPARAM lParam) override; void FormatUsage(HWND hwndDlg, wstring& s, btrfs_usage* usage); void RefreshUsage(HWND hwndDlg); void ShowUsage(HWND hwndDlg); INT_PTR CALLBACK UsageDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); void RefreshDevList(HWND devlist); INT_PTR CALLBACK DeviceDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); void ShowDevices(HWND hwndDlg); void ShowScrub(HWND hwndDlg); void ShowChangeDriveLetter(HWND hwndDlg); INT_PTR CALLBACK StatsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); void ShowStats(HWND hwndDlg, uint64_t devid); void ResetStats(HWND hwndDlg); btrfs_device* devices; bool readonly; BtrfsBalance* balance; BTRFS_UUID uuid; bool uuid_set; private: LONG refcount; bool ignore; STGMEDIUM stgm; bool stgm_set; wstring fn; uint64_t stats_dev; }; class BtrfsChangeDriveLetter { public: BtrfsChangeDriveLetter(HWND hwnd, wstring_view fn) : hwnd(hwnd), fn(fn) { } void show(); INT_PTR DlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); private: void do_change(HWND hwndDlg); HWND hwnd; wstring fn; vector letters; }; ================================================ FILE: src/tests/create.cpp ================================================ #include "test.h" #include #define FSCTL_CREATE_OR_GET_OBJECT_ID CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 48, METHOD_BUFFERED, FILE_ANY_ACCESS) using namespace std; static OBJECT_BASIC_INFORMATION query_object_basic_information(HANDLE h) { NTSTATUS Status; OBJECT_BASIC_INFORMATION obi; ULONG len; Status = NtQueryObject(h, ObjectBasicInformation, &obi, sizeof(obi), &len); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (len != sizeof(obi)) throw formatted_error("returned length was {}, expected {}", len, sizeof(obi)); return obi; } template concept has_CreationTime = requires { T::CreationTime; }; template concept has_LastAccessTime = requires { T::LastAccessTime; }; template concept has_LastWriteTime = requires { T::LastWriteTime; }; template concept has_ChangeTime = requires { T::ChangeTime; }; template concept has_EndOfFile = requires { T::EndOfFile; }; template concept has_AllocationSize = requires { T::AllocationSize; }; template concept has_FileAttributes = requires { T::FileAttributes; }; template concept has_FileNameLength = requires { T::FileNameLength; }; template concept has_FileId = requires { T::FileId; }; template static void check_dir_entry(const u16string& dir, u16string_view name, const FILE_BASIC_INFORMATION& fbi, const FILE_STANDARD_INFORMATION& fsi, int64_t file_id, const FILE_ID_128& file_id_128) { auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if constexpr (has_CreationTime) { if (fdi.CreationTime.QuadPart != fbi.CreationTime.QuadPart) throw formatted_error("CreationTime was {}, expected {}.", fdi.CreationTime.QuadPart, fbi.CreationTime.QuadPart); } if constexpr (has_LastAccessTime) { if (fdi.LastAccessTime.QuadPart != fbi.LastAccessTime.QuadPart) throw formatted_error("LastAccessTime was {}, expected {}.", fdi.LastAccessTime.QuadPart, fbi.LastAccessTime.QuadPart); } if constexpr (has_LastWriteTime) { if (fdi.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart) throw formatted_error("LastWriteTime was {}, expected {}.", fdi.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart); } if constexpr (has_ChangeTime) { if (fdi.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart) throw formatted_error("ChangeTime was {}, expected {}.", fdi.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart); } if constexpr (has_EndOfFile) { if (fdi.EndOfFile.QuadPart != fsi.EndOfFile.QuadPart) throw formatted_error("EndOfFile was {}, expected {}.", fdi.EndOfFile.QuadPart, fsi.EndOfFile.QuadPart); } if constexpr (has_AllocationSize) { if (fdi.AllocationSize.QuadPart != fsi.AllocationSize.QuadPart) throw formatted_error("AllocationSize was {}, expected {}.", fdi.AllocationSize.QuadPart, fsi.AllocationSize.QuadPart); } if constexpr (has_FileAttributes) { if (fdi.FileAttributes != fbi.FileAttributes) throw formatted_error("FileAttributes was {}, expected {}.", fdi.FileAttributes, fbi.FileAttributes); } if constexpr (has_FileNameLength) { if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); } if constexpr (has_FileId) { if constexpr (sizeof(T::FileId) == 8) { if (fdi.FileId.QuadPart != file_id) throw formatted_error("FileId was {:x}, expected {:x}.", fdi.FileId.QuadPart, file_id); } else { if (memcmp(&fdi.FileId, &file_id_128, sizeof(FILE_ID_128))) throw runtime_error("FileId was not as expected."); } } // FIXME - EaSize // FIXME - ShortNameLength / ShortName // FIXME - ReparsePointTag } void test_create(HANDLE token, const u16string& dir) { unique_handle h; test("Create file", [&]() { h = create_file(dir + u"\\file", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create duplicate file", [&]() { exp_status([&]() { create_file(dir + u"\\file", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, STATUS_OBJECT_NAME_COLLISION); }); test("Create file differing in case", [&]() { exp_status([&]() { create_file(dir + u"\\FILE", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, STATUS_OBJECT_NAME_COLLISION); }); FILE_BASIC_INFORMATION fbi; test("Check attributes", [&]() { fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE", fbi.FileAttributes); } }); FILE_STANDARD_INFORMATION fsi; test("Check standard information", [&]() { fsi = query_information(h.get()); if (fsi.AllocationSize.QuadPart != 0) throw formatted_error("AllocationSize was {}, expected 0", fsi.AllocationSize.QuadPart); if (fsi.EndOfFile.QuadPart != 0) throw formatted_error("EndOfFile was {}, expected 0", fsi.EndOfFile.QuadPart); if (fsi.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsi.NumberOfLinks); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsi.Directory) throw runtime_error("Directory was true, expected false"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\file"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\file\"."); }); test("Check access information", [&]() { auto fai = query_information(h.get()); ACCESS_MASK exp = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD | FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA | FILE_WRITE_DATA | FILE_READ_DATA; if (fai.AccessFlags != exp) throw formatted_error("AccessFlags was {:x}, expected {:x}", fai.AccessFlags, exp); }); test("Check mode information", [&]() { auto fmi = query_information(h.get()); if (fmi.Mode != 0) throw formatted_error("Mode was {:x}, expected 0", fmi.Mode); }); test("Check alignment information", [&]() { auto fai = query_information(h.get()); if (fai.AlignmentRequirement != FILE_WORD_ALIGNMENT) throw formatted_error("AlignmentRequirement was {:x}, expected FILE_WORD_ALIGNMENT", fai.AlignmentRequirement); }); test("Check position information", [&]() { auto fpi = query_information(h.get()); if (fpi.CurrentByteOffset.QuadPart != 0) throw formatted_error("CurrentByteOffset was {:x}, expected 0", fpi.CurrentByteOffset.QuadPart); }); test("Check attribute tag information", [&]() { auto fati = query_information(h.get()); if (fati.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE", fati.FileAttributes); } if (fati.ReparseTag != 0) throw formatted_error("ReparseTag was {:08x}, expected 0", fati.ReparseTag); }); test("Check compression information", [&]() { auto fci = query_information(h.get()); if (fci.CompressedFileSize.QuadPart != 0) throw formatted_error("CompressedFileSize was {}, expected 0", fci.CompressedFileSize.QuadPart); if (fci.CompressionFormat != 0) throw formatted_error("CompressionFormat was {}, expected 0", fci.CompressionFormat); if (fci.CompressionUnitShift != 0) throw formatted_error("CompressionUnitShift was {}, expected 0", fci.CompressionUnitShift); if (fci.ChunkShift != 0) throw formatted_error("ChunkShift was {}, expected 0", fci.ChunkShift); if (fci.ClusterShift != 0) throw formatted_error("ClusterShift was {}, expected 0", fci.ClusterShift); }); test("Check EA information", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != 0) throw formatted_error("EaSize was {}, expected 0", feai.EaSize); }); int64_t file_id = 0; test("Check internal information", [&]() { auto fii = query_information(h.get()); file_id = fii.IndexNumber.QuadPart; }); test("Check network open information", [&]() { auto fnoi = query_information(h.get()); if (fnoi.CreationTime.QuadPart != fbi.CreationTime.QuadPart) throw formatted_error("CreationTime was {}, expected {}", fnoi.CreationTime.QuadPart, fbi.CreationTime.QuadPart); if (fnoi.LastAccessTime.QuadPart != fbi.LastAccessTime.QuadPart) throw formatted_error("LastAccessTime was {}, expected {}", fnoi.LastAccessTime.QuadPart, fbi.LastAccessTime.QuadPart); if (fnoi.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart) throw formatted_error("LastWriteTime was {}, expected {}", fnoi.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart); if (fnoi.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart) throw formatted_error("ChangeTime was {}, expected {}", fnoi.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart); if (fnoi.AllocationSize.QuadPart != fsi.AllocationSize.QuadPart) throw formatted_error("AllocationSize was {}, expected {}", fnoi.AllocationSize.QuadPart, fsi.AllocationSize.QuadPart); if (fnoi.EndOfFile.QuadPart != fsi.EndOfFile.QuadPart) throw formatted_error("EndOfFile was {}, expected {}", fnoi.EndOfFile.QuadPart, fsi.EndOfFile.QuadPart); if (fnoi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE", fnoi.FileAttributes); } }); test("Try to check normalized name", [&]() { // needs traverse privilege exp_status([&]() { query_file_name_information(h.get(), true); }, STATUS_ACCESS_DENIED); }); test("Check standard link information", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Check FileStatInformation", [&]() { auto fsi2 = query_information(h.get()); if (fsi2.FileId.QuadPart != file_id) throw formatted_error("FileId was {}, expected {}", fsi2.FileId.QuadPart, file_id); if (fsi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart) throw formatted_error("CreationTime was {}, expected {}", fsi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart); if (fsi2.LastAccessTime.QuadPart != fbi.LastAccessTime.QuadPart) throw formatted_error("LastAccessTime was {}, expected {}", fsi2.LastAccessTime.QuadPart, fbi.LastAccessTime.QuadPart); if (fsi2.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart) throw formatted_error("LastWriteTime was {}, expected {}", fsi2.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart); if (fsi2.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart) throw formatted_error("ChangeTime was {}, expected {}", fsi2.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart); if (fsi2.AllocationSize.QuadPart != fsi.AllocationSize.QuadPart) throw formatted_error("AllocationSize was {}, expected {}", fsi2.AllocationSize.QuadPart, fsi.AllocationSize.QuadPart); if (fsi2.EndOfFile.QuadPart != fsi.EndOfFile.QuadPart) throw formatted_error("EndOfFile was {}, expected {}", fsi2.EndOfFile.QuadPart, fsi.EndOfFile.QuadPart); if (fsi2.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_ARCHIVE", fsi2.FileAttributes); if (fsi2.ReparseTag != 0) throw formatted_error("ReparseTag was {:08x}, expected 0", fsi2.ReparseTag); if (fsi2.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsi2.NumberOfLinks); ACCESS_MASK exp = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD | FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA | FILE_WRITE_DATA | FILE_READ_DATA; if (fsi2.EffectiveAccess != exp) throw formatted_error("EffectiveAccess was {:x}, expected {:x}", fsi2.EffectiveAccess, exp); }); test("Check FileStatLxInformation", [&]() { auto fsli = query_information(h.get()); if (fsli.FileId.QuadPart != file_id) throw formatted_error("FileId was {}, expected {}", fsli.FileId.QuadPart, file_id); if (fsli.CreationTime.QuadPart != fbi.CreationTime.QuadPart) throw formatted_error("CreationTime was {}, expected {}", fsli.CreationTime.QuadPart, fbi.CreationTime.QuadPart); if (fsli.LastAccessTime.QuadPart != fbi.LastAccessTime.QuadPart) throw formatted_error("LastAccessTime was {}, expected {}", fsli.LastAccessTime.QuadPart, fbi.LastAccessTime.QuadPart); if (fsli.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart) throw formatted_error("LastWriteTime was {}, expected {}", fsli.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart); if (fsli.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart) throw formatted_error("ChangeTime was {}, expected {}", fsli.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart); if (fsli.AllocationSize.QuadPart != fsi.AllocationSize.QuadPart) throw formatted_error("AllocationSize was {}, expected {}", fsli.AllocationSize.QuadPart, fsi.AllocationSize.QuadPart); if (fsli.EndOfFile.QuadPart != fsi.EndOfFile.QuadPart) throw formatted_error("EndOfFile was {}, expected {}", fsli.EndOfFile.QuadPart, fsi.EndOfFile.QuadPart); if (fsli.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_ARCHIVE", fsli.FileAttributes); if (fsli.ReparseTag != 0) throw formatted_error("ReparseTag was {:08x}, expected 0", fsli.ReparseTag); if (fsli.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsli.NumberOfLinks); ACCESS_MASK exp = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD | FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA | FILE_WRITE_DATA | FILE_READ_DATA; if (fsli.EffectiveAccess != exp) throw formatted_error("EffectiveAccess was {:x}, expected {:x}", fsli.EffectiveAccess, exp); }); FILE_ID_128 file_id_128 = {}; test("Check FileIdInformation", [&]() { auto fidi = query_information(h.get()); file_id_128 = fidi.FileId; }); test("Check FileAllInformation", [&]() { auto buf = query_all_information(h.get()); auto& fai = *static_cast(buf); if (fai.BasicInformation.CreationTime.QuadPart != fbi.CreationTime.QuadPart) throw formatted_error("BasicInformation.CreationTime was {}, expected {}", fai.BasicInformation.CreationTime.QuadPart, fbi.CreationTime.QuadPart); if (fai.BasicInformation.LastAccessTime.QuadPart != fbi.LastAccessTime.QuadPart) throw formatted_error("BasicInformation.LastAccessTime was {}, expected {}", fai.BasicInformation.LastAccessTime.QuadPart, fbi.LastAccessTime.QuadPart); if (fai.BasicInformation.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart) throw formatted_error("BasicInformation.LastWriteTime was {}, expected {}", fai.BasicInformation.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart); if (fai.BasicInformation.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart) throw formatted_error("BasicInformation.ChangeTime was {}, expected {}", fai.BasicInformation.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart); if (fai.BasicInformation.FileAttributes != fbi.FileAttributes) throw formatted_error("BasicInformation.FileAttributes was {:x}, expected {:x}", fai.BasicInformation.FileAttributes, fbi.FileAttributes); if (fai.StandardInformation.AllocationSize.QuadPart != fsi.AllocationSize.QuadPart) throw formatted_error("StandardInformation.AllocationSize was {}, expected {}", fai.StandardInformation.AllocationSize.QuadPart, fsi.AllocationSize.QuadPart); if (fai.StandardInformation.EndOfFile.QuadPart != fsi.EndOfFile.QuadPart) throw formatted_error("StandardInformation.EndOfFile was {}, expected {}", fai.StandardInformation.EndOfFile.QuadPart, fsi.EndOfFile.QuadPart); if (fai.StandardInformation.NumberOfLinks != fsi.NumberOfLinks) throw formatted_error("StandardInformation.NumberOfLinks was {}, expected {}", fai.StandardInformation.NumberOfLinks, fsi.NumberOfLinks); if (!!fai.StandardInformation.DeletePending != !!fsi.DeletePending) throw formatted_error("StandardInformation.DeletePending was {}, expected {}", fai.StandardInformation.DeletePending, fsi.DeletePending); if (!!fai.StandardInformation.Directory != !!fsi.Directory) throw formatted_error("StandardInformation.Directory was {}, expected {}", fai.StandardInformation.Directory, fsi.Directory); if (fai.InternalInformation.IndexNumber.QuadPart != file_id) throw formatted_error("InternalInformation.IndexNumber was {:x}, expected {:x}", fai.InternalInformation.IndexNumber.QuadPart, file_id); if (fai.EaInformation.EaSize != 0) throw formatted_error("EaInformation.EaSize was {:x}, expected 0", fai.EaInformation.EaSize); ACCESS_MASK exp_access = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD | FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA | FILE_WRITE_DATA | FILE_READ_DATA; if (fai.AccessInformation.AccessFlags != exp_access) throw formatted_error("AccessInformation.AccessFlags was {:x}, expected {:x}", fai.AccessInformation.AccessFlags, exp_access); if (fai.PositionInformation.CurrentByteOffset.QuadPart != 0) throw formatted_error("PositionInformation.CurrentByteOffset was {:x}, expected 0", fai.PositionInformation.CurrentByteOffset.QuadPart); if (fai.ModeInformation.Mode != 0) throw formatted_error("ModeInformation.Mode was {:x}, expected 0", fai.ModeInformation.Mode); if (fai.AlignmentInformation.AlignmentRequirement != FILE_WORD_ALIGNMENT) throw formatted_error("AlignmentInformation.AlignmentRequirement was {:x}, expected FILE_WORD_ALIGNMENT", fai.AlignmentInformation.AlignmentRequirement); auto fn = u16string_view((char16_t*)fai.NameInformation.FileName, fai.NameInformation.FileNameLength / sizeof(char16_t)); static const u16string_view ends_with = u"\\file"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\file\"."); }); test("Check FILE_STANDARD_INFORMATION_EX", [&]() { auto fsix = query_information(h.get()); if (fsix.AllocationSize.QuadPart != 0) throw formatted_error("AllocationSize was {}, expected 0", fsix.AllocationSize.QuadPart); if (fsix.EndOfFile.QuadPart != 0) throw formatted_error("EndOfFile was {}, expected 0", fsix.EndOfFile.QuadPart); if (fsix.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsix.NumberOfLinks); if (fsix.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsix.Directory) throw runtime_error("Directory was true, expected false"); if (fsix.AlternateStream) throw runtime_error("AlternateStream was true, expected false"); if (fsix.MetadataAttribute) throw runtime_error("MetadataAttribute was true, expected false"); }); // FIXME - FileHardLinkFullIdInformation (does this work? Undocumented, and seems to always return STATUS_INVALID_PARAMETER on NTFS for 21H2) // FIXME - FileAlternateNameInformation // FIXME - FileSfioReserveInformation // FIXME - FileDesiredStorageClassInformation // FIXME - FileStorageReserveIdInformation // FIXME - FileKnownFolderInformation static const u16string_view name = u"file"; test("Check directory entry (FILE_DIRECTORY_INFORMATION)", [&]() { check_dir_entry(dir, name, fbi, fsi, file_id, file_id_128); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_dir_entry(dir, name, fbi, fsi, file_id, file_id_128); }); test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_dir_entry(dir, name, fbi, fsi, file_id, file_id_128); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_dir_entry(dir, name, fbi, fsi, file_id, file_id_128); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_dir_entry(dir, name, fbi, fsi, file_id, file_id_128); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_dir_entry(dir, name, fbi, fsi, file_id, file_id_128); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_dir_entry(dir, name, fbi, fsi, file_id, file_id_128); }); test("Check directory entry (FILE_NAMES_INFORMATION)", [&]() { check_dir_entry(dir, name, fbi, fsi, file_id, file_id_128); }); test("Check granted access", [&]() { auto obi = query_object_basic_information(h.get()); ACCESS_MASK exp = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD | FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA | FILE_WRITE_DATA | FILE_READ_DATA; if (obi.GrantedAccess != exp) throw formatted_error("granted access was {:x}, expected {:x}", obi.GrantedAccess, exp); }); h.reset(); } // traverse privilege needed for FileNormalizedNameInformation test("Add SeChangeNotifyPrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Open file", [&]() { h = create_file(dir + u"\\file", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Check normalized name", [&]() { auto fn = query_file_name_information(h.get(), true); static const u16string_view ends_with = u"\\file"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\file\"."); }); } disable_token_privileges(token); test("Create file (FILE_NON_DIRECTORY_FILE)", [&]() { h = create_file(dir + u"\\file2", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE", fbi.FileAttributes); } }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.AllocationSize.QuadPart != 0) throw formatted_error("AllocationSize was {}, expected 0", fsi.AllocationSize.QuadPart); if (fsi.EndOfFile.QuadPart != 0) throw formatted_error("EndOfFile was {}, expected 0", fsi.EndOfFile.QuadPart); if (fsi.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsi.NumberOfLinks); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsi.Directory) throw runtime_error("Directory was true, expected false"); }); h.reset(); } test("Create file (FILE_NON_DIRECTORY_FILE, FILE_ATTRIBUTE_DIRECTORY)", [&]() { h = create_file(dir + u"\\file3", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_DIRECTORY, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE", fbi.FileAttributes); } }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.AllocationSize.QuadPart != 0) throw formatted_error("AllocationSize was {}, expected 0", fsi.AllocationSize.QuadPart); if (fsi.EndOfFile.QuadPart != 0) throw formatted_error("EndOfFile was {}, expected 0", fsi.EndOfFile.QuadPart); if (fsi.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsi.NumberOfLinks); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsi.Directory) throw runtime_error("Directory was true, expected false"); }); h.reset(); } test("Create directory (FILE_DIRECTORY_FILE)", [&]() { h = create_file(dir + u"\\dir", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_DIRECTORY", fbi.FileAttributes); } }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.AllocationSize.QuadPart != 0) throw formatted_error("AllocationSize was {}, expected 0", fsi.AllocationSize.QuadPart); if (fsi.EndOfFile.QuadPart != 0) throw formatted_error("EndOfFile was {}, expected 0", fsi.EndOfFile.QuadPart); if (fsi.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsi.NumberOfLinks); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (!fsi.Directory) throw runtime_error("Directory was false, expected true"); }); test("Check granted access", [&]() { auto obi = query_object_basic_information(h.get()); ACCESS_MASK exp = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD | FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA | FILE_WRITE_DATA | FILE_READ_DATA; if (obi.GrantedAccess != exp) throw formatted_error("granted access was {:x}, expected {:x}", obi.GrantedAccess, exp); }); h.reset(); test("Open directory", [&]() { create_file(dir + u"\\dir", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); } test("Create file (FILE_ATTRIBUTE_DIRECTORY)", [&]() { h = create_file(dir + u"\\file4", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_DIRECTORY, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE", fbi.FileAttributes); } }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.AllocationSize.QuadPart != 0) throw formatted_error("AllocationSize was {}, expected 0", fsi.AllocationSize.QuadPart); if (fsi.EndOfFile.QuadPart != 0) throw formatted_error("EndOfFile was {}, expected 0", fsi.EndOfFile.QuadPart); if (fsi.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsi.NumberOfLinks); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsi.Directory) throw runtime_error("Directory was true, expected false"); }); h.reset(); } test("Create file (FILE_ATTRIBUTE_HIDDEN)", [&]() { h = create_file(dir + u"\\filehidden", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_HIDDEN)) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_HIDDEN", fbi.FileAttributes); } }); h.reset(); } test("Create file (FILE_ATTRIBUTE_READONLY)", [&]() { h = create_file(dir + u"\\filero", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_READONLY)) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_READONLY", fbi.FileAttributes); } }); h.reset(); } test("Create file (FILE_ATTRIBUTE_SYSTEM)", [&]() { h = create_file(dir + u"\\filesys", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_SYSTEM, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_SYSTEM)) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_SYSTEM", fbi.FileAttributes); } }); h.reset(); } test("Create file (FILE_ATTRIBUTE_NORMAL)", [&]() { h = create_file(dir + u"\\filenormal", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE", fbi.FileAttributes); } }); h.reset(); } test("Create directory (FILE_ATTRIBUTE_HIDDEN)", [&]() { h = create_file(dir + u"\\dirhidden", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN)) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN", fbi.FileAttributes); } }); h.reset(); } test("Create directory (FILE_ATTRIBUTE_READONLY)", [&]() { h = create_file(dir + u"\\dirro", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY)) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY", fbi.FileAttributes); } }); h.reset(); } test("Create directory (FILE_ATTRIBUTE_SYSTEM)", [&]() { h = create_file(dir + u"\\dirsys", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_SYSTEM, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM)) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM", fbi.FileAttributes); } }); h.reset(); } test("Create directory (FILE_ATTRIBUTE_NORMAL)", [&]() { h = create_file(dir + u"\\dirnormal", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_DIRECTORY", fbi.FileAttributes); } }); h.reset(); } test("Create file (FILE_SHARE_READ)", [&]() { h = create_file(dir + u"\\fileshareread", FILE_READ_DATA, 0, FILE_SHARE_READ, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Open for read", [&]() { create_file(dir + u"\\fileshareread", FILE_READ_DATA, 0, FILE_SHARE_READ, FILE_OPEN, 0, FILE_OPENED); }); test("Open for write", [&]() { exp_status([&]() { create_file(dir + u"\\fileshareread", FILE_WRITE_DATA, 0, FILE_SHARE_READ, FILE_OPEN, 0, FILE_OPENED); }, STATUS_SHARING_VIOLATION); }); test("Open for delete", [&]() { exp_status([&]() { create_file(dir + u"\\fileshareread", DELETE, 0, FILE_SHARE_READ, FILE_OPEN, 0, FILE_OPENED); }, STATUS_SHARING_VIOLATION); }); h.reset(); } test("Create file (FILE_SHARE_WRITE)", [&]() { h = create_file(dir + u"\\filesharewrite", FILE_WRITE_DATA, 0, FILE_SHARE_WRITE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Open for read", [&]() { exp_status([&]() { create_file(dir + u"\\filesharewrite", FILE_READ_DATA, 0, FILE_SHARE_WRITE, FILE_OPEN, 0, FILE_OPENED); }, STATUS_SHARING_VIOLATION); }); test("Open for write", [&]() { create_file(dir + u"\\filesharewrite", FILE_WRITE_DATA, 0, FILE_SHARE_WRITE, FILE_OPEN, 0, FILE_OPENED); }); test("Open for delete", [&]() { exp_status([&]() { create_file(dir + u"\\filesharewrite", DELETE, 0, FILE_SHARE_WRITE, FILE_OPEN, 0, FILE_OPENED); }, STATUS_SHARING_VIOLATION); }); h.reset(); } test("Create file (FILE_SHARE_DELETE)", [&]() { h = create_file(dir + u"\\filesharedelete", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Open for read", [&]() { exp_status([&]() { create_file(dir + u"\\filesharedelete", FILE_READ_DATA, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }, STATUS_SHARING_VIOLATION); }); test("Open for write", [&]() { exp_status([&]() { create_file(dir + u"\\filesharedelete", FILE_WRITE_DATA, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }, STATUS_SHARING_VIOLATION); }); test("Open for delete", [&]() { create_file(dir + u"\\filesharedelete", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); h.reset(); } test("Create file in invalid path", [&]() { exp_status([&]() { create_file(dir + u"\\nosuchdir\\file", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }, STATUS_OBJECT_PATH_NOT_FOUND); }); test("Create directory in invalid path", [&]() { exp_status([&]() { create_file(dir + u"\\nosuchdir\\file", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }, STATUS_OBJECT_PATH_NOT_FOUND); }); test("Create file with FILE_OPEN_IF", [&]() { h = create_file(dir + u"\\openif", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN_IF, 0, FILE_CREATED); }); if (h) { h.reset(); test("Open file with FILE_OPEN_IF", [&]() { create_file(dir + u"\\openif", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN_IF, 0, FILE_OPENED); }); } test("Create file with long name", [&]() { u16string longname(256, u'x'); exp_status([&]() { create_file(dir + u"\\" + longname, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, STATUS_OBJECT_NAME_INVALID); }); test("Create file with emoji", [&]() { create_file(dir + u"\\\U0001f525", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create file", [&]() { create_file(dir + u"\\notadir", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Try to create file within other file", [&]() { exp_status([&]() { create_file(dir + u"\\notadir\\file", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, STATUS_OBJECT_PATH_NOT_FOUND); }); test("Try to open file within other file", [&]() { exp_status([&]() { create_file(dir + u"\\notadir\\file", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_OBJECT_PATH_NOT_FOUND); }); /* The limits for Btrfs are more stringent than NTFS, to make sure we don't * create a filename that will confuse Linux. */ bool is_ntfs = fstype == fs_type::ntfs; test("Create file with more than 255 UTF-8 characters", [&]() { auto fn = dir + u"\\"; for (unsigned int i = 0; i < 64; i++) { fn += u"\U0001f525"; } exp_status([&]() { create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); test("Create file with WTF-16 (1)", [&]() { auto fn = dir + u"\\"; fn += (char16_t)0xd83d; exp_status([&]() { create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); test("Create file with WTF-16 (2)", [&]() { auto fn = dir + u"\\"; fn += (char16_t)0xdd25; exp_status([&]() { create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); test("Create file with WTF-16 (3)", [&]() { auto fn = dir + u"\\"; fn += (char16_t)0xdd25; fn += (char16_t)0xd83d; exp_status([&]() { create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); struct { u16string name; string desc; } invalid_names[] = { { u"/", "slash" }, { u":", "colon" }, { u"<", "less than" }, { u">", "greater than" }, { u"\"", "quote" }, { u"|", "pipe" }, { u"?", "question mark" }, { u"*", "asterisk" } }; for (const auto& n : invalid_names) { test("Create file with invalid name (" + n.desc + ")", [&]() { auto fn = dir + u"\\" + n.name; exp_status([&]() { create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, STATUS_OBJECT_NAME_INVALID); }); } test("Create file called CON", [&]() { // allowed by NT API, not allowed by Win32 API create_file(dir + u"\\CON", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); // FIXME - if we try to open file with invalid name, do we get NOT_FOUND or INVALID? // FIXME - test all the variations of NtQueryInformationFile // FIXME - test NtOpenFile // FIXME - permissions needed to create file or subdirectory // FIXME - permissions needed when overwriting // FIXME - what exactly does FILE_DELETE_CHILD do? // FIXME - overwriting mapped file } template requires (is_same_v || is_same_v>) static unique_handle open_by_id(HANDLE dir, const T& id, ACCESS_MASK access, ULONG atts, ULONG share, ULONG dispo, ULONG options, ULONG_PTR exp_info, optional allocation = nullopt) { NTSTATUS Status; HANDLE h; IO_STATUS_BLOCK iosb; UNICODE_STRING us; OBJECT_ATTRIBUTES oa; LARGE_INTEGER alloc_size; oa.Length = sizeof(oa); oa.RootDirectory = dir; if constexpr (is_same_v>) { us.Length = us.MaximumLength = id.size(); us.Buffer = (WCHAR*)id.data(); } else { us.Length = us.MaximumLength = sizeof(id); us.Buffer = (WCHAR*)&id; } oa.ObjectName = &us; oa.Attributes = 0; oa.SecurityDescriptor = nullptr; oa.SecurityQualityOfService = nullptr; if (allocation) alloc_size.QuadPart = allocation.value(); iosb.Information = 0xdeadbeef; Status = NtCreateFile(&h, access, &oa, &iosb, allocation ? &alloc_size : nullptr, atts, share, dispo, options | FILE_OPEN_BY_FILE_ID, nullptr, 0); if (Status != STATUS_SUCCESS) { if (NT_SUCCESS(Status)) // STATUS_OPLOCK_BREAK_IN_PROGRESS etc. NtClose(h); throw ntstatus_error(Status); } if (iosb.Information != exp_info) throw formatted_error("iosb.Information was {}, expected {}", iosb.Information, exp_info); return unique_handle(h); } static array create_or_get_object_id(HANDLE h) { NTSTATUS Status; FILE_OBJECTID_BUFFER foib; IO_STATUS_BLOCK iosb; auto ev = create_event(); Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb, FSCTL_CREATE_OR_GET_OBJECT_ID, nullptr, 0, &foib, sizeof(foib)); if (Status == STATUS_PENDING) { Status = NtWaitForSingleObject(ev.get(), false, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); Status = iosb.Status; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); array ret; memcpy(ret.data(), foib.ObjectId, 16); return ret; } void test_open_id(HANDLE token, const u16string& dir) { unique_handle h, dirh; uint64_t file_id = 0; auto random = random_data(4096); // traverse privilege needed to query filename and hard links test("Add SeChangeNotifyPrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file", [&]() { h = create_file(dir + u"\\id1", SYNCHRONIZE | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Write to file", [&]() { write_file(h.get(), random, 0); }); test("Get file ID", [&]() { auto fii = query_information(h.get()); file_id = fii.IndexNumber.QuadPart; }); h.reset(); } test("Check directory entry", [&]() { u16string_view name = u"id1"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); if ((uint64_t)fdi.FileId.QuadPart != file_id) throw runtime_error("File IDs did not match."); }); test("Try opening by ID without RootDirectory value", [&]() { exp_status([&]() { open_by_id(nullptr, file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPENED); }, STATUS_INVALID_PARAMETER); }); test("Open directory", [&]() { dirh = create_file(dir, MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED); }); test("Try to open by ID with FILE_DIRECTORY_FILE", [&]() { exp_status([&]() { open_by_id(dirh.get(), file_id, SYNCHRONIZE | MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE, FILE_OPENED); }, STATUS_NOT_A_DIRECTORY); }); test("Open by ID", [&]() { h = open_by_id(dirh.get(), file_id, SYNCHRONIZE | MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPENED); }); dirh.reset(); if (h) { test("Read file", [&]() { auto data = read_file(h.get(), random.size(), 0); if (data.size() != random.size()) throw formatted_error("Read {} bytes, {} expected.", data.size(), random.size()); if (memcmp(data.data(), random.data(), random.size())) throw runtime_error("Data read did not match data written"); }); test("Check filename", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\id1"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\id1\"."); }); test("Check links", [&]() { auto items = query_links(h.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& item = items.front(); if (item.second != u"id1") throw formatted_error("Link was called {}, expected id1", u16string_to_string(item.second)); }); test("Create hardlink", [&]() { set_link_information(h.get(), false, nullptr, dir + u"\\id1a"); }); test("Try renaming", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\id1b"); }, STATUS_INVALID_PARAMETER); }); test("Try deleting", [&]() { exp_status([&]() { set_disposition_information(h.get(), true); }, STATUS_INVALID_PARAMETER); }); h.reset(); } test("Open directory", [&]() { dirh = create_file(dir, MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED); }); test("Open by ID with FILE_DELETE_ON_CLOSE", [&]() { h = open_by_id(dirh.get(), file_id, SYNCHRONIZE | DELETE, 0, 0, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE, FILE_OPENED); }); dirh.reset(); if (h) { h.reset(); test("Check directory entry 1 still there", [&]() { u16string_view name = u"id1"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); if ((uint64_t)fdi.FileId.QuadPart != file_id) throw runtime_error("File IDs did not match."); }); test("Check directory entry 2 still there", [&]() { u16string_view name = u"id1a"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); if ((uint64_t)fdi.FileId.QuadPart != file_id) throw runtime_error("File IDs did not match."); }); } test("Open directory", [&]() { dirh = create_file(dir, MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED); }); test("Open by ID with FILE_OPEN_IF", [&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN_IF, 0, FILE_OPENED); }); test("Open by ID with FILE_OVERWRITE_IF", [&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN); }); test("Open by ID with FILE_SUPERSEDE", [&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); test("Try open by ID with FILE_CREATE", [&]() { exp_status([&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, STATUS_OBJECT_NAME_COLLISION); }); test("Try open by ID with FILE_OVERWRITE", [&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); dirh.reset(); test("Create file", [&]() { h = create_file(dir + u"\\id2", FILE_READ_ATTRIBUTES | DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); file_id = fii.IndexNumber.QuadPart; }); test("Delete file", [&]() { set_disposition_information(h.get(), true); }); h.reset(); } test("Open directory", [&]() { dirh = create_file(dir, MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED); }); test("Try to open invalid ID with FILE_OPEN", [&]() { exp_status([&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_INVALID_PARAMETER); }); test("Try to open invalid ID with FILE_OPEN_IF", [&]() { exp_status([&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN_IF, 0, FILE_OPENED); }, STATUS_INVALID_PARAMETER); }); test("Try to open invalid ID with FILE_CREATE", [&]() { exp_status([&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, STATUS_INVALID_PARAMETER); }); test("Try to open invalid ID with FILE_OVERWRITE", [&]() { exp_status([&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }, STATUS_INVALID_PARAMETER); }); test("Try to open invalid ID with FILE_OVERWRITE_IF", [&]() { exp_status([&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN); }, STATUS_INVALID_PARAMETER); }); test("Try to open invalid ID with FILE_SUPERSEDE", [&]() { exp_status([&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }, STATUS_INVALID_PARAMETER); }); dirh.reset(); test("Create directory", [&]() { h = create_file(dir + u"\\id3", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); file_id = fii.IndexNumber.QuadPart; }); h.reset(); test("Open directory", [&]() { dirh = create_file(dir, MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED); }); test("Try to open subdirectory by ID with FILE_NON_DIRECTORY_FILE", [&]() { exp_status([&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }, STATUS_FILE_IS_A_DIRECTORY); }); test("Open subdirectory by ID", [&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); dirh.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\id4", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle h2; test("Open second handle to file", [&]() { h2 = create_file(dir + u"\\id4", DELETE, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); test("Do POSIX deletion", [&]() { set_disposition_information_ex(h2.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS); }); h2.reset(); test("Get file ID", [&]() { auto fii = query_information(h.get()); file_id = fii.IndexNumber.QuadPart; }); test("Open directory", [&]() { dirh = create_file(dir, MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED); }); test("Try to open orphaned inode by file ID", [&]() { exp_status([&]() { open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_DELETE_PENDING); }); dirh.reset(); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\id5", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { array obj_id; test("Get object ID", [&]() { obj_id = create_or_get_object_id(h.get()); }); test("Open directory", [&]() { dirh = create_file(dir, MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED); }); test("Open by object ID", [&]() { open_by_id(dirh.get(), obj_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); dirh.reset(); h.reset(); } disable_token_privileges(token); test("Create file", [&]() { h = create_file(dir + u"\\id6", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle h2; test("Get file ID", [&]() { auto fii = query_information(h.get()); file_id = fii.IndexNumber.QuadPart; }); h.reset(); test("Open directory", [&]() { dirh = create_file(dir, MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED); }); test("Open file by ID", [&]() { h2 = open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); test("Try to query filename without traverse privilege", [&]() { exp_status([&]() { query_file_name_information(h2.get()); }, STATUS_ACCESS_DENIED); }); dirh.reset(); } } ================================================ FILE: src/tests/cs.cpp ================================================ #include "test.h" using namespace std; static void set_case_sensitive(HANDLE h, bool case_sensitive) { NTSTATUS Status; IO_STATUS_BLOCK iosb; FILE_CASE_SENSITIVE_INFORMATION fcsi; fcsi.Flags = case_sensitive ? FILE_CS_FLAG_CASE_SENSITIVE_DIR : 0; Status = NtSetInformationFile(h, &iosb, &fcsi, sizeof(fcsi), FileCaseSensitiveInformation); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != 0) throw formatted_error("iosb.Information was {}, expected 0", iosb.Information); } static unique_handle create_file_cs(u16string_view path, ACCESS_MASK access, ULONG atts, ULONG share, ULONG dispo, ULONG options, ULONG_PTR exp_info, optional allocation = nullopt) { NTSTATUS Status; HANDLE h; IO_STATUS_BLOCK iosb; UNICODE_STRING us; OBJECT_ATTRIBUTES oa; LARGE_INTEGER alloc_size; oa.Length = sizeof(oa); oa.RootDirectory = nullptr; // FIXME - test us.Length = us.MaximumLength = path.length() * sizeof(char16_t); us.Buffer = (WCHAR*)path.data(); oa.ObjectName = &us; oa.Attributes = 0; // not OBJ_CASE_INSENSITIVE oa.SecurityDescriptor = nullptr; // FIXME - test oa.SecurityQualityOfService = nullptr; // FIXME - test(?) if (allocation) alloc_size.QuadPart = allocation.value(); // FIXME - EaBuffer and EaLength iosb.Information = 0xdeadbeef; Status = NtCreateFile(&h, access, &oa, &iosb, allocation ? &alloc_size : nullptr, atts, share, dispo, options, nullptr, 0); if (Status != STATUS_SUCCESS) { if (NT_SUCCESS(Status)) // STATUS_OPLOCK_BREAK_IN_PROGRESS etc. NtClose(h); throw ntstatus_error(Status); } if (iosb.Information != exp_info) throw formatted_error("iosb.Information was {}, expected {}", iosb.Information, exp_info); return unique_handle(h); } void test_cs(const u16string& dir) { unique_handle h; int64_t lc_id = 0, uc_id = 0; test("Create directory", [&]() { h = create_file(dir + u"\\csdir", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { // returns STATUS_NOT_SUPPORTED unless HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\NtfsEnableDirCaseSensitivity is set to 1 test("Set case-sensitive flag", [&]() { set_case_sensitive(h.get(), true); }); test("Query case-sensitive flag", [&]() { auto fcsi = query_information(h.get()); if (fcsi.Flags != FILE_CS_FLAG_CASE_SENSITIVE_DIR) throw formatted_error("Flags was {:x}, expected FILE_CS_FLAG_CASE_SENSITIVE_DIR", fcsi.Flags); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\csdir\\cs1", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); lc_id = fii.IndexNumber.QuadPart; }); h.reset(); } test("Check directory entry", [&]() { u16string_view name = u"cs1"; auto items = query_dir(dir + u"\\csdir", name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check directory entry with wrong case", [&]() { u16string_view name = u"CS1"; exp_status([&]() { query_dir(dir + u"\\csdir", name); }, STATUS_NO_SUCH_FILE); }); test("Try opening file with wrong case (FILE_OPEN)", [&]() { exp_status([&]() { create_file(dir + u"\\csdir\\CS1", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }, STATUS_OBJECT_NAME_NOT_FOUND); }); test("Try opening file with wrong case (FILE_OVERWRITE)", [&]() { exp_status([&]() { create_file(dir + u"\\csdir\\CS1", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE, FILE_NON_DIRECTORY_FILE, FILE_OVERWRITTEN); }, STATUS_OBJECT_NAME_NOT_FOUND); }); test("Create file with different case (FILE_CREATE)", [&]() { h = create_file(dir + u"\\csdir\\CS1", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); uc_id = fii.IndexNumber.QuadPart; }); h.reset(); } test("Open uppercase file (FILE_OPEN_IF)", [&]() { h = create_file(dir + u"\\csdir\\CS1", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); if (h) { test("Check file ID", [&]() { auto fii = query_information(h.get()); if (fii.IndexNumber.QuadPart != uc_id) throw runtime_error("Wrong file ID"); }); h.reset(); } test("Open uppercase file (FILE_OVERWRITE_IF)", [&]() { h = create_file(dir + u"\\csdir\\CS1", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE_IF, FILE_NON_DIRECTORY_FILE, FILE_OVERWRITTEN); }); if (h) { test("Check file ID", [&]() { auto fii = query_information(h.get()); if (fii.IndexNumber.QuadPart != uc_id) throw runtime_error("Wrong file ID"); }); h.reset(); } test("Open uppercase file (FILE_SUPERSEDE)", [&]() { h = create_file(dir + u"\\csdir\\CS1", MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE, FILE_NON_DIRECTORY_FILE, FILE_SUPERSEDED); }); if (h) { test("Check file ID", [&]() { auto fii = query_information(h.get()); if (fii.IndexNumber.QuadPart != uc_id) throw runtime_error("Wrong file ID"); }); h.reset(); } test("Create subdir", [&]() { h = create_file(dir + u"\\csdir\\cs2", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Check case-sensitive flag inherited", [&]() { auto fcsi = query_information(h.get()); if (fcsi.Flags != FILE_CS_FLAG_CASE_SENSITIVE_DIR) throw formatted_error("Flags was {:x}, expected FILE_CS_FLAG_CASE_SENSITIVE_DIR", fcsi.Flags); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\csdir\\cs3", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Try to set case-sensitive flag on file", [&]() { exp_status([&]() { set_case_sensitive(h.get(), true); }, STATUS_INVALID_PARAMETER); }); test("Query case-sensitive flag on file", [&]() { auto fcsi = query_information(h.get()); if (fcsi.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", fcsi.Flags); }); h.reset(); } test("Create subdir", [&]() { h = create_file(dir + u"\\csdir\\cs5", FILE_WRITE_ATTRIBUTES | WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Set DACL to FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD", [&]() { set_dacl(h.get(), FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD); }); test("Try to set case-sensitive flag", [&]() { exp_status([&]() { set_case_sensitive(h.get(), true); }, STATUS_ACCESS_DENIED); }); test("Set DACL to FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY", [&]() { set_dacl(h.get(), FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY); }); test("Try to set case-sensitive flag", [&]() { exp_status([&]() { set_case_sensitive(h.get(), true); }, STATUS_ACCESS_DENIED); }); test("Set DACL to FILE_ADD_FILE | FILE_DELETE_CHILD", [&]() { set_dacl(h.get(), FILE_ADD_FILE | FILE_DELETE_CHILD); }); test("Try to set case-sensitive flag", [&]() { exp_status([&]() { set_case_sensitive(h.get(), true); }, STATUS_ACCESS_DENIED); }); test("Set DACL to FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD", [&]() { set_dacl(h.get(), FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD); }); test("Set case-sensitive flag", [&]() { set_case_sensitive(h.get(), true); }); h.reset(); } test("Create subdir", [&]() { create_file(dir + u"\\csdir\\cs6", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create file", [&]() { create_file(dir + u"\\csdir\\cs6\\file", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); test("Open subdir", [&]() { h = create_file(dir + u"\\csdir\\cs6", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED); }); if (h) { test("Set case-sensitive flag on non-empty directory", [&]() { set_case_sensitive(h.get(), true); }); test("Clear case-sensitive flag on non-empty directory", [&]() { set_case_sensitive(h.get(), false); }); test("Set case-sensitive flag on non-empty directory again", [&]() { set_case_sensitive(h.get(), true); }); h.reset(); } test("Create file differing by case", [&]() { create_file(dir + u"\\csdir\\cs6\\FILE", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); test("Open subdir", [&]() { h = create_file(dir + u"\\csdir\\cs6", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED); }); if (h) { test("Try to clear case-sensitive flag on directory with files differing in case", [&]() { exp_status([&]() { set_case_sensitive(h.get(), false); }, STATUS_CASE_DIFFERING_NAMES_IN_DIR); }); h.reset(); } test("Create file in normal dir without OBJ_CASE_INSENSITIVE", [&]() { create_file_cs(dir + u"\\cs7", MAXIMUM_ALLOWED, 0, 0, FILE_CREATED, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); test("Open file in normal dir without OBJ_CASE_INSENSITIVE", [&]() { create_file_cs(dir + u"\\cs7", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); // succeeds unless HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\kernel\ObCaseInsensitive set to 0 test("Open file in normal dir without OBJ_CASE_INSENSITIVE with wrong case", [&]() { exp_status([&]() { create_file_cs(dir + u"\\CS7", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }, STATUS_OBJECT_NAME_NOT_FOUND); }); test("Create stream in case-sensitive directory", [&]() { create_file(dir + u"\\csdir\\cs8:stream", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); test("Open stream in case-sensitive directory with wrong case", [&]() { // succeeds! create_file(dir + u"\\csdir\\cs8:STREAM", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); } ================================================ FILE: src/tests/delete.cpp ================================================ #include "test.h" using namespace std; void set_disposition_information(HANDLE h, bool delete_file) { NTSTATUS Status; IO_STATUS_BLOCK iosb; FILE_DISPOSITION_INFORMATION fdi; fdi.DoDeleteFile = delete_file; iosb.Information = 0xdeadbeef; Status = NtSetInformationFile(h, &iosb, &fdi, sizeof(fdi), FileDispositionInformation); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != 0) throw formatted_error("iosb.Information was {}, expected 0", iosb.Information); } void set_disposition_information_ex(HANDLE h, uint32_t flags) { NTSTATUS Status; IO_STATUS_BLOCK iosb; FILE_DISPOSITION_INFORMATION_EX fdie; fdie.Flags = flags; iosb.Information = 0xdeadbeef; Status = NtSetInformationFile(h, &iosb, &fdie, sizeof(fdie), FileDispositionInformationEx); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != 0) throw formatted_error("iosb.Information was {}, expected 0", iosb.Information); } void test_delete(const u16string& dir) { unique_handle h, h2; test("Create file", [&]() { h = create_file(dir + u"\\deletefile1", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Check directory entry", [&]() { u16string_view name = u"deletefile1"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Set disposition", [&]() { set_disposition_information(h.get(), true); }); test("Check directory entry still there", [&]() { u16string_view name = u"deletefile1"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check standard information again", [&]() { auto fsi = query_information(h.get()); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 0", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); h.reset(); test("Check directory entry gone after close", [&]() { u16string_view name = u"deletefile1"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); } test("Create directory", [&]() { h = create_file(dir + u"\\deletedir2", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Check directory entry", [&]() { u16string_view name = u"deletedir2"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (!fsli.Directory) throw runtime_error("Directory was false, expected true"); }); test("Set disposition", [&]() { set_disposition_information(h.get(), true); }); test("Check directory entry still there", [&]() { u16string_view name = u"deletedir2"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check standard information again", [&]() { auto fsi = query_information(h.get()); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information again", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (!fsli.Directory) throw runtime_error("Directory was false, expected true"); }); h.reset(); test("Check directory entry gone after close", [&]() { u16string_view name = u"deletedir2"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); } test("Create directory", [&]() { h = create_file(dir + u"\\deletedir3", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create file", [&]() { h2 = create_file(dir + u"\\deletedir3\\file", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h && h2) { test("Try to set disposition on directory", [&]() { exp_status([&]() { set_disposition_information(h.get(), true); }, STATUS_DIRECTORY_NOT_EMPTY); }); test("Set disposition on file", [&]() { set_disposition_information(h2.get(), true); }); test("Try to set disposition on directory again", [&]() { exp_status([&]() { set_disposition_information(h.get(), true); }, STATUS_DIRECTORY_NOT_EMPTY); }); h2.reset(); test("Set disposition on directory now empty", [&]() { set_disposition_information(h.get(), true); }); h.reset(); test("Check directory entry gone after close", [&]() { u16string_view name = u"deletedir3"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); } test("Create file", [&]() { h = create_file(dir + u"\\deletefile4", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Set deletion flag on file", [&]() { set_disposition_information(h.get(), true); }); test("Try to reopen file marked for deletion", [&]() { exp_status([&]() { create_file(dir + u"\\deletefile4", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }, STATUS_DELETE_PENDING); }); test("Clear deletion flag on file", [&]() { set_disposition_information(h.get(), false); }); test("Check standard information again", [&]() { auto fsi = query_information(h.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information again", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Reopen file now no longer marked for deletion", [&]() { h2 = create_file(dir + u"\\deletefile4", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Set deletion flag again", [&]() { set_disposition_information(h.get(), true); }); h.reset(); test("Check directory entry after first handle closed", [&]() { u16string_view name = u"deletefile4"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); h2.reset(); test("Check directory entry gone after second handle closed", [&]() { u16string_view name = u"deletefile4"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); } test("Create file", [&]() { h = create_file(dir + u"\\deletefile5", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Set deletion flag on file", [&]() { set_disposition_information(h.get(), true); }); test("Clear deletion flag on file", [&]() { set_disposition_information(h.get(), false); }); h.reset(); test("Check directory entry", [&]() { u16string_view name = u"deletefile5"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); } test("Create file without DELETE flag", [&]() { h = create_file(dir + u"\\deletefile6", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try to set deletion flag on file", [&]() { exp_status([&]() { set_disposition_information(h.get(), true); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\deletefile7", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create stream on file", [&]() { h2 = create_file(dir + u"\\deletefile7:ads", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); test("Set deletion flag on file", [&]() { set_disposition_information(h.get(), true); }); test("Check standard information on file", [&]() { auto fsi = query_information(h.get()); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information on file", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 0", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Check standard information on stream", [&]() { auto fsi = query_information(h2.get()); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information on stream", [&]() { auto fsli = query_information(h2.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 0", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Clear deletion flag on stream", [&]() { set_disposition_information(h2.get(), false); // gets ignored }); test("Check standard information on file", [&]() { auto fsi = query_information(h.get()); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information on file", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 0", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Check standard information on stream", [&]() { auto fsi = query_information(h2.get()); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information on stream", [&]() { auto fsli = query_information(h2.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 0", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); h.reset(); test("Check directory entry still there after file handle closed", [&]() { u16string_view name = u"deletefile7"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Clear deletion flag on stream", [&]() { set_disposition_information(h2.get(), false); // still ignored }); test("Check standard information on stream", [&]() { auto fsi = query_information(h2.get()); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information on stream", [&]() { auto fsli = query_information(h2.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 0", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); h2.reset(); test("Check directory entry gone after stream handle closed", [&]() { u16string_view name = u"deletefile7"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); } test("Create file with FILE_DELETE_ON_CLOSE", [&]() { h = create_file(dir + u"\\deletefile8", DELETE, 0, 0, FILE_CREATE, FILE_DELETE_ON_CLOSE, FILE_CREATED); }); if (h) { test("Check standard information on file", [&]() { auto fsi = query_information(h.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information on file", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); h.reset(); test("Check directory entry gone after file closed", [&]() { u16string_view name = u"deletefile8"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); } test("Create file with FILE_DELETE_ON_CLOSE", [&]() { h = create_file(dir + u"\\deletefile9", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, FILE_DELETE_ON_CLOSE, FILE_CREATED); }); if (h) { test("Try to open second handle to file without FILE_SHARE_DELETE", [&]() { exp_status([&]() { create_file(dir + u"\\deletefile9", FILE_READ_DATA, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_SHARING_VIOLATION); }); test("Open second handle to file with FILE_SHARE_DELETE", [&]() { h2 = create_file(dir + u"\\deletefile9", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check standard information on second handle", [&]() { auto fsi = query_information(h2.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information on second handle", [&]() { auto fsli = query_information(h2.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); h.reset(); test("Check standard information after first handle closed", [&]() { auto fsi = query_information(h2.get()); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information after first handle closed", [&]() { auto fsli = query_information(h2.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 0", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Clear deletion flag", [&]() { set_disposition_information(h2.get(), false); // ignored }); test("Check standard information again", [&]() { auto fsi = query_information(h2.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information again", [&]() { auto fsli = query_information(h2.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Try to open third handle to file", [&]() { exp_status([&]() { create_file(dir + u"\\deletefile9", FILE_READ_DATA, 0, 0, FILE_OPEN, FILE_SHARE_DELETE, FILE_OPENED); }, STATUS_SHARING_VIOLATION); }); h2.reset(); test("Check directory entry still there after both handles closed", [&]() { u16string_view name = u"deletefile9"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); } test("Create readonly file", [&]() { create_file(dir + u"\\deletefile10", FILE_READ_DATA, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Try to open readonly file with FILE_DELETE_ON_CLOSE", [&]() { exp_status([&]() { create_file(dir + u"\\deletefile10", DELETE, 0, 0, FILE_OPEN, FILE_DELETE_ON_CLOSE, FILE_OPENED); }, STATUS_CANNOT_DELETE); }); test("Create readonly file", [&]() { h = create_file(dir + u"\\deletefile11", DELETE, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try to set deletion flag on readonly file", [&]() { exp_status([&]() { set_disposition_information(h.get(), true); }, STATUS_CANNOT_DELETE); }); h.reset(); } test("Create directory with FILE_DELETE_ON_CLOSE", [&]() { h = create_file(dir + u"\\deletedir12", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE, FILE_CREATED); }); if (h) { test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (!fsli.Directory) throw runtime_error("Directory was false, expected true"); }); h.reset(); test("Check directory entry gone after handle closed", [&]() { u16string_view name = u"deletedir12"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); } test("Create directory with FILE_DELETE_ON_CLOSE", [&]() { h = create_file(dir + u"\\deletedir13", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE, FILE_CREATED); }); if (h) { test("Create file in directory", [&]() { create_file(dir + u"\\deletedir13\\file", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); h.reset(); test("Check directory entry still there after handle closed", [&]() { u16string_view name = u"deletedir13"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); } } void test_delete_ex(HANDLE token, const u16string& dir) { unique_handle h, h2; test("Create file", [&]() { h = create_file(dir + u"\\deletefileex1", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Check directory entry", [&]() { u16string_view name = u"deletefileex1"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Set disposition", [&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE); }); test("Check directory entry still there", [&]() { u16string_view name = u"deletefileex1"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check standard information again", [&]() { auto fsi = query_information(h.get()); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information again", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 0", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); h.reset(); test("Check directory entry gone after close", [&]() { u16string_view name = u"deletefileex1"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); } test("Create directory", [&]() { h = create_file(dir + u"\\deleteexdir2", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Check directory entry", [&]() { u16string_view name = u"deleteexdir2"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (!fsli.Directory) throw runtime_error("Directory was false, expected true"); }); test("Set disposition", [&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE); }); test("Check directory entry still there", [&]() { u16string_view name = u"deleteexdir2"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check standard information again", [&]() { auto fsi = query_information(h.get()); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information again", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 0", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (!fsli.Directory) throw runtime_error("Directory was false, expected true"); }); h.reset(); test("Check directory entry gone after close", [&]() { u16string_view name = u"deleteexdir2"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); } test("Create file", [&]() { h = create_file(dir + u"\\deleteexfile3", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Set deletion flag on file", [&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE); }); test("Try to reopen file marked for deletion", [&]() { exp_status([&]() { create_file(dir + u"\\deleteexfile3", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }, STATUS_DELETE_PENDING); }); test("Clear deletion flag on file", [&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DO_NOT_DELETE); }); test("Check standard information again", [&]() { auto fsi = query_information(h.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information again", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Reopen file now no longer marked for deletion", [&]() { h2 = create_file(dir + u"\\deleteexfile3", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Set deletion flag again", [&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE); }); h.reset(); test("Check directory entry after first handle closed", [&]() { u16string_view name = u"deleteexfile3"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); h2.reset(); test("Check directory entry gone after second handle closed", [&]() { u16string_view name = u"deleteexfile3"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); } test("Create image file", [&]() { h = create_file(dir + u"\\deleteexfile4", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { unique_handle sect; test("Write to file", [&]() { auto img = pe_image(as_bytes(span("hello"))); write_file(h.get(), img); }); test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get()); }); if (sect) { test("Try deleting mapped image file with 1 link and FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK", [&]() { exp_status([&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK); }, STATUS_CANNOT_DELETE); }); test("Try deleting mapped image file with 1 link", [&]() { exp_status([&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE); }, STATUS_CANNOT_DELETE); }); test("Create hard link", [&]() { set_link_information(h.get(), false, nullptr, dir + u"\\deleteexfile4a"); }); test("Try deleting mapped image file with 2 links and FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK", [&]() { exp_status([&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK); }, STATUS_CANNOT_DELETE); }); test("Delete mapped image file with 2 links", [&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE); }); } h.reset(); } test("Create readonly file", [&]() { h = create_file(dir + u"\\deleteexfile5", DELETE, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try to set deletion flag on readonly file", [&]() { exp_status([&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE); }, STATUS_CANNOT_DELETE); }); test("Set deletion flag on readonly file with FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE", [&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE); }); h.reset(); } // traverse privilege needed to query hard links test("Add SeChangeNotifyPrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file", [&]() { h = create_file(dir + u"\\deleteexfile6", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); test("Open second handle to file", [&]() { h2 = create_file(dir + u"\\deleteexfile6", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Check directory entry", [&]() { u16string_view name = u"deleteexfile6"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\deleteexfile6"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\deleteexfile6\"."); }); int64_t dir_id; test("Check hardlinks", [&]() { auto items = query_links(h.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& item = items.front(); if (item.second != u"deleteexfile6") throw formatted_error("Link was called {}, expected deleteexfile6", u16string_to_string(item.second)); dir_id = item.first; }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsi.NumberOfLinks); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Set disposition with FILE_DISPOSITION_POSIX_SEMANTICS", [&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS); }); test("Check directory entry still there", [&]() { u16string_view name = u"deleteexfile6"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check standard information again", [&]() { auto fsi = query_information(h.get()); if (fsi.NumberOfLinks != 0) throw formatted_error("NumberOfLinks was {}, expected 0", fsi.NumberOfLinks); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information again", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 0", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); h.reset(); test("Check directory entry gone after close", [&]() { u16string_view name = u"deleteexfile6"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); test("Check name", [&]() { auto fn = query_file_name_information(h2.get()); static const u16string_view ends_with = u"\\deleteexfile6"; // NTFS moves this to \$Extend\$Deleted directory if (fn.size() >= ends_with.size() && fn.substr(fn.size() - ends_with.size()) == ends_with) throw runtime_error("Name ended with \"\\deleteexfile6\"."); }); test("Check hardlinks", [&]() { auto items = query_links(h2.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& item = items.front(); if (item.second == u"deleteexfile6") throw formatted_error("Link was called deleteexfile6, expected something else", u16string_to_string(item.second)); if (item.first == dir_id) throw runtime_error("Dir ID of orphaned inode is same as before"); }); test("Check standard information on second handle", [&]() { auto fsi = query_information(h2.get()); if (fsi.NumberOfLinks != 0) throw formatted_error("NumberOfLinks was {}, expected 0", fsi.NumberOfLinks); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information on second handle", [&]() { auto fsli = query_information(h2.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 0", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); h2.reset(); test("Try opening deleted file", [&]() { exp_status([&]() { create_file(dir + u"\\deleteexfile6", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }, STATUS_OBJECT_NAME_NOT_FOUND); }); } disable_token_privileges(token); test("Create file with FILE_DELETE_ON_CLOSE", [&]() { h = create_file(dir + u"\\deleteexfile7", DELETE, 0, 0, FILE_CREATE, FILE_DELETE_ON_CLOSE, FILE_CREATED); }); if (h) { test("Check standard information on file", [&]() { auto fsi = query_information(h.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information on file", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Clear delete on close flag", [&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DO_NOT_DELETE | FILE_DISPOSITION_ON_CLOSE); }); test("Check standard information on file", [&]() { auto fsi = query_information(h.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information on file", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); h.reset(); test("Check directory entry still there after file closed", [&]() { u16string_view name = u"deleteexfile7"; query_dir(dir, name); }); } test("Create file without FILE_DELETE_ON_CLOSE", [&]() { h = create_file(dir + u"\\deleteexfile8", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Check standard information on file", [&]() { auto fsi = query_information(h.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information on file", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); // see https://community.osr.com/discussion/comment/302155/#Comment_302155 test("Try to set delete on close flag", [&]() { exp_status([&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_ON_CLOSE); }, STATUS_NOT_SUPPORTED); }); h.reset(); } test("Create file with FILE_DELETE_ON_CLOSE", [&]() { h = create_file(dir + u"\\deleteexfile9", DELETE, 0, 0, FILE_CREATE, FILE_DELETE_ON_CLOSE, FILE_CREATED); }); if (h) { test("Set delete on close flag", [&]() { set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_ON_CLOSE); }); h.reset(); } } ================================================ FILE: src/tests/ea.cpp ================================================ #include "test.h" #include using namespace std; #ifdef _MSC_VER typedef struct _FILE_FULL_EA_INFORMATION { ULONG NextEntryOffset; UCHAR Flags; UCHAR EaNameLength; USHORT EaValueLength; CHAR EaName[1]; } FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION; #endif static constexpr uint32_t ea_size(string_view name, string_view value) { uint32_t size = offsetof(FILE_FULL_EA_INFORMATION, EaName) + name.length() + 1 + value.length(); if (size & 3) size = ((size >> 2) + 1) << 2; return size; } void write_ea(HANDLE h, string_view name, string_view value, bool need_ea) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf; buf.resize(ea_size(name, value)); auto& ffeai = *(FILE_FULL_EA_INFORMATION*)buf.data(); ffeai.NextEntryOffset = 0; ffeai.Flags = need_ea ? FILE_NEED_EA : 0; ffeai.EaNameLength = name.size(); ffeai.EaValueLength = value.size(); memcpy(ffeai.EaName, name.data(), name.size()); ffeai.EaName[name.size()] = 0; memcpy(ffeai.EaName + name.size() + 1, value.data(), value.size()); Status = NtSetEaFile(h, &iosb, buf.data(), buf.size()); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } template static void write_eas(HANDLE h, const array, N>& arr) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf; size_t size = 0; for (unsigned int i = 0; i < N; i++) { size += ea_size(arr[i].first, arr[i].second); } buf.resize(size); auto ptr = buf.data(); for (unsigned int i = 0; i < N; i++) { auto& ffeai = *(FILE_FULL_EA_INFORMATION*)ptr; ffeai.NextEntryOffset = 0; ffeai.Flags = 0; ffeai.EaNameLength = arr[i].first.size(); ffeai.EaValueLength = arr[i].second.size(); memcpy(ffeai.EaName, arr[i].first.data(), arr[i].first.size()); ffeai.EaName[arr[i].first.size()] = 0; memcpy(ffeai.EaName + arr[i].first.size() + 1, arr[i].second.data(), arr[i].second.size()); if (i != N - 1) { ffeai.NextEntryOffset = ea_size(arr[i].first, arr[i].second); ptr += ffeai.NextEntryOffset; } } Status = NtSetEaFile(h, &iosb, buf.data(), buf.size()); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static vector> read_ea(HANDLE h) { NTSTATUS Status; IO_STATUS_BLOCK iosb; char buf[4096]; Status = NtQueryEaFile(h, &iosb, buf, sizeof(buf), false, nullptr, 0, nullptr, true); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); vector> ret; auto ptr = buf; do { auto& ffeai = *(FILE_FULL_EA_INFORMATION*)ptr; ret.emplace_back(); auto& item = ret.back(); item.buf.resize(offsetof(FILE_FULL_EA_INFORMATION, EaName) + ffeai.EaNameLength + 1 + ffeai.EaValueLength); memcpy(item.buf.data(), &ffeai, item.buf.size()); if (ffeai.NextEntryOffset == 0) break; ptr += ffeai.NextEntryOffset; } while (true); return ret; } template static unique_handle create_file_ea(u16string_view path, ACCESS_MASK access, ULONG atts, ULONG share, ULONG dispo, ULONG options, ULONG_PTR exp_info, optional allocation, const array, N>& eas) { NTSTATUS Status; HANDLE h; IO_STATUS_BLOCK iosb; UNICODE_STRING us; OBJECT_ATTRIBUTES oa; LARGE_INTEGER alloc_size; vector buf; oa.Length = sizeof(oa); oa.RootDirectory = nullptr; us.Length = us.MaximumLength = path.length() * sizeof(char16_t); us.Buffer = (WCHAR*)path.data(); oa.ObjectName = &us; oa.Attributes = OBJ_CASE_INSENSITIVE; oa.SecurityDescriptor = nullptr; oa.SecurityQualityOfService = nullptr; if (allocation) alloc_size.QuadPart = allocation.value(); if constexpr (N != 0) { size_t size = 0; for (unsigned int i = 0; i < N; i++) { size += ea_size(eas[i].first, eas[i].second); } buf.resize(size); auto ptr = buf.data(); for (unsigned int i = 0; i < N; i++) { auto& ffeai = *(FILE_FULL_EA_INFORMATION*)ptr; ffeai.NextEntryOffset = 0; ffeai.Flags = 0; ffeai.EaNameLength = eas[i].first.size(); ffeai.EaValueLength = eas[i].second.size(); memcpy(ffeai.EaName, eas[i].first.data(), eas[i].first.size()); ffeai.EaName[eas[i].first.size()] = 0; memcpy(ffeai.EaName + eas[i].first.size() + 1, eas[i].second.data(), eas[i].second.size()); if (i != N - 1) { ffeai.NextEntryOffset = ea_size(eas[i].first, eas[i].second); ptr += ffeai.NextEntryOffset; } } } iosb.Information = 0xdeadbeef; Status = NtCreateFile(&h, access, &oa, &iosb, allocation ? &alloc_size : nullptr, atts, share, dispo, options, buf.data(), buf.size()); if (Status != STATUS_SUCCESS) { if (NT_SUCCESS(Status)) // STATUS_OPLOCK_BREAK_IN_PROGRESS etc. NtClose(h); throw ntstatus_error(Status); } if (iosb.Information != exp_info) throw formatted_error("iosb.Information was {}, expected {}", iosb.Information, exp_info); return unique_handle(h); } template static void check_ea_dirent(const u16string& dir, u16string_view name, uint32_t exp_size) { auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); if (fdi.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", fdi.EaSize, exp_size); } template static vector> read_eas(HANDLE h, const array& arr, bool single_entry, ULONG* index, bool restart) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buflist; size_t size; char buf[4096]; size = 0; for (unsigned int i = 0; i < N; i++) { size += offsetof(FILE_GET_EA_INFORMATION, EaName) + arr[i].size() + 1; if (i != N - 1 && size & 3) size = ((size >> 2) + 1) << 2; } buflist.resize(size); { auto ptr = buflist.data(); for (unsigned int i = 0; i < N; i++) { auto& fgeai = *(FILE_GET_EA_INFORMATION*)ptr; fgeai.NextEntryOffset = 0; fgeai.EaNameLength = arr[i].size(); memcpy(fgeai.EaName, arr[i].data(), arr[i].size()); fgeai.EaName[arr[i].size()] = 0; if (i != N - 1) { fgeai.NextEntryOffset = offsetof(FILE_GET_EA_INFORMATION, EaName) + arr[i].size() + 1; if (fgeai.NextEntryOffset & 3) fgeai.NextEntryOffset = ((fgeai.NextEntryOffset >> 2) + 1) << 2; ptr += fgeai.NextEntryOffset; } } } Status = NtQueryEaFile(h, &iosb, buf, sizeof(buf), single_entry, buflist.data(), buflist.size(), index, restart); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); vector> ret; auto ptr = buf; do { auto& ffeai = *(FILE_FULL_EA_INFORMATION*)ptr; ret.emplace_back(); auto& item = ret.back(); item.buf.resize(offsetof(FILE_FULL_EA_INFORMATION, EaName) + ffeai.EaNameLength + 1 + ffeai.EaValueLength); memcpy(item.buf.data(), &ffeai, item.buf.size()); if (ffeai.NextEntryOffset == 0) break; ptr += ffeai.NextEntryOffset; } while (true); return ret; } void test_ea(const u16string& dir) { unique_handle h; test("Create file", [&]() { h = create_file(dir + u"\\ea1", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { static const string_view ea_name = "hello", ea_value = "world"; test("Read EA", [&]() { exp_status([&]() { read_ea(h.get()); }, STATUS_NO_EAS_ON_FILE); }); test("Write EA", [&]() { write_ea(h.get(), ea_name, ea_value); }); test("Read EA", [&]() { auto items = read_ea(h.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& ffeai = *static_cast(items.front()); if (ffeai.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai.Flags); auto name = string_view(ffeai.EaName, ffeai.EaNameLength); if (name != "HELLO") // gets capitalized throw formatted_error("EA name was \"{}\", expected \"HELLO\"", name); auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength); if (value != ea_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value, ea_value); }); auto exp_size = ea_size(ea_name, ea_value); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", feai.EaSize, exp_size); }); test("Query FileAllInformation", [&]() { auto buf = query_all_information(h.get()); auto& fai = *static_cast(buf); if (fai.EaInformation.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", fai.EaInformation.EaSize, exp_size); }); h.reset(); // EaSize in dirents not updated until file closed? test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); } test("Open file", [&]() { h = create_file(dir + u"\\ea1", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { static const string_view ea1_name = "HELLO", ea1_value = "world"; static const string_view ea2_name = "fOo", ea2_value = "bar"; test("Add second EA", [&]() { write_ea(h.get(), ea2_name, ea2_value); }); test("Read EAs", [&]() { auto items = read_ea(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2", items.size()); auto& ffeai1 = *static_cast(items[0]); if (ffeai1.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai1.Flags); auto name1 = string_view(ffeai1.EaName, ffeai1.EaNameLength); if (name1 != ea1_name) throw formatted_error("EA name was \"{}\", expected \"{}\"", name1, ea1_name); auto value1 = string_view(ffeai1.EaName + ffeai1.EaNameLength + 1, ffeai1.EaValueLength); if (value1 != ea1_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value1, ea1_value); auto& ffeai2 = *static_cast(items[1]); if (ffeai2.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai2.Flags); auto name2 = string_view(ffeai2.EaName, ffeai2.EaNameLength); if (name2 != "FOO") throw formatted_error("EA name was \"{}\", expected \"FOO\"", name2); auto value2 = string_view(ffeai2.EaName + ffeai2.EaNameLength + 1, ffeai2.EaValueLength); if (value2 != ea2_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value2, ea2_value); }); auto exp_size = ea_size(ea1_name, ea1_value) + ea_size(ea2_name, ea2_value); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", feai.EaSize, exp_size); }); test("Query FileAllInformation", [&]() { auto buf = query_all_information(h.get()); auto& fai = *static_cast(buf); if (fai.EaInformation.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", fai.EaInformation.EaSize, exp_size); }); h.reset(); test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); } test("Open file", [&]() { h = create_file(dir + u"\\ea1", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { static const string_view ea1_name = "FOO", ea1_value = "bar"; static const string_view ea2_name = "HeLlO", ea2_value = "baz"; test("Replace first EA", [&]() { write_ea(h.get(), ea2_name, ea2_value); }); test("Read EAs", [&]() { auto items = read_ea(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2", items.size()); auto& ffeai1 = *static_cast(items[0]); if (ffeai1.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai1.Flags); auto name1 = string_view(ffeai1.EaName, ffeai1.EaNameLength); if (name1 != ea1_name) throw formatted_error("EA name was \"{}\", expected \"{}\"", name1, ea1_name); auto value1 = string_view(ffeai1.EaName + ffeai1.EaNameLength + 1, ffeai1.EaValueLength); if (value1 != ea1_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value1, ea1_value); auto& ffeai2 = *static_cast(items[1]); if (ffeai2.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai2.Flags); auto name2 = string_view(ffeai2.EaName, ffeai2.EaNameLength); if (name2 != "HELLO") throw formatted_error("EA name was \"{}\", expected \"HELLO\"", name2); auto value2 = string_view(ffeai2.EaName + ffeai2.EaNameLength + 1, ffeai2.EaValueLength); if (value2 != ea2_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value2, ea2_value); }); auto exp_size = ea_size(ea1_name, ea1_value) + ea_size(ea2_name, ea2_value); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", feai.EaSize, exp_size); }); test("Query FileAllInformation", [&]() { auto buf = query_all_information(h.get()); auto& fai = *static_cast(buf); if (fai.EaInformation.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", fai.EaInformation.EaSize, exp_size); }); h.reset(); test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); } test("Open file", [&]() { h = create_file(dir + u"\\ea1", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { static const string_view ea1_name = "FOO", ea1_value = "bar"; static const string_view ea2_name = "HELlo"; test("Delete first EA", [&]() { write_ea(h.get(), ea2_name, ""); }); test("Read EAs", [&]() { auto items = read_ea(h.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& ffeai1 = *static_cast(items[0]); if (ffeai1.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai1.Flags); auto name1 = string_view(ffeai1.EaName, ffeai1.EaNameLength); if (name1 != ea1_name) throw formatted_error("EA name was \"{}\", expected \"{}\"", name1, ea1_name); auto value1 = string_view(ffeai1.EaName + ffeai1.EaNameLength + 1, ffeai1.EaValueLength); if (value1 != ea1_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value1, ea1_value); }); auto exp_size = ea_size(ea1_name, ea1_value); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", feai.EaSize, exp_size); }); test("Query FileAllInformation", [&]() { auto buf = query_all_information(h.get()); auto& fai = *static_cast(buf); if (fai.EaInformation.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", fai.EaInformation.EaSize, exp_size); }); h.reset(); test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); } test("Open file", [&]() { h = create_file(dir + u"\\ea1", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { static const string_view ea1_name = "foo"; test("Delete second EA", [&]() { write_ea(h.get(), ea1_name, ""); }); test("Read EAs", [&]() { exp_status([&]() { read_ea(h.get()); }, STATUS_NO_EAS_ON_FILE); }); uint32_t exp_size = 0; test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", feai.EaSize, exp_size); }); test("Query FileAllInformation", [&]() { auto buf = query_all_information(h.get()); auto& fai = *static_cast(buf); if (fai.EaInformation.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", fai.EaInformation.EaSize, exp_size); }); h.reset(); test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea1", exp_size); }); } test("Open file", [&]() { h = create_file(dir + u"\\ea2", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { static const string_view ea1_name = "qux", ea1_value = "xyzzy"; static const string_view ea2_name = "y2", ea2_value = "plugh"; test("Add two EAs", [&]() { write_eas(h.get(), array{ pair(ea1_name, ea1_value), pair(ea2_name, ea2_value) }); }); test("Read EAs", [&]() { auto items = read_ea(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2", items.size()); auto& ffeai1 = *static_cast(items[0]); if (ffeai1.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai1.Flags); auto name1 = string_view(ffeai1.EaName, ffeai1.EaNameLength); if (name1 != "QUX") throw formatted_error("EA name was \"{}\", expected \"QUX\"", name1); auto value1 = string_view(ffeai1.EaName + ffeai1.EaNameLength + 1, ffeai1.EaValueLength); if (value1 != ea1_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value1, ea1_value); auto& ffeai2 = *static_cast(items[1]); if (ffeai2.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai2.Flags); auto name2 = string_view(ffeai2.EaName, ffeai2.EaNameLength); if (name2 != "Y2") throw formatted_error("EA name was \"{}\", expected \"Y2\"", name2); auto value2 = string_view(ffeai2.EaName + ffeai2.EaNameLength + 1, ffeai2.EaValueLength); if (value2 != ea2_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value2, ea2_value); }); uint32_t exp_size = ea_size(ea1_name, ea1_value) + ea_size(ea2_name, ea2_value); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", feai.EaSize, exp_size); }); test("Query FileAllInformation", [&]() { auto buf = query_all_information(h.get()); auto& fai = *static_cast(buf); if (fai.EaInformation.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", fai.EaInformation.EaSize, exp_size); }); h.reset(); test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea2", exp_size); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea2", exp_size); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea2", exp_size); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea2", exp_size); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea2", exp_size); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea2", exp_size); }); } test("Create file with EAs", [&]() { static const string_view ea1_name = "foo", ea1_value = "bar"; static const string_view ea2_name = "baz", ea2_value = "quux"; h = create_file_ea(dir + u"\\ea3", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, 0, FILE_CREATED, nullopt, array{ pair(ea1_name, ea1_value), pair(ea2_name, ea2_value) }); }); if (h) { static const string_view ea1_name = "foo", ea1_value = "bar"; static const string_view ea2_name = "baz", ea2_value = "quux"; test("Read EAs", [&]() { auto items = read_ea(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2", items.size()); auto& ffeai1 = *static_cast(items[0]); if (ffeai1.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai1.Flags); auto name1 = string_view(ffeai1.EaName, ffeai1.EaNameLength); if (name1 != "FOO") throw formatted_error("EA name was \"{}\", expected \"FOO\"", name1); auto value1 = string_view(ffeai1.EaName + ffeai1.EaNameLength + 1, ffeai1.EaValueLength); if (value1 != ea1_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value1, ea1_value); auto& ffeai2 = *static_cast(items[1]); if (ffeai2.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai2.Flags); auto name2 = string_view(ffeai2.EaName, ffeai2.EaNameLength); if (name2 != "BAZ") throw formatted_error("EA name was \"{}\", expected \"BAZ\"", name2); auto value2 = string_view(ffeai2.EaName + ffeai2.EaNameLength + 1, ffeai2.EaValueLength); if (value2 != ea2_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value2, ea2_value); }); uint32_t exp_size = ea_size(ea1_name, ea1_value) + ea_size(ea2_name, ea2_value); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", feai.EaSize, exp_size); }); test("Query FileAllInformation", [&]() { auto buf = query_all_information(h.get()); auto& fai = *static_cast(buf); if (fai.EaInformation.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", fai.EaInformation.EaSize, exp_size); }); h.reset(); test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea3", exp_size); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea3", exp_size); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea3", exp_size); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea3", exp_size); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea3", exp_size); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea3", exp_size); }); } test("Create file", [&]() { h = create_file(dir + u"\\ea4", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { static const string_view ea_name = "hello", ea_value = "world"; test("Write EA", [&]() { write_ea(h.get(), ea_name, ea_value); }); test("Read EA", [&]() { auto items = read_ea(h.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& ffeai = *static_cast(items.front()); if (ffeai.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai.Flags); auto name = string_view(ffeai.EaName, ffeai.EaNameLength); if (name != "HELLO") // gets capitalized throw formatted_error("EA name was \"{}\", expected \"HELLO\"", name); auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength); if (value != ea_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value, ea_value); }); auto exp_size = ea_size(ea_name, ea_value); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", feai.EaSize, exp_size); }); test("Query FileAllInformation", [&]() { auto buf = query_all_information(h.get()); auto& fai = *static_cast(buf); if (fai.EaInformation.EaSize != exp_size) throw formatted_error("EaSize was {}, expected {}", fai.EaInformation.EaSize, exp_size); }); h.reset(); // EaSize in dirents not updated until file closed? test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea4", exp_size); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea4", exp_size); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea4", exp_size); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea4", exp_size); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea4", exp_size); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_ea_dirent(dir, u"ea4", exp_size); }); } test("Create file", [&]() { h = create_file(dir + u"\\ea5", FILE_WRITE_EA | FILE_READ_EA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { static const string_view ea_name = "hello", ea_value = "world"; test("Write EA with FILE_NEED_EA", [&]() { write_ea(h.get(), ea_name, ea_value, true); }); test("Read EA", [&]() { auto items = read_ea(h.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& ffeai = *static_cast(items.front()); if (ffeai.Flags != FILE_NEED_EA) throw formatted_error("Flags was {:x}, expected FILE_NEED_EA", ffeai.Flags); auto name = string_view(ffeai.EaName, ffeai.EaNameLength); if (name != "HELLO") // gets capitalized throw formatted_error("EA name was \"{}\", expected \"HELLO\"", name); auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength); if (value != ea_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value, ea_value); }); h.reset(); } test("Open file with FILE_NO_EA_KNOWLEDGE", [&]() { exp_status([&]() { create_file(dir + u"\\ea5", FILE_WRITE_EA | FILE_READ_EA, 0, 0, FILE_CREATE, FILE_NO_EA_KNOWLEDGE, FILE_CREATED); }, STATUS_ACCESS_DENIED); }); test("Create file without FILE_WRITE_EA", [&]() { h = create_file(dir + u"\\ea6", FILE_READ_EA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { static const string_view ea_name = "hello", ea_value = "world"; test("Try to write EA", [&]() { exp_status([&]() { write_ea(h.get(), ea_name, ea_value, false); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Open file without FILE_READ_EA", [&]() { h = create_file(dir + u"\\ea6", FILE_WRITE_EA, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Try to read EA", [&]() { exp_status([&]() { read_ea(h.get()); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\ea7", FILE_WRITE_EA | FILE_READ_EA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { static const string_view ea1_name = "hello", ea1_value = "world"; static const string_view ea2_name = "foo", ea2_value = "bar"; static const string_view ea3_name = "xyzzy", ea3_value = "plugh"; test("Write EAs", [&]() { write_eas(h.get(), array{ pair(ea1_name, ea1_value), pair(ea2_name, ea2_value), pair(ea3_name, ea3_value) }); }); test("Read EAs with filter", [&]() { auto items = read_eas(h.get(), array{ ea2_name, ea3_name }, false, nullptr, true); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2", items.size()); auto& ffeai1 = *static_cast(items[0]); if (ffeai1.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai1.Flags); auto name1 = string_view(ffeai1.EaName, ffeai1.EaNameLength); if (name1 != "FOO") // gets capitalized throw formatted_error("EA name was \"{}\", expected \"FOO\"", name1); auto value1 = string_view(ffeai1.EaName + ffeai1.EaNameLength + 1, ffeai1.EaValueLength); if (value1 != ea2_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value1, ea2_value); auto& ffeai2 = *static_cast(items[1]); if (ffeai2.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai2.Flags); auto name2 = string_view(ffeai2.EaName, ffeai2.EaNameLength); if (name2 != "XYZZY") // gets capitalized throw formatted_error("EA name was \"{}\", expected \"FOO\"", name2); auto value2 = string_view(ffeai2.EaName + ffeai2.EaNameLength + 1, ffeai2.EaValueLength); if (value2 != ea3_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value2, ea3_value); }); test("Read single EA", [&]() { auto items = read_eas(h.get(), array{ ea2_name, ea3_name }, true, nullptr, true); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& ffeai = *static_cast(items.front()); if (ffeai.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai.Flags); auto name = string_view(ffeai.EaName, ffeai.EaNameLength); if (name != "FOO") // gets capitalized throw formatted_error("EA name was \"{}\", expected \"FOO\"", name); auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength); if (value != ea2_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value, ea2_value); }); test("Read next EA", [&]() { // still returns first entry auto items = read_eas(h.get(), array{ ea2_name, ea3_name }, true, nullptr, false); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& ffeai = *static_cast(items.front()); if (ffeai.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai.Flags); auto name = string_view(ffeai.EaName, ffeai.EaNameLength); if (name != "FOO") // gets capitalized throw formatted_error("EA name was \"{}\", expected \"FOO\"", name); auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength); if (value != ea2_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value, ea2_value); }); test("Read single EA without filter", [&]() { auto items = read_eas(h.get(), array{}, true, nullptr, true); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& ffeai = *static_cast(items.front()); if (ffeai.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai.Flags); auto name = string_view(ffeai.EaName, ffeai.EaNameLength); if (name != "HELLO") // gets capitalized throw formatted_error("EA name was \"{}\", expected \"HELLO\"", name); auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength); if (value != ea1_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value, ea1_value); }); test("Read single EA without filter", [&]() { auto items = read_eas(h.get(), array{}, true, nullptr, false); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& ffeai = *static_cast(items.front()); if (ffeai.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai.Flags); auto name = string_view(ffeai.EaName, ffeai.EaNameLength); if (name != "FOO") // gets capitalized throw formatted_error("EA name was \"{}\", expected \"FOO\"", name); auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength); if (value != ea2_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value, ea2_value); }); test("Read single EA without filter", [&]() { auto items = read_eas(h.get(), array{}, true, nullptr, false); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& ffeai = *static_cast(items.front()); if (ffeai.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai.Flags); auto name = string_view(ffeai.EaName, ffeai.EaNameLength); if (name != "XYZZY") // gets capitalized throw formatted_error("EA name was \"{}\", expected \"XYZZY\"", name); auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength); if (value != ea3_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value, ea3_value); }); test("Read single EA without filter", [&]() { exp_status([&]() { read_eas(h.get(), array{}, true, nullptr, false); }, STATUS_NO_MORE_EAS); }); test("Read single EA with offset", [&]() { ULONG index = 3; // starts at 1 auto items = read_eas(h.get(), array{}, true, &index, false); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& ffeai = *static_cast(items.front()); if (ffeai.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai.Flags); auto name = string_view(ffeai.EaName, ffeai.EaNameLength); if (name != "XYZZY") // gets capitalized throw formatted_error("EA name was \"{}\", expected \"XYZZY\"", name); auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength); if (value != ea3_value) throw formatted_error("EA value was \"{}\", expected \"{}\"", value, ea3_value); }); test("Read non-existent EA", [&]() { auto items = read_eas(h.get(), array{ "baz"sv }, false, nullptr, true); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& ffeai = *static_cast(items.front()); if (ffeai.Flags != 0) throw formatted_error("Flags was {:x}, expected 0", ffeai.Flags); auto name = string_view(ffeai.EaName, ffeai.EaNameLength); if (name != "BAZ") // gets capitalized throw formatted_error("EA name was \"{}\", expected \"BAZ\"", name); auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength); if (value != "") throw formatted_error("EA value was \"{}\", expected \"\"", value); }); h.reset(); } test("Open volume", [&]() { auto colon = dir.find(u":"); if (colon == string::npos) throw runtime_error("Unable to extract volume path from directory."); auto vol = dir.substr(0, colon + 1); h = create_file(vol, FILE_WRITE_EA | FILE_READ_EA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Try to write EA on volume", [&]() { exp_status([&]() { write_ea(h.get(), "HELLO", "world", true); }, STATUS_INVALID_PARAMETER); }); test("Try to read EA on volume", [&]() { exp_status([&]() { read_ea(h.get()); }, STATUS_INVALID_PARAMETER); }); } } ================================================ FILE: src/tests/fileinfo.cpp ================================================ #include "test.h" using namespace std; void set_basic_information(HANDLE h, int64_t creation_time, int64_t last_access_time, int64_t last_write_time, int64_t change_time, uint32_t attributes) { NTSTATUS Status; IO_STATUS_BLOCK iosb; FILE_BASIC_INFORMATION fbi; fbi.CreationTime.QuadPart = creation_time; fbi.LastAccessTime.QuadPart = last_access_time; fbi.LastWriteTime.QuadPart = last_write_time; fbi.ChangeTime.QuadPart = change_time; fbi.FileAttributes = attributes; Status = NtSetInformationFile(h, &iosb, &fbi, sizeof(fbi), FileBasicInformation); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != 0) throw formatted_error("iosb.Information was {}, expected 0", iosb.Information); } void test_fileinfo(const u16string& dir) { unique_handle h; LARGE_INTEGER delay; // ignoring LastAccessTime for the most part here, as the NtfsDisableLastAccessUpdate option means it's unpredictable delay.QuadPart = -1000000; // 100ms - should be 2 seconds for FAT? test("Create file", [&]() { h = create_file(dir + u"\\fileinfo1", SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { static const vector data = {'a','b','c','d','e','f'}; FILE_BASIC_INFORMATION fbi; test("Query basic information", [&]() { fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_ARCHIVE", fbi.FileAttributes); }); test("Set times and attributes to 0", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, 0); }); test("Check times and attributes unchanged", [&]() { auto fbi2 = query_information(h.get()); if (fbi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart) throw formatted_error("CreationTime was {}, expected {}", fbi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart); if (fbi2.LastAccessTime.QuadPart != fbi.LastAccessTime.QuadPart) throw formatted_error("LastAccessTime was {}, expected {}", fbi2.LastAccessTime.QuadPart, fbi.LastAccessTime.QuadPart); if (fbi2.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart) throw formatted_error("LastWriteTime was {}, expected {}", fbi2.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart); if (fbi2.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart) throw formatted_error("ChangeTime was {}, expected {}", fbi2.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart); if (fbi2.FileAttributes != fbi.FileAttributes) throw formatted_error("FileAttributes was {:x}, expected {:x}", fbi2.FileAttributes, fbi.FileAttributes); }); NtDelayExecution(false, &delay); test("Write to file", [&]() { write_file(h.get(), data); }); test("Query basic information", [&]() { auto fbi2 = query_information(h.get()); if (fbi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart) throw formatted_error("CreationTime was {}, expected {}", fbi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart); if (fbi2.LastWriteTime.QuadPart == fbi.LastWriteTime.QuadPart) throw runtime_error("LastWriteTime was unchanged"); if (fbi2.ChangeTime.QuadPart == fbi.ChangeTime.QuadPart) throw runtime_error("ChangeTime was unchanged"); if (fbi2.FileAttributes != fbi.FileAttributes) throw formatted_error("FileAttributes was {:x}, expected {:x}", fbi2.FileAttributes, fbi.FileAttributes); fbi = fbi2; }); test("Set LastWriteTime to -1", [&]() { set_basic_information(h.get(), 0, 0, -1, 0, 0); }); NtDelayExecution(false, &delay); test("Write to file", [&]() { write_file(h.get(), data); }); test("Query basic information", [&]() { auto fbi2 = query_information(h.get()); if (fbi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart) throw formatted_error("CreationTime was {}, expected {}", fbi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart); if (fbi2.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart) throw formatted_error("LastWriteTime was {}, expected {}", fbi2.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart); if (fbi2.ChangeTime.QuadPart == fbi.ChangeTime.QuadPart) throw runtime_error("ChangeTime was unchanged"); if (fbi2.FileAttributes != fbi.FileAttributes) throw formatted_error("FileAttributes was {:x}, expected {:x}", fbi2.FileAttributes, fbi.FileAttributes); fbi = fbi2; }); test("Set ChangeTime to -1", [&]() { set_basic_information(h.get(), 0, 0, 0, -1, 0); }); NtDelayExecution(false, &delay); test("Write to file", [&]() { write_file(h.get(), data); }); test("Query basic information", [&]() { auto fbi2 = query_information(h.get()); if (fbi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart) throw formatted_error("CreationTime was {}, expected {}", fbi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart); if (fbi2.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart) throw formatted_error("LastWriteTime was {}, expected {}", fbi2.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart); if (fbi2.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart) throw formatted_error("ChangeTime was {}, expected {}", fbi2.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart); if (fbi2.FileAttributes != fbi.FileAttributes) throw formatted_error("FileAttributes was {:x}, expected {:x}", fbi2.FileAttributes, fbi.FileAttributes); fbi = fbi2; }); test("Set LastWriteTime to -2", [&]() { set_basic_information(h.get(), 0, 0, -2, 0, 0); }); NtDelayExecution(false, &delay); test("Write to file", [&]() { write_file(h.get(), data); }); test("Query basic information", [&]() { auto fbi2 = query_information(h.get()); if (fbi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart) throw formatted_error("CreationTime was {}, expected {}", fbi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart); if (fbi2.LastWriteTime.QuadPart == fbi.LastWriteTime.QuadPart) throw runtime_error("LastWriteTime was unchanged"); if (fbi2.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart) throw formatted_error("ChangeTime was {}, expected {}", fbi2.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart); if (fbi2.FileAttributes != fbi.FileAttributes) throw formatted_error("FileAttributes was {:x}, expected {:x}", fbi2.FileAttributes, fbi.FileAttributes); fbi = fbi2; }); test("Set ChangeTime to -2", [&]() { set_basic_information(h.get(), 0, 0, 0, -2, 0); }); NtDelayExecution(false, &delay); test("Write to file", [&]() { write_file(h.get(), data); }); test("Query basic information", [&]() { auto fbi2 = query_information(h.get()); if (fbi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart) throw formatted_error("CreationTime was {}, expected {}", fbi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart); if (fbi2.LastWriteTime.QuadPart == fbi.LastWriteTime.QuadPart) throw runtime_error("LastWriteTime was unchanged"); if (fbi2.ChangeTime.QuadPart == fbi.ChangeTime.QuadPart) throw runtime_error("ChangeTime was unchanged"); if (fbi2.FileAttributes != fbi.FileAttributes) throw formatted_error("FileAttributes was {:x}, expected {:x}", fbi2.FileAttributes, fbi.FileAttributes); fbi = fbi2; }); int64_t date_val = 128790414900000000; // 2009-02-13T23:31:30 NtDelayExecution(false, &delay); test("Set CreationTime", [&]() { set_basic_information(h.get(), date_val, 0, 0, 0, 0); }); test("Query basic information", [&]() { auto fbi2 = query_information(h.get()); if (fbi2.CreationTime.QuadPart != date_val) throw formatted_error("CreationTime was {}, expected {}", fbi2.CreationTime.QuadPart, date_val); if (fbi2.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart) throw formatted_error("LastWriteTime was {}, expected {}", fbi2.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart); if (fbi2.ChangeTime.QuadPart == fbi.ChangeTime.QuadPart) throw runtime_error("ChangeTime was unchanged"); if (fbi2.FileAttributes != fbi.FileAttributes) throw formatted_error("FileAttributes was {:x}, expected {:x}", fbi2.FileAttributes, fbi.FileAttributes); fbi = fbi2; }); NtDelayExecution(false, &delay); test("Set LastWriteTime", [&]() { set_basic_information(h.get(), 0, 0, date_val, 0, 0); }); test("Query basic information", [&]() { auto fbi2 = query_information(h.get()); if (fbi2.CreationTime.QuadPart != date_val) throw formatted_error("CreationTime was {}, expected {}", fbi2.CreationTime.QuadPart, date_val); if (fbi2.LastWriteTime.QuadPart != date_val) throw formatted_error("LastWriteTime was {}, expected {}", fbi2.LastWriteTime.QuadPart, date_val); if (fbi2.ChangeTime.QuadPart == fbi.ChangeTime.QuadPart) throw runtime_error("ChangeTime was unchanged"); if (fbi2.FileAttributes != fbi.FileAttributes) throw formatted_error("FileAttributes was {:x}, expected {:x}", fbi2.FileAttributes, fbi.FileAttributes); fbi = fbi2; }); NtDelayExecution(false, &delay); test("Set ChangeTime", [&]() { set_basic_information(h.get(), 0, 0, 0, date_val, 0); }); test("Query basic information", [&]() { auto fbi2 = query_information(h.get()); if (fbi2.CreationTime.QuadPart != date_val) throw formatted_error("CreationTime was {}, expected {}", fbi2.CreationTime.QuadPart, date_val); if (fbi2.LastWriteTime.QuadPart != date_val) throw formatted_error("LastWriteTime was {}, expected {}", fbi2.LastWriteTime.QuadPart, date_val); if (fbi2.ChangeTime.QuadPart != date_val) throw formatted_error("ChangeTime was {}, expected {}", fbi2.ChangeTime.QuadPart, date_val); if (fbi2.FileAttributes != fbi.FileAttributes) throw formatted_error("FileAttributes was {:x}, expected {:x}", fbi2.FileAttributes, fbi.FileAttributes); fbi = fbi2; }); h.reset(); } test("Open file without FILE_READ_ATTRIBUTES", [&]() { h = create_file(dir + u"\\fileinfo1", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Try to query basic information", [&]() { exp_status([&]() { query_information(h.get()); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Open file without FILE_WRITE_ATTRIBUTES", [&]() { h = create_file(dir + u"\\fileinfo1", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Try to set basic information", [&]() { exp_status([&]() { set_basic_information(h.get(), 0, 0, 0, 0, 0); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\fileinfo1", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Set hidden flag", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_HIDDEN); }); test("Query basic information", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_HIDDEN) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_HIDDEN", fbi.FileAttributes); }); test("Set attributes to FILE_ATTRIBUTE_NORMAL", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL); }); test("Query basic information", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL", fbi.FileAttributes); }); test("Set attributes to FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_READONLY", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_READONLY); }); test("Query basic information", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_READONLY) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_READONLY", fbi.FileAttributes); }); test("Try to set attributes to FILE_ATTRIBUTE_DIRECTORY", [&]() { // fails exp_status([&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_DIRECTORY); }, STATUS_INVALID_PARAMETER); }); test("Set attributes to FILE_ATTRIBUTE_REPARSE_POINT", [&]() { // gets ignored set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_REPARSE_POINT); }); test("Query basic information", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL", fbi.FileAttributes); }); test("Set attributes to FILE_ATTRIBUTE_SPARSE_FILE", [&]() { // gets ignored set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_SPARSE_FILE); }); test("Query basic information", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL", fbi.FileAttributes); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\fileinfo2", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Set hidden flag", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_HIDDEN); }); test("Query basic information", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN)) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN", fbi.FileAttributes); }); test("Set attributes to FILE_ATTRIBUTE_NORMAL", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL); }); test("Query basic information", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY", fbi.FileAttributes); }); test("Set attributes to FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_READONLY", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_READONLY); }); test("Query basic information", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY)) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY", fbi.FileAttributes); }); test("Set attributes to FILE_ATTRIBUTE_DIRECTORY", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_DIRECTORY); }); test("Query basic information", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY", fbi.FileAttributes); }); test("Set attributes to FILE_ATTRIBUTE_REPARSE_POINT", [&]() { // gets ignored set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_REPARSE_POINT); }); test("Query basic information", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY", fbi.FileAttributes); }); test("Set attributes to FILE_ATTRIBUTE_SPARSE_FILE", [&]() { // gets ignored set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_SPARSE_FILE); }); test("Query basic information", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY", fbi.FileAttributes); }); h.reset(); } } ================================================ FILE: src/tests/io.cpp ================================================ #include "test.h" #include #include #include using namespace std; #define FSCTL_SET_ZERO_DATA CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 50, METHOD_BUFFERED, FILE_WRITE_DATA) void adjust_token_privileges(HANDLE token, const LUID_AND_ATTRIBUTES& priv) { NTSTATUS Status; array buf; auto& tp = *(TOKEN_PRIVILEGES*)buf.data(); tp.PrivilegeCount = 1; tp.Privileges[0] = priv; Status = NtAdjustPrivilegesToken(token, false, &tp, 0, nullptr, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } void set_allocation(HANDLE h, uint64_t alloc) { NTSTATUS Status; IO_STATUS_BLOCK iosb; FILE_ALLOCATION_INFORMATION fai; fai.AllocationSize.QuadPart = alloc; Status = NtSetInformationFile(h, &iosb, &fai, sizeof(fai), FileAllocationInformation); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != 0) throw formatted_error("iosb.Information was {}, expected 0", iosb.Information); } vector random_data(size_t len) { vector random(len); random_device rd; mt19937 gen(rd()); uniform_int_distribution distrib(0, 0xffffffff); for (auto& s : span((uint32_t*)random.data(), random.size() / sizeof(uint32_t))) { s = distrib(gen); } return random; } void write_file(HANDLE h, span data, optional offset) { NTSTATUS Status; IO_STATUS_BLOCK iosb; LARGE_INTEGER off; if (offset) off.QuadPart = *offset; Status = NtWriteFile(h, nullptr, nullptr, nullptr, &iosb, (void*)data.data(), data.size(), offset ? &off : nullptr, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != data.size()) throw formatted_error("iosb.Information was {}, expected {}", iosb.Information, data.size()); } unique_handle create_event() { NTSTATUS Status; HANDLE h; Status = NtCreateEvent(&h, EVENT_ALL_ACCESS, nullptr, NotificationEvent, false); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); return unique_handle(h); } void write_file_wait(HANDLE h, span data, optional offset) { NTSTATUS Status; IO_STATUS_BLOCK iosb; LARGE_INTEGER off; if (offset) off.QuadPart = *offset; auto event = create_event(); Status = NtWriteFile(h, event.get(), nullptr, nullptr, &iosb, (void*)data.data(), data.size(), offset ? &off : nullptr, nullptr); if (Status == STATUS_PENDING) { Status = NtWaitForSingleObject(event.get(), false, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); Status = iosb.Status; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != data.size()) throw formatted_error("iosb.Information was {}, expected {}", iosb.Information, data.size()); } vector read_file(HANDLE h, ULONG len, optional offset) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf(len); LARGE_INTEGER off; if (offset) off.QuadPart = *offset; buf.resize(len); Status = NtReadFile(h, nullptr, nullptr, nullptr, &iosb, buf.data(), len, offset ? &off : nullptr, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information > len) throw formatted_error("iosb.Information was {}, expected maximum of {}", iosb.Information, len); buf.resize(iosb.Information); return buf; } vector read_file_wait(HANDLE h, ULONG len, optional offset) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf(len); LARGE_INTEGER off; if (offset) off.QuadPart = *offset; buf.resize(len); auto event = create_event(); Status = NtReadFile(h, event.get(), nullptr, nullptr, &iosb, buf.data(), len, offset ? &off : nullptr, nullptr); if (Status == STATUS_PENDING) { Status = NtWaitForSingleObject(event.get(), false, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); Status = iosb.Status; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information > len) throw formatted_error("iosb.Information was {}, expected maximum of {}", iosb.Information, len); buf.resize(iosb.Information); return buf; } static void set_position(HANDLE h, uint64_t pos) { NTSTATUS Status; IO_STATUS_BLOCK iosb; FILE_POSITION_INFORMATION fpi; fpi.CurrentByteOffset.QuadPart = pos; iosb.Information = 0xdeadbeef; Status = NtSetInformationFile(h, &iosb, &fpi, sizeof(fpi), FilePositionInformation); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != 0) throw formatted_error("iosb.Information was {}, expected 0", iosb.Information); } void set_valid_data_length(HANDLE h, uint64_t vdl) { NTSTATUS Status; IO_STATUS_BLOCK iosb; FILE_VALID_DATA_LENGTH_INFORMATION fvdli; fvdli.ValidDataLength.QuadPart = vdl; Status = NtSetInformationFile(h, &iosb, &fvdli, sizeof(fvdli), FileValidDataLengthInformation); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != 0) throw formatted_error("iosb.Information was {}, expected 0", iosb.Information); } void set_end_of_file(HANDLE h, uint64_t eof) { NTSTATUS Status; IO_STATUS_BLOCK iosb; FILE_END_OF_FILE_INFORMATION feofi; feofi.EndOfFile.QuadPart = eof; Status = NtSetInformationFile(h, &iosb, &feofi, sizeof(feofi), FileEndOfFileInformation); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != 0) throw formatted_error("iosb.Information was {}, expected 0", iosb.Information); } void set_zero_data(HANDLE h, uint64_t start, uint64_t end) { NTSTATUS Status; FILE_ZERO_DATA_INFORMATION fzdi; IO_STATUS_BLOCK iosb; fzdi.FileOffset.QuadPart = start; fzdi.BeyondFinalZero.QuadPart = end; auto ev = create_event(); Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb, FSCTL_SET_ZERO_DATA, &fzdi, sizeof(fzdi), nullptr, 0); if (Status == STATUS_PENDING) { Status = NtWaitForSingleObject(ev.get(), false, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); Status = iosb.Status; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } void test_io(HANDLE token, const u16string& dir) { unique_handle h; // needed to set VDL test("Add SeManageVolumePrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); auto write_check = [&](uint64_t multiple, bool sector_align) { auto random = random_data(multiple * 2); test("Write file", [&]() { write_file(h.get(), random); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size()) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, random.size()); } }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if ((uint64_t)fsi.AllocationSize.QuadPart < random.size()) { throw formatted_error("AllocationSize was {}, expected at least {}", fsi.AllocationSize.QuadPart, random.size()); } if ((uint64_t)fsi.EndOfFile.QuadPart != random.size()) { throw formatted_error("EndOfFile was {}, expected {}", fsi.EndOfFile.QuadPart, random.size()); } }); test("Check compression information", [&]() { auto fci = query_information(h.get()); if ((size_t)fci.CompressedFileSize.QuadPart != random.size()) throw formatted_error("CompressedFileSize was {}, expected {}", fci.CompressedFileSize.QuadPart, random.size()); }); test("Read file at end", [&]() { exp_status([&]() { read_file(h.get(), sector_align ? multiple : 100); }, STATUS_END_OF_FILE); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size()) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, random.size()); } }); if (sector_align) { test("Set position", [&]() { set_position(h.get(), random.size() + multiple); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size() + multiple) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, random.size() + multiple); } }); test("Read file after end", [&]() { exp_status([&]() { read_file(h.get(), multiple); }, STATUS_END_OF_FILE); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size() + multiple) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, random.size() + multiple); } }); } else { test("Set position", [&]() { set_position(h.get(), random.size() + 100); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size() + 100) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, random.size() + 100); } }); test("Read file after end", [&]() { exp_status([&]() { read_file(h.get(), 100); }, STATUS_END_OF_FILE); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size() + 100) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, random.size() + 100); } }); } test("Set negative position", [&]() { exp_status([&]() { set_position(h.get(), -100); }, STATUS_INVALID_PARAMETER); }); test("Set position to start", [&]() { set_position(h.get(), 0); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 0) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 0); } }); test("Read whole file", [&]() { auto ret = read_file(h.get(), random.size()); if (ret.size() != random.size()) throw formatted_error("{} bytes read, expected {}", ret.size(), random.size()); if (memcmp(ret.data(), random.data(), random.size())) throw runtime_error("Data read did not match data written"); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size()) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, random.size()); } }); test("Set position to start", [&]() { set_position(h.get(), 0); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 0) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 0); } }); if (sector_align) { test("Try reading 100 bytes", [&]() { exp_status([&]() { read_file(h.get(), 100); }, STATUS_INVALID_PARAMETER); }); test("Try seting position to odd value near end", [&]() { exp_status([&]() { set_position(h.get(), random.size() - 100); }, STATUS_INVALID_PARAMETER); }); test("Set position to near end", [&]() { set_position(h.get(), random.size() - multiple); }); test("Read past end of file", [&]() { auto ret = read_file(h.get(), multiple * 2); if (ret.size() != multiple) throw formatted_error("{} bytes read, expected {}", ret.size(), multiple); if (memcmp(ret.data(), random.data() + random.size() - multiple, multiple)) throw runtime_error("Data read did not match data written"); }); } else { test("Read 100 bytes", [&]() { auto ret = read_file(h.get(), 100); if (ret.size() != 100) throw formatted_error("{} bytes read, expected {}", ret.size(), 100); if (memcmp(ret.data(), random.data(), 100)) throw runtime_error("Data read did not match data written"); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 100) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 100); } }); test("Set position to near end", [&]() { set_position(h.get(), random.size() - 100); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size() - 100) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, random.size() - 100); } }); test("Read past end of file", [&]() { auto ret = read_file(h.get(), 200); if (ret.size() != 100) throw formatted_error("{} bytes read, expected {}", ret.size(), 100); if (memcmp(ret.data(), random.data() + random.size() - 100, 100)) throw runtime_error("Data read did not match data written"); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size()) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, random.size()); } }); } test("Extend file", [&]() { set_end_of_file(h.get(), multiple * 3); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if ((uint64_t)fsi.EndOfFile.QuadPart != multiple * 3) { throw formatted_error("EndOfFile was {}, expected {}", fsi.EndOfFile.QuadPart, random.size()); } }); test("Read new end of file", [&]() { auto ret = read_file(h.get(), multiple * 2); if (ret.size() != multiple) throw formatted_error("{} bytes read, expected {}", ret.size(), multiple); auto it = ranges::find_if(ret, [](uint8_t c) { return c != 0; }); if (it != ret.end()) throw runtime_error("End of file not zeroed"); }); test("Try setting valid data length to 0", [&]() { exp_status([&]() { set_valid_data_length(h.get(), 0); }, STATUS_INVALID_PARAMETER); }); test("Set valid data length to end of file", [&]() { set_valid_data_length(h.get(), multiple * 3); }); test("Try setting valid data length to after end of file", [&]() { exp_status([&]() { set_valid_data_length(h.get(), multiple * 4); }, STATUS_INVALID_PARAMETER); }); test("Truncate file", [&]() { set_end_of_file(h.get(), multiple); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if ((uint64_t)fsi.EndOfFile.QuadPart != multiple) { throw formatted_error("EndOfFile was {}, expected {}", fsi.EndOfFile.QuadPart, multiple); } }); test("Set position to start", [&]() { set_position(h.get(), 0); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 0) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 0); } }); test("Read whole file", [&]() { auto ret = read_file(h.get(), random.size()); if (ret.size() != multiple) throw formatted_error("{} bytes read, expected {}", ret.size(), multiple); if (memcmp(ret.data(), random.data(), multiple)) throw runtime_error("Data read did not match data written"); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != multiple) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, random.size()); } }); }; test("Create file (long)", [&]() { h = create_file(dir + u"\\io", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { write_check(4096, false); h.reset(); } test("Create file (short)", [&]() { h = create_file(dir + u"\\ioshort", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { write_check(200, false); h.reset(); } disable_token_privileges(token); test("Create file", [&]() { h = create_file(dir + u"\\io2", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Extend file", [&]() { set_end_of_file(h.get(), 4096); }); test("Try setting valid data length without privilege", [&]() { exp_status([&]() { set_valid_data_length(h.get(), 4096); }, STATUS_PRIVILEGE_NOT_HELD); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\ioalloc", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Set allocation to 4096", [&]() { set_allocation(h.get(), 4096); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if ((uint64_t)fsi.AllocationSize.QuadPart != 4096) { throw formatted_error("AllocationSize was {}, expected {}", fsi.EndOfFile.QuadPart, 4096); } if ((uint64_t)fsi.EndOfFile.QuadPart != 0) { throw formatted_error("EndOfFile was {}, expected {}", fsi.EndOfFile.QuadPart, 0); } }); auto random = random_data(4096); test("Write data", [&]() { write_file(h.get(), random); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if ((uint64_t)fsi.AllocationSize.QuadPart != 4096) { throw formatted_error("AllocationSize was {}, expected {}", fsi.EndOfFile.QuadPart, 4096); } if ((uint64_t)fsi.EndOfFile.QuadPart != 4096) { throw formatted_error("EndOfFile was {}, expected {}", fsi.EndOfFile.QuadPart, 4096); } }); test("Set allocation to 0", [&]() { set_allocation(h.get(), 0); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if ((uint64_t)fsi.AllocationSize.QuadPart != 0) { throw formatted_error("AllocationSize was {}, expected {}", fsi.EndOfFile.QuadPart, 0); } if ((uint64_t)fsi.EndOfFile.QuadPart != 0) { throw formatted_error("EndOfFile was {}, expected {}", fsi.EndOfFile.QuadPart, 0); } }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\iodir", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Try setting end of file", [&]() { exp_status([&]() { set_end_of_file(h.get(), 0); }, STATUS_INVALID_PARAMETER); }); test("Try setting allocation", [&]() { exp_status([&]() { set_allocation(h.get(), 0); }, STATUS_INVALID_PARAMETER); }); h.reset(); } test("Create preallocated file", [&]() { h = create_file(dir + u"\\ioprealloc", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED, 4096); }); if (h) { test("Check standard information", [&]() { auto fsi = query_information(h.get()); if ((uint64_t)fsi.AllocationSize.QuadPart != 4096) { throw formatted_error("AllocationSize was {}, expected {}", fsi.EndOfFile.QuadPart, 4096); } if ((uint64_t)fsi.EndOfFile.QuadPart != 0) { throw formatted_error("EndOfFile was {}, expected {}", fsi.EndOfFile.QuadPart, 0); } }); auto random = random_data(4096); test("Write data", [&]() { write_file(h.get(), random); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if ((uint64_t)fsi.AllocationSize.QuadPart != 4096) { throw formatted_error("AllocationSize was {}, expected {}", fsi.EndOfFile.QuadPart, 4096); } if ((uint64_t)fsi.EndOfFile.QuadPart != 4096) { throw formatted_error("EndOfFile was {}, expected {}", fsi.EndOfFile.QuadPart, 4096); } }); test("Set allocation to 0", [&]() { set_allocation(h.get(), 0); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if ((uint64_t)fsi.AllocationSize.QuadPart != 0) { throw formatted_error("AllocationSize was {}, expected {}", fsi.EndOfFile.QuadPart, 0); } if ((uint64_t)fsi.EndOfFile.QuadPart != 0) { throw formatted_error("EndOfFile was {}, expected {}", fsi.EndOfFile.QuadPart, 0); } }); h.reset(); } test("Create preallocated directory", [&]() { h = create_file(dir + u"\\iopreallocdir", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED, 4096); }); if (h) { test("Check standard information", [&]() { auto fsi = query_information(h.get()); if ((uint64_t)fsi.AllocationSize.QuadPart != 0) { throw formatted_error("AllocationSize was {}, expected {}", fsi.EndOfFile.QuadPart, 0); } if ((uint64_t)fsi.EndOfFile.QuadPart != 0) { throw formatted_error("EndOfFile was {}, expected {}", fsi.EndOfFile.QuadPart, 0); } }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\io3", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { vector data = {'a','b','c','d','e','f'}; test("Write to file", [&]() { write_file(h.get(), data); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != data.size()) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, data.size()); } }); test("Read from file specifying offset", [&]() { auto buf = read_file(h.get(), 2, 0); if (buf.size() != 2) throw formatted_error("Read {} bytes, expected {}", buf.size(), 2); if (buf[0] != data[0] || buf[1] != data[1]) throw runtime_error("Data read did not match data written"); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 2) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 2); } }); test("Read from file specifying offset as FILE_USE_FILE_POINTER_POSITION", [&]() { LARGE_INTEGER li; li.HighPart = -1; li.LowPart = FILE_USE_FILE_POINTER_POSITION; auto buf = read_file(h.get(), 2, li.QuadPart); if (buf.size() != 2) throw formatted_error("Read {} bytes, expected {}", buf.size(), 2); if (buf[0] != data[2] || buf[1] != data[3]) throw runtime_error("Data read did not match data written"); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 4) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 4); } }); vector data2 = {'g','h'}; test("Write to file specifying offset as FILE_USE_FILE_POINTER_POSITION", [&]() { LARGE_INTEGER li; li.HighPart = -1; li.LowPart = FILE_USE_FILE_POINTER_POSITION; write_file(h.get(), data2, li.QuadPart); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 6) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 6); } }); test("Set position to 0", [&]() { set_position(h.get(), 0); }); vector data3 = {'i','j'}; test("Write to file specifying offset as FILE_WRITE_TO_END_OF_FILE", [&]() { LARGE_INTEGER li; li.HighPart = -1; li.LowPart = FILE_WRITE_TO_END_OF_FILE; write_file(h.get(), data3, li.QuadPart); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 8) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 8); } }); test("Try reading from file specifying offset as FILE_WRITE_TO_END_OF_FILE", [&]() { LARGE_INTEGER li; li.HighPart = -1; li.LowPart = FILE_WRITE_TO_END_OF_FILE; exp_status([&]() { read_file(h.get(), 2, li.QuadPart); }, STATUS_INVALID_PARAMETER); }); test("Set position to 0", [&]() { set_position(h.get(), 0); }); test("Check file contents", [&]() { auto buf = read_file(h.get(), 8); if (buf.size() != 8) throw formatted_error("Read {} bytes, expected {}", buf.size(), 8); if (buf[0] != data[0] || buf[1] != data[1] || buf[2] != data[2] || buf[3] != data[3] || buf[4] != data2[0] || buf[5] != data2[1] || buf[6] != data3[0] || buf[7] != data3[1]) { throw runtime_error("Data read did not match data written"); } }); } test("Create file without file pointer", [&]() { h = create_file(dir + u"\\io4", FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { vector data = {'a','b','c','d','e','f'}; test("Try writing to file with no offset specified", [&]() { exp_status([&]() { write_file_wait(h.get(), data); }, STATUS_INVALID_PARAMETER); }); test("Write to file at 0", [&]() { write_file_wait(h.get(), data, 0); }); test("Check position hasn't moved", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 0) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 0); } }); test("Set position", [&]() { set_position(h.get(), data.size()); }); test("Check position has now moved", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != data.size()) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, data.size()); } }); test("Read from file specifying offset", [&]() { auto buf = read_file_wait(h.get(), 2, 0); if (buf.size() != 2) throw formatted_error("Read {} bytes, expected {}", buf.size(), 2); if (buf[0] != data[0] || buf[1] != data[1]) throw runtime_error("Data read did not match data written"); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != data.size()) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, data.size()); } }); test("Read from file specifying offset as FILE_USE_FILE_POINTER_POSITION", [&]() { LARGE_INTEGER li; li.HighPart = -1; li.LowPart = FILE_USE_FILE_POINTER_POSITION; exp_status([&]() { read_file_wait(h.get(), 2, li.QuadPart); }, STATUS_INVALID_PARAMETER); }); vector data2 = {'g','h'}; test("Write to file specifying offset as FILE_USE_FILE_POINTER_POSITION", [&]() { LARGE_INTEGER li; li.HighPart = -1; li.LowPart = FILE_USE_FILE_POINTER_POSITION; exp_status([&]() { write_file_wait(h.get(), data2, li.QuadPart); }, STATUS_INVALID_PARAMETER); }); test("Set position to 0", [&]() { set_position(h.get(), 0); }); vector data3 = {'i','j'}; test("Write to file specifying offset as FILE_WRITE_TO_END_OF_FILE", [&]() { LARGE_INTEGER li; li.HighPart = -1; li.LowPart = FILE_WRITE_TO_END_OF_FILE; write_file_wait(h.get(), data3, li.QuadPart); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 0) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 0); } }); test("Try reading from file specifying offset as FILE_WRITE_TO_END_OF_FILE", [&]() { LARGE_INTEGER li; li.HighPart = -1; li.LowPart = FILE_WRITE_TO_END_OF_FILE; exp_status([&]() { read_file_wait(h.get(), 2, li.QuadPart); }, STATUS_INVALID_PARAMETER); }); test("Check file contents", [&]() { auto buf = read_file_wait(h.get(), 8, 0); if (buf.size() != 8) throw formatted_error("Read {} bytes, expected {}", buf.size(), 8); if (buf[0] != data[0] || buf[1] != data[1] || buf[2] != data[2] || buf[3] != data[3] || buf[4] != data[4] || buf[5] != data[5] || buf[6] != data3[0] || buf[7] != data3[1]) { throw runtime_error("Data read did not match data written"); } }); } test("Create file for FILE_APPEND_DATA", [&]() { h = create_file(dir + u"\\io5", SYNCHRONIZE | FILE_READ_DATA | FILE_APPEND_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { vector data = {'a','b','c','d','e','f'}; test("Write to file", [&]() { write_file(h.get(), data); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != data.size()) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, data.size()); } }); test("Read from file specifying offset", [&]() { auto buf = read_file(h.get(), 2, 0); if (buf.size() != 2) throw formatted_error("Read {} bytes, expected {}", buf.size(), 2); if (buf[0] != data[0] || buf[1] != data[1]) throw runtime_error("Data read did not match data written"); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 2) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 2); } }); test("Read from file specifying offset as FILE_USE_FILE_POINTER_POSITION", [&]() { LARGE_INTEGER li; li.HighPart = -1; li.LowPart = FILE_USE_FILE_POINTER_POSITION; auto buf = read_file(h.get(), 2, li.QuadPart); if (buf.size() != 2) throw formatted_error("Read {} bytes, expected {}", buf.size(), 2); if (buf[0] != data[2] || buf[1] != data[3]) throw runtime_error("Data read did not match data written"); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 4) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 4); } }); vector data2 = {'g','h'}; test("Write to file specifying offset as FILE_USE_FILE_POINTER_POSITION", [&]() { LARGE_INTEGER li; li.HighPart = -1; li.LowPart = FILE_USE_FILE_POINTER_POSITION; write_file(h.get(), data2, li.QuadPart); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 8) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 8); } }); test("Set position to 0", [&]() { set_position(h.get(), 0); }); vector data3 = {'i','j'}; test("Write to file specifying offset as 0", [&]() { write_file(h.get(), data3, 0); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 10) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 10); } }); test("Write to file specifying offset as FILE_WRITE_TO_END_OF_FILE", [&]() { LARGE_INTEGER li; li.HighPart = -1; li.LowPart = FILE_WRITE_TO_END_OF_FILE; write_file(h.get(), data3, li.QuadPart); }); test("Check position", [&]() { auto fpi = query_information(h.get()); if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 12) { throw formatted_error("CurrentByteOffset was {}, expected {}", fpi.CurrentByteOffset.QuadPart, 12); } }); test("Try reading from file specifying offset as FILE_WRITE_TO_END_OF_FILE", [&]() { LARGE_INTEGER li; li.HighPart = -1; li.LowPart = FILE_WRITE_TO_END_OF_FILE; exp_status([&]() { read_file(h.get(), 2, li.QuadPart); }, STATUS_INVALID_PARAMETER); }); test("Set position to 0", [&]() { set_position(h.get(), 0); }); test("Check file contents", [&]() { auto buf = read_file(h.get(), 12); if (buf.size() != 12) throw formatted_error("Read {} bytes, expected {}", buf.size(), 12); if (buf[0] != data[0] || buf[1] != data[1] || buf[2] != data[2] || buf[3] != data[3] || buf[4] != data[4] || buf[5] != data[5] || buf[6] != data2[0] || buf[7] != data2[1] || buf[8] != data3[0] || buf[9] != data3[1] || buf[10] != data3[0] || buf[11] != data3[1]) { throw runtime_error("Data read did not match data written"); } }); } test("Add SeManageVolumePrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file with FILE_NO_INTERMEDIATE_BUFFERING", [&]() { h = create_file(dir + u"\\io6", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_NO_INTERMEDIATE_BUFFERING, FILE_CREATED); }); if (h) { write_check(4096, true); auto random = random_data(100); test("Try writing less than sector", [&]() { exp_status([&]() { write_file(h.get(), random); }, STATUS_INVALID_PARAMETER); }); test("Try setting position to odd value", [&]() { exp_status([&]() { set_position(h.get(), 100); }, STATUS_INVALID_PARAMETER); }); test("Set position to 0", [&]() { set_position(h.get(), 0); }); test("Try reading less than sector", [&]() { exp_status([&]() { read_file(h.get(), 100); }, STATUS_INVALID_PARAMETER); }); test("Set length to odd value", [&]() { set_end_of_file(h.get(), 100); }); test("Set length to sector", [&]() { set_end_of_file(h.get(), 4096); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if ((uint64_t)fsi.EndOfFile.QuadPart != 4096) { throw formatted_error("EndOfFile was {}, expected {}", fsi.EndOfFile.QuadPart, 4096); } }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\io7", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { auto random = random_data(150); test("Write file", [&]() { write_file(h.get(), random); }); test("Set zero data", [&]() { set_zero_data(h.get(), 25, 125); }); test("Read file", [&]() { auto ret = read_file(h.get(), random.size(), 0); auto exp = random; memset(exp.data() + 25, 0, 100); if (memcmp(ret.data(), exp.data(), exp.size())) throw runtime_error("Data read did not match data written"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\io8", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { auto random = random_data(4096 * 3); test("Write file", [&]() { write_file(h.get(), random); }); test("Set zero data", [&]() { set_zero_data(h.get(), 4096, 4096 * 2); }); test("Read file", [&]() { auto ret = read_file(h.get(), random.size(), 0); auto exp = random; memset(exp.data() + 4096, 0, 4096); if (memcmp(ret.data(), exp.data(), exp.size())) throw runtime_error("Data read did not match data written"); }); h.reset(); } // FIXME - DASD I/O } ================================================ FILE: src/tests/links.cpp ================================================ #include "test.h" using namespace std; vector> query_links(HANDLE h) { IO_STATUS_BLOCK iosb; NTSTATUS Status; FILE_LINKS_INFORMATION fli; vector> ret; fli.BytesNeeded = 0; Status = NtQueryInformationFile(h, &iosb, &fli, sizeof(fli), FileHardLinkInformation); if (Status != STATUS_SUCCESS && Status != STATUS_BUFFER_OVERFLOW) throw ntstatus_error(Status); if (fli.BytesNeeded == 0) throw runtime_error("fli.BytesNeeded was 0"); vector buf(fli.BytesNeeded); auto& fli2 = *reinterpret_cast(buf.data()); Status = NtQueryInformationFile(h, &iosb, &fli2, buf.size(), FileHardLinkInformation); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != buf.size()) throw formatted_error("iosb.Information was {}, expected {}", iosb.Information, buf.size()); ret.resize(fli2.EntriesReturned); auto flei = &fli2.Entry; for (unsigned int i = 0; i < fli2.EntriesReturned; i++) { auto& p = ret[i]; p.first = flei->ParentFileId; p.second.resize(flei->FileNameLength); memcpy(p.second.data(), flei->FileName, flei->FileNameLength * sizeof(char16_t)); if (flei->NextEntryOffset == 0) break; flei = (FILE_LINK_ENTRY_INFORMATION*)((uint8_t*)flei + flei->NextEntryOffset); } return ret; } void set_link_information(HANDLE h, bool replace_if_exists, HANDLE root_dir, u16string_view filename) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf(offsetof(FILE_LINK_INFORMATION, FileName) + (filename.length() * sizeof(char16_t))); auto& fli = *(FILE_LINK_INFORMATION*)buf.data(); fli.ReplaceIfExists = replace_if_exists; fli.RootDirectory = root_dir; fli.FileNameLength = filename.length() * sizeof(char16_t); memcpy(fli.FileName, filename.data(), fli.FileNameLength); Status = NtSetInformationFile(h, &iosb, &fli, buf.size(), FileLinkInformation); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != 0) throw formatted_error("iosb.Information was {}, expected 0", iosb.Information); } static void set_link_information_ex(HANDLE h, ULONG flags, HANDLE root_dir, u16string_view filename) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf(offsetof(FILE_LINK_INFORMATION_EX, FileName) + (filename.length() * sizeof(char16_t))); auto& fli = *(FILE_LINK_INFORMATION_EX*)buf.data(); fli.Flags = flags; fli.RootDirectory = root_dir; fli.FileNameLength = filename.length() * sizeof(char16_t); memcpy(fli.FileName, filename.data(), fli.FileNameLength); Status = NtSetInformationFile(h, &iosb, &fli, buf.size(), FileLinkInformationEx); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != 0) throw formatted_error("iosb.Information was {}, expected 0", iosb.Information); } void test_links(HANDLE token, const std::u16string& dir) { unique_handle h, h2; // traverse privilege needed to query hard links test("Add SeChangeNotifyPrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file", [&]() { h = create_file(dir + u"\\link1a", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\link1a"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\link1a\"."); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsi.NumberOfLinks); }); test("Check standard link information", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Check links", [&]() { auto items = query_links(h.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& item = items.front(); if (item.second != u"link1a") throw formatted_error("Link was called {}, expected link1a", u16string_to_string(item.second)); }); test("Create link", [&]() { set_link_information(h.get(), false, nullptr, dir + u"\\link1b"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\link1a"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\link1a\"."); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.NumberOfLinks != 2) throw formatted_error("NumberOfLinks was {}, expected 2", fsi.NumberOfLinks); }); test("Check standard link information", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 2) throw formatted_error("NumberOfAccessibleLinks was {}, expected 2", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 2) throw formatted_error("TotalNumberOfLinks was {}, expected 2", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Check links", [&]() { auto items = query_links(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2.", items.size()); auto& item1 = items[0]; auto& item2 = items[1]; if (item1.first != item2.first) throw runtime_error("Links were in different directories"); if (!(item1.second == u"link1a" && item2.second == u"link1b") && !(item1.second == u"link1b" && item2.second == u"link1a")) throw runtime_error("Link names were not what was expected"); }); test("Open second link", [&]() { h2 = create_file(dir + u"\\link1b", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h2) { int64_t file_id = 0; test("Check index numbers are the same", [&]() { auto fii1 = query_information(h.get()); auto fii2 = query_information(h2.get()); if (fii1.IndexNumber.QuadPart != fii2.IndexNumber.QuadPart) throw runtime_error("Index numbers did not match"); file_id = fii1.IndexNumber.QuadPart; }); test("Check name on second link", [&]() { auto fn = query_file_name_information(h2.get()); static const u16string_view ends_with = u"\\link1b"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\link1b\"."); }); test("Check directory entry of link 1", [&]() { u16string_view name = u"link1a"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); if (fdi.FileId.QuadPart != file_id) throw runtime_error("FileId did not match index number."); }); test("Check directory entry of link 2", [&]() { u16string_view name = u"link1b"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); if (fdi.FileId.QuadPart != file_id) throw runtime_error("FileId did not match index number."); }); test("Delete first link", [&]() { set_disposition_information(h.get(), true); }); test("Check standard information of first link", [&]() { auto fsi = query_information(h.get()); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsi.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsi.NumberOfLinks); }); test("Check standard link information of first link", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 2) throw formatted_error("TotalNumberOfLinks was {}, expected 2", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Check standard information of second link", [&]() { auto fsi = query_information(h2.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsi.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsi.NumberOfLinks); }); test("Check standard link information of second link", [&]() { auto fsli = query_information(h2.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 2) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); h2.reset(); } h.reset(); test("Check directory entry of link 1 gone", [&]() { u16string_view name = u"link1a"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); test("Check directory entry of link 2", [&]() { u16string_view name = u"link1b"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); } test("Create directory", [&]() { h = create_file(dir + u"\\link2dir", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Create link", [&]() { exp_status([&]() { set_link_information(h.get(), false, nullptr, dir + u"\\link2dira"); }, STATUS_FILE_IS_A_DIRECTORY); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\link3a", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create second file", [&]() { create_file(dir + u"\\link3b", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Try overwrite by link without ReplaceIfExists set", [&]() { exp_status([&]() { set_link_information(h.get(), false, nullptr, dir + u"\\link3b"); }, STATUS_OBJECT_NAME_COLLISION); }); } test("Create file with FILE_DELETE_ON_CLOSE", [&]() { h = create_file(dir + u"\\link4a", DELETE, 0, 0, FILE_CREATE, FILE_DELETE_ON_CLOSE, FILE_CREATED); }); if (h) { test("Create link", [&]() { set_link_information(h.get(), false, nullptr, dir + u"\\link4b"); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsi.NumberOfLinks != 2) throw formatted_error("NumberOfLinks was {}, expected 2", fsi.NumberOfLinks); }); test("Check standard link information", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 2) throw formatted_error("NumberOfAccessibleLinks was {}, expected 2", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 2) throw formatted_error("TotalNumberOfLinks was {}, expected 2", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); h.reset(); test("Check directory entry of created link after close", [&]() { u16string_view name = u"link4b"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); } test("Create file", [&]() { h = create_file(dir + u"\\link5file", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create directory without FILE_SHARE_WRITE", [&]() { h2 = create_file(dir + u"\\link5dir", FILE_READ_DATA, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h && h2) { test("Try create link through directory handle", [&]() { exp_status([&]() { set_link_information(h.get(), false, h2.get(), u"file"); }, STATUS_SHARING_VIOLATION); }); h.reset(); h2.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\link6file", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create directory", [&]() { h2 = create_file(dir + u"\\link6dir", FILE_READ_DATA, 0, FILE_SHARE_WRITE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h && h2) { test("Create link", [&]() { set_link_information(h.get(), false, h2.get(), u"file"); }); h2.reset(); test("Check links", [&]() { auto items = query_links(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2.", items.size()); auto& item1 = items[0]; auto& item2 = items[1]; if (item1.first == item2.first) throw runtime_error("Links were in same directory"); if (!(item1.second == u"link6file" && item2.second == u"file") && !(item1.second == u"file" && item2.second == u"link6file")) throw runtime_error("Link names were not what was expected"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\link6a", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create second file", [&]() { create_file(dir + u"\\link6b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Overwrite second file by link", [&]() { set_link_information(h.get(), true, nullptr, dir + u"\\link6b"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\link7a", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create directory", [&]() { create_file(dir + u"\\link7b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Try overwriting directory by link", [&]() { exp_status([&]() { set_link_information(h.get(), true, nullptr, dir + u"\\link7b"); }, STATUS_ACCESS_DENIED); }); } test("Create file", [&]() { h = create_file(dir + u"\\link8a", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create second file", [&]() { h2 = create_file(dir + u"\\link8b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h2) { test("Try to overwrite open file by link", [&]() { exp_status([&]() { set_link_information(h.get(), true, nullptr, dir + u"\\link8b"); }, STATUS_ACCESS_DENIED); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\link9", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try creating link with same name without ReplaceIfExists", [&]() { exp_status([&]() { set_link_information(h.get(), false, nullptr, dir + u"\\link9"); }, STATUS_OBJECT_NAME_COLLISION); }); test("Create link with same name with ReplaceIfExists", [&]() { set_link_information(h.get(), true, nullptr, dir + u"\\link9"); // nop }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsi.NumberOfLinks); }); test("Check standard link information", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\link9"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\link9\"."); }); test("Create link with same name but different case", [&]() { set_link_information(h.get(), true, nullptr, dir + u"\\LINK9"); }); test("Check standard information", [&]() { auto fsi = query_information(h.get()); if (fsi.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsi.NumberOfLinks); }); test("Check standard link information", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\LINK9"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\LINK9\"."); }); } test("Create file", [&]() { h = create_file(dir + u"\\link10", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { struct { u16string name; string desc; } invalid_names[] = { { u"/", "slash" }, { u":", "colon" }, { u"<", "less than" }, { u">", "greater than" }, { u"\"", "quote" }, { u"|", "pipe" }, { u"?", "question mark" }, { u"*", "asterisk" } }; for (const auto& n : invalid_names) { test("Try creating link to invalid name (" + n.desc + ")", [&]() { auto fn = dir + u"\\link10" + n.name; exp_status([&]() { set_link_information(h.get(), false, nullptr, fn); }, STATUS_OBJECT_NAME_INVALID); }); } bool is_ntfs = fstype == fs_type::ntfs; test("Create link with more than 255 UTF-8 characters", [&]() { auto fn = dir + u"\\link10"; for (unsigned int i = 0; i < 64; i++) { fn += u"\U0001f525"; } exp_status([&]() { set_link_information(h.get(), false, nullptr, fn); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); test("Create link with WTF-16 (1)", [&]() { auto fn = dir + u"\\link10"; fn += (char16_t)0xd83d; exp_status([&]() { set_link_information(h.get(), false, nullptr, fn); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); test("Create link with WTF-16 (2)", [&]() { auto fn = dir + u"\\link10"; fn += (char16_t)0xdd25; exp_status([&]() { set_link_information(h.get(), false, nullptr, fn); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); test("Create link with WTF-16 (3)", [&]() { auto fn = dir + u"\\link10"; fn += (char16_t)0xdd25; fn += (char16_t)0xd83d; exp_status([&]() { set_link_information(h.get(), false, nullptr, fn); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\link11", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try linking into non-existent directory", [&]() { exp_status([&]() { set_link_information(h.get(), false, nullptr, dir + u"\\linknonsuch\\file"); }, STATUS_OBJECT_PATH_NOT_FOUND); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\link12a", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create readonly file", [&]() { create_file(dir + u"\\link12b", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Try overwriting readonly file by linking", [&]() { exp_status([&]() { set_link_information(h.get(), true, nullptr, dir + u"\\link12b"); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\link13", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create directory 1", [&]() { h2 = create_file(dir + u"\\link13dir1", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Set directory 1 ACL to SYNCHRONIZE | FILE_ADD_FILE", [&]() { set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_FILE); }); h2.reset(); test("Create link", [&]() { set_link_information(h.get(), false, nullptr, dir + u"\\link13dir1\\file"); }); test("Create directory 2", [&]() { h2 = create_file(dir + u"\\link13dir2", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Clear directory 2 ACL", [&]() { set_dacl(h2.get(), 0); }); h2.reset(); test("Try to create link", [&]() { exp_status([&]() { set_link_information(h.get(), false, nullptr, dir + u"\\link13dir2\\file"); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\link14", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle h3; test("Create directory", [&]() { h2 = create_file(dir + u"\\link14dir", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create file in directory", [&]() { h3 = create_file(dir + u"\\link14dir\\file", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Clear directory ACL", [&]() { set_dacl(h2.get(), 0); }); h2.reset(); test("Set file ACL to DELETE", [&]() { set_dacl(h3.get(), DELETE); }); h3.reset(); test("Try to create link", [&]() { exp_status([&]() { set_link_information(h.get(), true, nullptr, dir + u"\\link14dir\\file"); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\link15", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle h3; test("Create directory", [&]() { h2 = create_file(dir + u"\\link15dir", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create file in directory", [&]() { h3 = create_file(dir + u"\\link15dir\\file", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Set directory ACL to SYNCHRONIZE | FILE_ADD_FILE", [&]() { set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_FILE); }); h2.reset(); test("Clear file ACL", [&]() { set_dacl(h3.get(), 0); }); h3.reset(); test("Try to create link", [&]() { exp_status([&]() { set_link_information(h.get(), true, nullptr, dir + u"\\link15dir\\file"); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\link16", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle h3; test("Create directory", [&]() { h2 = create_file(dir + u"\\link16dir", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create file in directory", [&]() { h3 = create_file(dir + u"\\link16dir\\file", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Set directory ACL to SYNCHRONIZE | FILE_ADD_FILE", [&]() { set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_FILE); }); h2.reset(); test("Set file ACL to DELETE", [&]() { set_dacl(h3.get(), DELETE); }); h3.reset(); test("Create link", [&]() { set_link_information(h.get(), true, nullptr, dir + u"\\link16dir\\file"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\link17", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle h3; test("Create directory", [&]() { h2 = create_file(dir + u"\\link17dir", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create file in directory", [&]() { h3 = create_file(dir + u"\\link17dir\\file", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Set directory ACL to SYNCHRONIZE | FILE_ADD_FILE | FILE_DELETE_CHILD", [&]() { set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_FILE | FILE_DELETE_CHILD); }); h2.reset(); test("Clear file ACL", [&]() { set_dacl(h3.get(), 0); }); h3.reset(); test("Create link", [&]() { set_link_information(h.get(), true, nullptr, dir + u"\\link17dir\\file"); }); h.reset(); } test("Create file 1", [&]() { create_file(dir + u"\\link18a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create file 2", [&]() { h = create_file(dir + u"\\link18b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try to link file within other file", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\link18a\\file"); }, STATUS_INVALID_PARAMETER); }); h.reset(); } disable_token_privileges(token); } void test_links_ex(HANDLE token, const u16string& dir) { unique_handle h, h2; test("Create file", [&]() { h = create_file(dir + u"\\linkex1a", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create second file", [&]() { create_file(dir + u"\\linkex1b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Try overwrite by link without FILE_LINK_REPLACE_IF_EXISTS", [&]() { exp_status([&]() { set_link_information_ex(h.get(), 0, nullptr, dir + u"\\linkex1b"); }, STATUS_OBJECT_NAME_COLLISION); }); test("Overwrite by link with FILE_LINK_REPLACE_IF_EXISTS", [&]() { set_link_information_ex(h.get(), FILE_LINK_REPLACE_IF_EXISTS, nullptr, dir + u"\\linkex1b"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\linkex2a", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create readonly file", [&]() { create_file(dir + u"\\linkex2b", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Try overwrite by link without FILE_LINK_IGNORE_READONLY_ATTRIBUTE", [&]() { exp_status([&]() { set_link_information_ex(h.get(), FILE_LINK_REPLACE_IF_EXISTS, nullptr, dir + u"\\linkex2b"); }, STATUS_ACCESS_DENIED); }); test("Overwrite by link with FILE_LINK_IGNORE_READONLY_ATTRIBUTE", [&]() { set_link_information_ex(h.get(), FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_IGNORE_READONLY_ATTRIBUTE, nullptr, dir + u"\\linkex2b"); }); h.reset(); } // FIXME - FILE_LINK_POSIX_SEMANTICS // traverse privilege needed to query hard links test("Add SeChangeNotifyPrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file 1", [&]() { h = create_file(dir + u"\\linkex3a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create file 2", [&]() { h2 = create_file(dir + u"\\linkex3b", SYNCHRONIZE | DELETE | FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h && h2) { test("Overwrite file 2 using FILE_LINK_POSIX_SEMANTICS", [&]() { set_link_information_ex(h.get(), FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_POSIX_SEMANTICS, nullptr, dir + u"\\linkex3b"); }); test("Check name of file 1", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\linkex3a"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\linkex3a\"."); }); test("Check name of file 2", [&]() { auto fn = query_file_name_information(h2.get()); static const u16string_view ends_with = u"\\linkex3b"; // NTFS moves this to \$Extend\$Deleted directory if (fn.size() >= ends_with.size() && fn.substr(fn.size() - ends_with.size()) == ends_with) throw runtime_error("Name ended with \"\\linkex3b\"."); }); test("Check standard information of file 1", [&]() { auto fsi = query_information(h.get()); if (fsi.NumberOfLinks != 2) throw formatted_error("NumberOfLinks was {}, expected 2", fsi.NumberOfLinks); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information of file 1", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 2) throw formatted_error("NumberOfAccessibleLinks was {}, expected 2", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 2) throw formatted_error("TotalNumberOfLinks was {}, expected 2", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Check standard information of file 2", [&]() { auto fsi = query_information(h2.get()); if (fsi.NumberOfLinks != 0) throw formatted_error("NumberOfLinks was {}, expected 0", fsi.NumberOfLinks); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information of file 2", [&]() { auto fsli = query_information(h2.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 0", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Check new directory entry", [&]() { u16string_view name = u"linkex3b"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check old directory entry", [&]() { u16string_view name = u"linkex3a"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Try to clear delete bit on file 2", [&]() { exp_status([&]() { set_disposition_information(h2.get(), false); }, STATUS_FILE_DELETED); }); test("Write to file 2", [&]() { static const vector data = {'h','e','l','l','o'}; write_file(h2.get(), data); }); test("Read from file 2", [&]() { static const vector exp = {'h','e','l','l','o'}; auto buf = read_file(h2.get(), exp.size(), 0); if (buf.size() != exp.size()) throw formatted_error("Read {} bytes, expected {}", buf.size(), exp.size()); if (buf != exp) throw runtime_error("Data read did not match data written"); }); int64_t dir_id; test("Check file 1 hardlinks", [&]() { auto items = query_links(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2.", items.size()); auto& item1 = items[0]; auto& item2 = items[1]; if (item1.first != item2.first) throw runtime_error("Links were in different directories"); if (!(item1.second == u"linkex3a" && item2.second == u"linkex3b") && !(item1.second == u"linkex3b" && item2.second == u"linkex3a")) throw runtime_error("Link names were not what was expected"); dir_id = item1.first; }); test("Check file 2 hardlinks", [&]() { auto items = query_links(h2.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& item = items.front(); if (item.second == u"linkex3b") throw formatted_error("Link was called linkex3b, expected something else", u16string_to_string(item.second)); if (item.first == dir_id) throw runtime_error("Dir ID of orphaned inode is same as before"); }); h.reset(); h2.reset(); } test("Disable token privileges", [&]() { disable_token_privileges(token); }); test("Create file 1", [&]() { h = create_file(dir + u"\\linkex4a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create file 2 without FILE_SHARE_DELETE", [&]() { h2 = create_file(dir + u"\\linkex4b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h && h2) { test("Try to overwrite file 2 using FILE_LINK_POSIX_SEMANTICS", [&]() { exp_status([&]() { set_link_information_ex(h.get(), FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_POSIX_SEMANTICS, nullptr, dir + u"\\linkex4b"); }, STATUS_SHARING_VIOLATION); }); h.reset(); h2.reset(); } test("Create image file", [&]() { h = create_file(dir + u"\\linkex5a", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); test("Create file", [&]() { h2 = create_file(dir + u"\\linkex5b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); auto img = pe_image(as_bytes(span("hello"))); if (h && h2) { unique_handle sect; test("Write to file", [&]() { write_file(h.get(), img); }); test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get()); }); h.reset(); if (sect) { test("Try overwriting mapped image file by link", [&]() { exp_status([&]() { set_link_information_ex(h2.get(), FILE_LINK_REPLACE_IF_EXISTS, nullptr, dir + u"\\linkex5a"); }, STATUS_ACCESS_DENIED); }); } h2.reset(); } test("Create image file", [&]() { h = create_file(dir + u"\\linkex6a", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); test("Create file", [&]() { h2 = create_file(dir + u"\\linkex6b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h && h2) { unique_handle sect; test("Write to file", [&]() { write_file(h.get(), img); }); test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get()); }); h.reset(); if (sect) { test("Try overwriting mapped image file by POSIX link", [&]() { exp_status([&]() { set_link_information_ex(h2.get(), FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_POSIX_SEMANTICS, nullptr, dir + u"\\linkex6a"); }, STATUS_ACCESS_DENIED); }); } h2.reset(); } // FIXME - FILE_LINK_SUPPRESS_STORAGE_RESERVE_INHERITANCE // FIXME - FILE_LINK_NO_INCREASE_AVAILABLE_SPACE // FIXME - FILE_LINK_NO_DECREASE_AVAILABLE_SPACE // FIXME - FILE_LINK_FORCE_RESIZE_TARGET_SR // FIXME - FILE_LINK_FORCE_RESIZE_SOURCE_SR } ================================================ FILE: src/tests/manifest.xml ================================================ UTF-8 ================================================ FILE: src/tests/mmap.cpp ================================================ #include "test.h" using namespace std; unique_handle create_section(ACCESS_MASK access, optional max_size, ULONG prot, ULONG atts, HANDLE file) { NTSTATUS Status; HANDLE h; LARGE_INTEGER li; if (max_size) li.QuadPart = max_size.value(); Status = NtCreateSection(&h, access, nullptr, max_size ? &li : nullptr, prot, atts, file); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); return unique_handle(h); } static void* map_view(HANDLE sect, uint64_t off, uint64_t len, ULONG prot) { NTSTATUS Status; void* addr = nullptr; LARGE_INTEGER li; SIZE_T size = len; li.QuadPart = off; Status = NtMapViewOfSection(sect, NtCurrentProcess(), &addr, 0, 0, &li, &size, ViewUnmap, 0, prot); if (Status != STATUS_SUCCESS && Status != STATUS_IMAGE_NOT_AT_BASE) throw ntstatus_error(Status); if (!addr) throw runtime_error("NtMapViewOfSection returned address of 0."); return addr; } static void unmap_view(void* addr) { NTSTATUS Status; Status = NtUnmapViewOfSection(NtCurrentProcess(), addr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static void lock_file(HANDLE h, uint64_t offset, uint64_t length, bool exclusive) { NTSTATUS Status; IO_STATUS_BLOCK iosb; LARGE_INTEGER offli, lenli; offli.QuadPart = offset; lenli.QuadPart = length; Status = NtLockFile(h, nullptr, nullptr, nullptr, &iosb, &offli, &lenli, 0, false, exclusive); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static constexpr unsigned int align(unsigned int x, unsigned int a) { return (x + a - 1) & ~(a - 1); } vector pe_image(span data) { 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"; static const unsigned int SECTION_ALIGNMENT = 0x1000; static const unsigned int FILE_ALIGNMENT = 0x200; static constexpr unsigned int header_size = align(sizeof(IMAGE_DOS_HEADER) + sizeof(stub) - 1 + sizeof(IMAGE_NT_HEADERS32) + sizeof(IMAGE_SECTION_HEADER), FILE_ALIGNMENT); vector buf(header_size + align(data.size(), FILE_ALIGNMENT)); memset(buf.data(), 0, buf.size()); auto& h = *(IMAGE_DOS_HEADER*)buf.data(); h.e_magic = IMAGE_DOS_SIGNATURE; h.e_cblp = 0x90; h.e_cp = 0x3; h.e_cparhdr = 0x4; h.e_maxalloc = 0xffff; h.e_sp = 0xb8; h.e_lfarlc = 0x40; h.e_lfanew = sizeof(h) + sizeof(stub) - 1; memcpy(buf.data() + sizeof(h), stub, sizeof(stub) - 1); auto& nth = *(IMAGE_NT_HEADERS32*)(buf.data() + h.e_lfanew); nth.Signature = IMAGE_NT_SIGNATURE; nth.FileHeader.Machine = IMAGE_FILE_MACHINE_I386; nth.FileHeader.NumberOfSections = 1; nth.FileHeader.SizeOfOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER32); nth.FileHeader.Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE; nth.OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR32_MAGIC; nth.OptionalHeader.MajorLinkerVersion = 0x2; nth.OptionalHeader.MinorLinkerVersion = 0x23; nth.OptionalHeader.SizeOfCode = 0; nth.OptionalHeader.SizeOfInitializedData = align(data.size(), SECTION_ALIGNMENT); nth.OptionalHeader.SizeOfUninitializedData = 0; nth.OptionalHeader.AddressOfEntryPoint = 0; nth.OptionalHeader.BaseOfCode = 0x1000; nth.OptionalHeader.BaseOfData = 0x1000; nth.OptionalHeader.ImageBase = 0x10000000; nth.OptionalHeader.SectionAlignment = SECTION_ALIGNMENT; nth.OptionalHeader.FileAlignment = FILE_ALIGNMENT; nth.OptionalHeader.MajorOperatingSystemVersion = 4; nth.OptionalHeader.MinorOperatingSystemVersion = 0; nth.OptionalHeader.MajorImageVersion = 0; nth.OptionalHeader.MinorImageVersion = 0; nth.OptionalHeader.MajorSubsystemVersion = 5; nth.OptionalHeader.MinorSubsystemVersion = 2; nth.OptionalHeader.Win32VersionValue = 0; nth.OptionalHeader.SizeOfImage = align(header_size, SECTION_ALIGNMENT) + align(data.size(), SECTION_ALIGNMENT); nth.OptionalHeader.SizeOfHeaders = header_size; nth.OptionalHeader.Subsystem = IMAGE_SUBSYSTEM_WINDOWS_GUI; nth.OptionalHeader.DllCharacteristics = 0; nth.OptionalHeader.SizeOfStackReserve = 0x100000; nth.OptionalHeader.SizeOfStackCommit = 0x1000; nth.OptionalHeader.SizeOfHeapReserve = 0x100000; nth.OptionalHeader.SizeOfHeapCommit = 0x1000; nth.OptionalHeader.LoaderFlags = 0; nth.OptionalHeader.NumberOfRvaAndSizes = IMAGE_NUMBEROF_DIRECTORY_ENTRIES; auto& sect = *(IMAGE_SECTION_HEADER*)(&nth + 1); memcpy(sect.Name, ".data\0\0\0", 8); sect.Misc.VirtualSize = align(data.size(), SECTION_ALIGNMENT); sect.VirtualAddress = align(header_size, SECTION_ALIGNMENT); sect.SizeOfRawData = align(data.size(), FILE_ALIGNMENT); sect.PointerToRawData = header_size; sect.PointerToRelocations = 0; sect.PointerToLinenumbers = 0; sect.NumberOfRelocations = 0; sect.NumberOfLinenumbers = 0; sect.Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_INITIALIZED_DATA; memcpy(buf.data() + header_size, data.data(), data.size()); return buf; } void test_mmap(const u16string& dir) { unique_handle h, h2; test("Create empty file", [&]() { h = create_file(dir + u"\\mmapempty", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try to create section on empty file", [&]() { exp_status([&]() { auto sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READONLY, SEC_COMMIT, h.get()); }, STATUS_MAPPED_FILE_SIZE_ZERO); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\mmapdir", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Try to create section on directory", [&]() { exp_status([&]() { auto sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READONLY, SEC_COMMIT, h.get()); }, STATUS_INVALID_FILE_FOR_SECTION); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\mmap1", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { auto data = random_data(4096); test("Write to file", [&]() { write_file(h.get(), data); }); unique_handle sect; test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READONLY, SEC_COMMIT, h.get()); }); void* addr = nullptr; test("Map view", [&]() { addr = map_view(sect.get(), 0, data.size(), PAGE_READONLY); }); if (addr) { test("Check data in mapping", [&]() { if (memcmp(addr, data.data(), data.size())) throw runtime_error("Data in mapping did not match was written."); }); uint32_t num = 0xdeadbeef; test("Write to file", [&]() { write_file(h.get(), span((uint8_t*)&num, sizeof(uint32_t)), 0); }); test("Check data in mapping again", [&]() { if (*(uint32_t*)addr != num) throw runtime_error("Data in mapping did not match was written."); }); test("Unmap view", [&]() { unmap_view(addr); }); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\mmap2", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); test("Try to create section larger than file", [&]() { exp_status([&]() { create_section(SECTION_ALL_ACCESS, 8192, PAGE_READONLY, SEC_COMMIT, h.get()); }, STATUS_SECTION_TOO_BIG); }); } test("Create file", [&]() { h = create_file(dir + u"\\mmap3", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); h.reset(); test("Reopen file without FILE_WRITE_DATA", [&]() { h = create_file(dir + u"\\mmap3", SYNCHRONIZE | FILE_READ_DATA, 0, 0, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPENED); }); test("Try to create RW section on RO file", [&]() { exp_status([&]() { create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get()); }, STATUS_ACCESS_DENIED); }); } test("Create file", [&]() { h = create_file(dir + u"\\mmap4", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { auto data = random_data(4096); test("Write to file", [&]() { write_file(h.get(), data); }); unique_handle sect; test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_COMMIT, h.get()); }); void* addr = nullptr; test("Map view", [&]() { addr = map_view(sect.get(), 0, data.size(), PAGE_READWRITE); }); if (addr) { test("Check data in mapping", [&]() { if (memcmp(addr, data.data(), data.size())) throw runtime_error("Data in mapping did not match was written."); }); *(uint32_t*)addr = 0xdeadbeef; test("Read from file", [&]() { auto buf = read_file(h.get(), sizeof(uint32_t), 0); if (buf.size() != sizeof(uint32_t)) throw formatted_error("Read {} bytes, expected {}.", buf.size(), sizeof(uint32_t)); auto& num = *(uint32_t*)buf.data(); if (num != 0xdeadbeef) throw runtime_error("Data read did not match was written to mapping."); }); test("Unmap view", [&]() { unmap_view(addr); }); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\mmap5", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); test("Lock file", [&]() { lock_file(h.get(), 0, 4096, true); }); unique_handle sect; test("Create section on locked file", [&]() { sect = create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get()); }); void* addr = nullptr; test("Map view", [&]() { addr = map_view(sect.get(), 0, 4096, PAGE_READWRITE); }); if (addr) { test("Unmap view", [&]() { unmap_view(addr); }); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\mmap6", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); unique_handle sect; test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get()); }); test("Extend file", [&]() { set_end_of_file(h.get(), 8192); }); test("Try clearing file", [&]() { exp_status([&]() { set_end_of_file(h.get(), 0); }, STATUS_USER_MAPPED_FILE); }); test("Try setting file to original size", [&]() { exp_status([&]() { set_end_of_file(h.get(), 4096); }, STATUS_USER_MAPPED_FILE); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\mmap7", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); unique_handle sect; test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get()); }); test("Try deleting file", [&]() { exp_status([&]() { set_disposition_information(h.get(), true); }, STATUS_CANNOT_DELETE); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\mmap8", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); unique_handle sect; void* addr = nullptr; test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get()); }); if (sect) { test("Map view", [&]() { addr = map_view(sect.get(), 0, 4096, PAGE_READWRITE); }); } h.reset(); test("Create file 2", [&]() { h = create_file(dir + u"\\mmap8a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Overwrite mapped file", [&]() { set_rename_information(h.get(), true, nullptr, dir + u"\\mmap8"); }); } if (addr) { test("Unmap view", [&]() { unmap_view(addr); }); } } test("Create file", [&]() { h = create_file(dir + u"\\mmap9", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); test("Mark file for deletion", [&]() { set_disposition_information(h.get(), true); }); unique_handle sect; test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get()); }); if (sect) { void* addr = nullptr; test("Map view", [&]() { addr = map_view(sect.get(), 0, 4096, PAGE_READWRITE); }); if (addr) { test("Unmap view", [&]() { unmap_view(addr); }); } } h.reset(); } auto imgdata = as_bytes(span("hello")); auto img = pe_image(imgdata); test("Create image file", [&]() { h = create_file(dir + u"\\mmap10", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { unique_handle sect; test("Write to file", [&]() { write_file(h.get(), img); }); test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get()); }); if (sect) { void* pe = nullptr; test("Map view", [&]() { pe = map_view(sect.get(), 0, 0, PAGE_READWRITE); if (!pe) throw runtime_error("Address returned was NULL."); }); if (pe) { test("Check mapped data", [&]() { if (memcmp((uint8_t*)pe + 0x1000, imgdata.data(), imgdata.size())) throw runtime_error("Data mapped did not match data written."); }); test("Unmap view", [&]() { unmap_view(pe); }); } } h.reset(); } test("Create image file", [&]() { h = create_file(dir + u"\\mmap11", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { unique_handle sect; test("Write to file", [&]() { write_file(h.get(), img); }); test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get()); }); if (sect) { void* pe = nullptr; test("Map view", [&]() { pe = map_view(sect.get(), 0, 0, PAGE_READWRITE); if (!pe) throw runtime_error("Address returned was NULL."); }); if (pe) { test("Try to truncate file", [&]() { exp_status([&]() { set_end_of_file(h.get(), 0); }, STATUS_USER_MAPPED_FILE); }); test("Extend file", [&]() { set_end_of_file(h.get(), 8192); }); test("Try to truncate file again", [&]() { exp_status([&]() { set_end_of_file(h.get(), 4096); }, STATUS_USER_MAPPED_FILE); }); test("Unmap view", [&]() { unmap_view(pe); }); } } h.reset(); } test("Create image file", [&]() { h = create_file(dir + u"\\mmap12", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { unique_handle sect; test("Write to file", [&]() { write_file(h.get(), img); }); test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get()); }); h.reset(); if (sect) { test("Try overwriting mapped image file", [&]() { exp_status([&]() { create_file(dir + u"\\mmap12", MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }, STATUS_SHARING_VIOLATION); }); } } test("Create image file", [&]() { h = create_file(dir + u"\\mmap13a", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); test("Create file", [&]() { h2 = create_file(dir + u"\\mmap13b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle sect; test("Write to file", [&]() { write_file(h.get(), img); }); test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get()); }); h.reset(); if (sect) { test("Try overwriting mapped image file by rename", [&]() { exp_status([&]() { set_rename_information(h2.get(), true, nullptr, dir + u"\\mmap13a"); }, STATUS_ACCESS_DENIED); }); } h2.reset(); } test("Create image file", [&]() { h = create_file(dir + u"\\mmap14", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { unique_handle sect; test("Write to file", [&]() { write_file(h.get(), img); }); test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get()); }); if (sect) { test("Try deleting mapped image file", [&]() { exp_status([&]() { set_disposition_information(h.get(), true); }, STATUS_CANNOT_DELETE); }); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\mmap15", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); unique_handle sect; void* addr = nullptr; test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get()); }); if (sect) { test("Map view", [&]() { addr = map_view(sect.get(), 0, 4096, PAGE_READWRITE); }); } h.reset(); test("Create file 2", [&]() { h = create_file(dir + u"\\mmap15a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Overwrite mapped file by linking", [&]() { set_link_information(h.get(), true, nullptr, dir + u"\\mmap15"); }); } if (addr) { test("Unmap view", [&]() { unmap_view(addr); }); } } test("Create image file", [&]() { h = create_file(dir + u"\\mmap16a", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); test("Create file", [&]() { h2 = create_file(dir + u"\\mmap16b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle sect; test("Write to file", [&]() { write_file(h.get(), img); }); test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get()); }); h.reset(); if (sect) { test("Try overwriting mapped image file by linking", [&]() { exp_status([&]() { set_link_information(h2.get(), true, nullptr, dir + u"\\mmap16a"); }, STATUS_ACCESS_DENIED); }); } h2.reset(); } } ================================================ FILE: src/tests/oplock.cpp ================================================ #include "test.h" #include using namespace std; #define FSCTL_REQUEST_OPLOCK_LEVEL_1 CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_REQUEST_OPLOCK_LEVEL_2 CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 1, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_REQUEST_BATCH_OPLOCK CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 2, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_OPLOCK_BREAK_ACKNOWLEDGE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 3, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_REQUEST_FILTER_OPLOCK CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 23, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_REQUEST_OPLOCK CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 144, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FILE_OPLOCK_BROKEN_TO_LEVEL_2 0x00000007 #define FILE_OPLOCK_BROKEN_TO_NONE 0x00000008 #define OPLOCK_LEVEL_CACHE_READ 0x00000001 #define OPLOCK_LEVEL_CACHE_HANDLE 0x00000002 #define OPLOCK_LEVEL_CACHE_WRITE 0x00000004 #define REQUEST_OPLOCK_INPUT_FLAG_REQUEST 0x00000001 #define REQUEST_OPLOCK_INPUT_FLAG_ACK 0x00000002 #define REQUEST_OPLOCK_INPUT_FLAG_COMPLETE_ACK_ON_CLOSE 0x00000004 #define REQUEST_OPLOCK_CURRENT_VERSION 1 enum oplock_type { level1, level2, batch, filter, read_oplock, read_handle, read_write, read_write_handle }; static unique_handle req_oplock(HANDLE h, IO_STATUS_BLOCK& iosb, enum oplock_type type) { NTSTATUS Status; HANDLE ev; iosb.Information = 0; Status = NtCreateEvent(&ev, MAXIMUM_ALLOWED, nullptr, NotificationEvent, false); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); switch (type) { case oplock_type::level1: Status = NtFsControlFile(h, ev, nullptr, nullptr, &iosb, FSCTL_REQUEST_OPLOCK_LEVEL_1, nullptr, 0, nullptr, 0); break; case oplock_type::level2: Status = NtFsControlFile(h, ev, nullptr, nullptr, &iosb, FSCTL_REQUEST_OPLOCK_LEVEL_2, nullptr, 0, nullptr, 0); break; case oplock_type::batch: Status = NtFsControlFile(h, ev, nullptr, nullptr, &iosb, FSCTL_REQUEST_BATCH_OPLOCK, nullptr, 0, nullptr, 0); break; case oplock_type::filter: Status = NtFsControlFile(h, ev, nullptr, nullptr, &iosb, FSCTL_REQUEST_FILTER_OPLOCK, nullptr, 0, nullptr, 0); break; default: throw runtime_error("Invalid oplock type for function."); } if (Status != STATUS_PENDING) throw ntstatus_error(Status); return unique_handle(ev); } static unique_handle req_oplock_win7(HANDLE h, IO_STATUS_BLOCK& iosb, enum oplock_type type, REQUEST_OPLOCK_OUTPUT_BUFFER& roob) { REQUEST_OPLOCK_INPUT_BUFFER roib; NTSTATUS Status; HANDLE ev; iosb.Information = 0; Status = NtCreateEvent(&ev, MAXIMUM_ALLOWED, nullptr, NotificationEvent, false); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); roib.StructureVersion = REQUEST_OPLOCK_CURRENT_VERSION; roib.StructureLength = sizeof(roib); roib.Flags = REQUEST_OPLOCK_INPUT_FLAG_REQUEST; switch (type) { case oplock_type::read_oplock: roib.RequestedOplockLevel = OPLOCK_LEVEL_CACHE_READ; break; case oplock_type::read_handle: roib.RequestedOplockLevel = OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE; break; case oplock_type::read_write: roib.RequestedOplockLevel = OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE; break; case oplock_type::read_write_handle: roib.RequestedOplockLevel = OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE; break; default: throw runtime_error("Invalid oplock type for function."); } roob.StructureVersion = REQUEST_OPLOCK_CURRENT_VERSION; roob.StructureLength = sizeof(roob); Status = NtFsControlFile(h, ev, nullptr, nullptr, &iosb, FSCTL_REQUEST_OPLOCK, &roib, sizeof(roib), &roob, sizeof(roob)); if (Status != STATUS_PENDING) throw ntstatus_error(Status); return unique_handle(ev); } static bool check_event(HANDLE h) { NTSTATUS Status; EVENT_BASIC_INFORMATION ebi; Status = NtQueryEvent(h, EventBasicInformation, &ebi, sizeof(ebi), nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); return ebi.EventState; } static void lock_file_wait(HANDLE h, uint64_t offset, uint64_t length, bool exclusive) { NTSTATUS Status; IO_STATUS_BLOCK iosb; LARGE_INTEGER offli, lenli; offli.QuadPart = offset; lenli.QuadPart = length; auto event = create_event(); Status = NtLockFile(h, event.get(), nullptr, nullptr, &iosb, &offli, &lenli, 0, false, exclusive); if (Status == STATUS_PENDING) { Status = NtWaitForSingleObject(event.get(), false, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); Status = iosb.Status; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static void unlock_file(HANDLE h, uint64_t offset, uint64_t length) { NTSTATUS Status; IO_STATUS_BLOCK iosb; LARGE_INTEGER offli, lenli; offli.QuadPart = offset; lenli.QuadPart = length; Status = NtUnlockFile(h, &iosb, &offli, &lenli, 0); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } void test_oplocks_ii(HANDLE token, const u16string& dir) { unique_handle h, h2; IO_STATUS_BLOCK iosb; auto data = random_data(4096); // needed to set valid data length test("Add SeManageVolumePrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file", [&]() { h = create_file(dir + u"\\oplockii1", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\oplockii1", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h) { unique_handle ev; test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OPEN)", [&]() { h2 = create_file(dir + u"\\oplockii1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock still not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h2.reset(); test("Try to open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)", [&]() { exp_status([&]() { create_file(dir + u"\\oplockii1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED); }, STATUS_CANNOT_BREAK_OPLOCK); }); test("Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)", [&]() { create_file(dir + u"\\oplockii1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_SUPERSEDE)", [&]() { h2 = create_file(dir + u"\\oplockii1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OVERWRITE)", [&]() { h2 = create_file(dir + u"\\oplockii1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); test("Try to open second handle with FILE_RESERVE_OPFILTER", [&]() { exp_status([&]() { create_file(dir + u"\\oplockii1", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OVERWRITE_IF)", [&]() { h2 = create_file(dir + u"\\oplockii1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OPEN)", [&]() { h2 = create_file(dir + u"\\oplockii1", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Read from second handle", [&]() { read_file_wait(h2.get(), 4096, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Write to first handle", [&]() { write_file_wait(h.get(), data, 0); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Write to second handle", [&]() { write_file_wait(h2.get(), data, 0); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); test("Open second handle (FILE_OPEN)", [&]() { h2 = create_file(dir + u"\\oplockii1", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Lock file", [&]() { lock_file_wait(h2.get(), 0, 4096, false); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); test("Open second handle (FILE_OPEN)", [&]() { h2 = create_file(dir + u"\\oplockii1", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPENED); }); test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set end of file", [&]() { set_end_of_file(h2.get(), 4096); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set allocation", [&]() { set_allocation(h2.get(), 4096); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set valid data length", [&]() { set_valid_data_length(h2.get(), 4096); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Rename file", [&]() { set_rename_information(h2.get(), false, nullptr, dir + u"\\oplockii1a"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Create hardlink", [&]() { set_link_information(h2.get(), false, nullptr, dir + u"\\oplockii1b"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set zero data", [&]() { set_zero_data(h2.get(), 0, 100); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set disposition information", [&]() { set_disposition_information(h2.get(), true); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockii2", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Try to get level 2 oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb, oplock_type::level2); }, STATUS_OPLOCK_NOT_GRANTED); }); h.reset(); } test("Create file with FILE_SYNCHRONOUS_IO_NONALERT", [&]() { h = create_file(dir + u"\\oplockii3", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Try to get level 2 oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb, oplock_type::level2); }, STATUS_OPLOCK_NOT_GRANTED); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\oplockii4", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Try to get level 2 oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb, oplock_type::level2); }, STATUS_INVALID_PARAMETER); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockii5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle on file", [&]() { h2 = create_file(dir + u"\\oplockii5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); if (h2) { unique_handle ev2; IO_STATUS_BLOCK iosb2; test("Get level 2 oplock on second handle", [&]() { ev2 = req_oplock(h2.get(), iosb2, oplock_type::level2); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockii6", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle on file", [&]() { h2 = create_file(dir + u"\\oplockii6", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); if (h2) { unique_handle ev2; IO_STATUS_BLOCK iosb2; test("Get level 2 oplock on second handle", [&]() { ev2 = req_oplock(h2.get(), iosb2, oplock_type::level2); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockii7", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle on file", [&]() { h2 = create_file(dir + u"\\oplockii7", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); if (h2) { test("Try to get level 2 oplock on second handle", [&]() { IO_STATUS_BLOCK iosb2; exp_status([&]() { req_oplock(h2.get(), iosb2, oplock_type::level2); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockii8", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get level 2 oplock", [&]() { IO_STATUS_BLOCK iosb2; exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::level2); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockii9", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get level 2 oplock", [&]() { IO_STATUS_BLOCK iosb2; exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::level2); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockii10", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get level 2 oplock", [&]() { IO_STATUS_BLOCK iosb2; exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::level2); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockii11", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get level 2 oplock", [&]() { IO_STATUS_BLOCK iosb2; exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::level2); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockii12", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get level 2 oplock", [&]() { IO_STATUS_BLOCK iosb2; exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::level2); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } disable_token_privileges(token); } void test_oplocks_r(HANDLE token, const u16string& dir) { unique_handle h, h2; IO_STATUS_BLOCK iosb; REQUEST_OPLOCK_OUTPUT_BUFFER roob; auto data = random_data(4096); // needed to set valid data length test("Add SeManageVolumePrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file", [&]() { h = create_file(dir + u"\\oplockr1", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\oplockr1", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h) { unique_handle ev; test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)", [&]() { create_file(dir + u"\\oplockr1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OPEN)", [&]() { h2 = create_file(dir + u"\\oplockr1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock still not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h2.reset(); test("Try to open second handle with FILE_RESERVE_OPFILTER", [&]() { exp_status([&]() { create_file(dir + u"\\oplockr1", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)", [&]() { create_file(dir + u"\\oplockr1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED); }); test("Check oplock is broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_SUPERSEDE)", [&]() { h2 = create_file(dir + u"\\oplockr1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OVERWRITE)", [&]() { h2 = create_file(dir + u"\\oplockr1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OVERWRITE_IF)", [&]() { h2 = create_file(dir + u"\\oplockr1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OPEN)", [&]() { h2 = create_file(dir + u"\\oplockr1", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Read from second handle", [&]() { read_file_wait(h2.get(), 4096, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Write to first handle", [&]() { write_file_wait(h.get(), data, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Write to second handle", [&]() { write_file_wait(h2.get(), data, 0); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); test("Open second handle (FILE_OPEN)", [&]() { h2 = create_file(dir + u"\\oplockr1", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Lock file", [&]() { lock_file_wait(h2.get(), 0, 4096, false); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); test("Open second handle (FILE_OPEN)", [&]() { h2 = create_file(dir + u"\\oplockr1", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPENED); }); test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set end of file", [&]() { set_end_of_file(h2.get(), 4096); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set allocation", [&]() { set_allocation(h2.get(), 4096); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set valid data length", [&]() { set_valid_data_length(h2.get(), 4096); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Rename file", [&]() { set_rename_information(h2.get(), false, nullptr, dir + u"\\oplockr1a"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Create hardlink", [&]() { set_link_information(h2.get(), false, nullptr, dir + u"\\oplockr1b"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set zero data", [&]() { set_zero_data(h2.get(), 0, 100); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set disposition information", [&]() { set_disposition_information(h2.get(), true); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockr2", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Try to get read oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); h.reset(); } test("Create file with FILE_SYNCHRONOUS_IO_NONALERT", [&]() { h = create_file(dir + u"\\oplockr3", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Try to get read oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\oplockr4", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { unique_handle ev; // Succeeds on Windows 8 and above (see https://docs.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_request_oplock) test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Create file in directory", [&]() { create_file(dir + u"\\oplockr4\\file", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockr5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle on file", [&]() { h2 = create_file(dir + u"\\oplockr5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); if (h2) { unique_handle ev2; IO_STATUS_BLOCK iosb2; test("Get read oplock on second handle", [&]() { ev2 = req_oplock_win7(h2.get(), iosb2, oplock_type::read_oplock, roob); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockr6", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle on file", [&]() { h2 = create_file(dir + u"\\oplockr6", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); if (h2) { unique_handle ev2; IO_STATUS_BLOCK iosb2; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; test("Get read oplock on second handle", [&]() { ev2 = req_oplock_win7(h2.get(), iosb2, oplock_type::read_oplock, roob2); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockr7", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle on file", [&]() { h2 = create_file(dir + u"\\oplockr7", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); if (h2) { unique_handle ev2; test("Get read oplock on second handle", [&]() { IO_STATUS_BLOCK iosb2; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; ev2 = req_oplock_win7(h2.get(), iosb2, oplock_type::read_oplock, roob2); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockr8", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read oplock", [&]() { IO_STATUS_BLOCK iosb2; exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_oplock, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockr9", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read oplock", [&]() { IO_STATUS_BLOCK iosb2; exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_oplock, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockr10", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read oplock", [&]() { IO_STATUS_BLOCK iosb2; exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_oplock, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockr11", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read oplock", [&]() { IO_STATUS_BLOCK iosb2; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_oplock, roob2); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockr12", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read oplock", [&]() { IO_STATUS_BLOCK iosb2; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_oplock, roob2); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } disable_token_privileges(token); } static DWORD __stdcall wait_and_acknowledge(void* param) { IO_STATUS_BLOCK iosb; auto ctx = reinterpret_cast(param); auto ev = ctx[0]; auto h = ctx[1]; NtWaitForSingleObject(ev, false, nullptr); NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_OPLOCK_BREAK_ACKNOWLEDGE, nullptr, 0, nullptr, 0); delete[] ctx; return 0; } static void ack_oplock(HANDLE event, HANDLE h) { HANDLE thread; auto ctx = new HANDLE[2]; ctx[0] = event; ctx[1] = h; thread = CreateThread(nullptr, 0, wait_and_acknowledge, ctx, 0, nullptr); if (!thread) { delete[] ctx; throw runtime_error("Could not create thread."); } NtClose(thread); } static DWORD __stdcall wait_and_acknowledge_win7(void* param) { IO_STATUS_BLOCK iosb; REQUEST_OPLOCK_INPUT_BUFFER roib; REQUEST_OPLOCK_OUTPUT_BUFFER roob; auto ctx = reinterpret_cast(param); auto ev = ctx[0]; auto h = ctx[1]; NtWaitForSingleObject(ev, false, nullptr); roib.StructureVersion = REQUEST_OPLOCK_CURRENT_VERSION; roib.StructureLength = sizeof(roib); roib.RequestedOplockLevel = 0; roib.Flags = REQUEST_OPLOCK_INPUT_FLAG_ACK; roob.StructureVersion = REQUEST_OPLOCK_CURRENT_VERSION; roob.StructureLength = sizeof(roob); NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_REQUEST_OPLOCK, &roib, sizeof(roib), &roob, sizeof(roob)); delete[] ctx; return 0; } static void ack_oplock_win7(HANDLE event, HANDLE h) { HANDLE thread; auto ctx = new HANDLE[2]; ctx[0] = event; ctx[1] = h; thread = CreateThread(nullptr, 0, wait_and_acknowledge_win7, ctx, 0, nullptr); if (!thread) { delete[] ctx; throw runtime_error("Could not create thread."); } NtClose(thread); } void test_oplocks_i(HANDLE token, const u16string& dir) { unique_handle h, h2; IO_STATUS_BLOCK iosb; auto data = random_data(4096); // needed to set valid data length test("Add SeManageVolumePrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file", [&]() { h = create_file(dir + u"\\oplocki1", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\oplocki1", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h) { unique_handle ev; test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)", [&]() { exp_status([&]() { create_file(dir + u"\\oplocki1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED); }, STATUS_CANNOT_BREAK_OPLOCK); }); ack_oplock(ev.get(), h.get()); test("Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)", [&]() { exp_status([&]() { create_file(dir + u"\\oplocki1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED); }, STATUS_OPLOCK_BREAK_IN_PROGRESS); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_LEVEL_2) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_LEVEL_2", iosb.Information); }); } h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\oplocki1", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h) { unique_handle ev; test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); if (ev) { ack_oplock(ev.get(), h.get()); test("Open second handle (FILE_OPEN)", [&]() { h2 = create_file(dir + u"\\oplocki1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_LEVEL_2) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_LEVEL_2", iosb.Information); }); h2.reset(); } test("Try to open second handle with FILE_RESERVE_OPFILTER", [&]() { exp_status([&]() { create_file(dir + u"\\oplocki1", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock(ev.get(), h.get()); test("Open second handle (FILE_SUPERSEDE)", [&]() { h2 = create_file(dir + u"\\oplocki1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); } test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock(ev.get(), h.get()); test("Open second handle (FILE_OVERWRITE)", [&]() { h2 = create_file(dir + u"\\oplocki1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); } test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock(ev.get(), h.get()); test("Open second handle (FILE_OVERWRITE_IF)", [&]() { h2 = create_file(dir + u"\\oplocki1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); } test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Read from file", [&]() { read_file_wait(h.get(), 4096, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Unlock file", [&]() { unlock_file(h.get(), 0, 4096); }); test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set allocation", [&]() { set_allocation(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set valid data length", [&]() { set_valid_data_length(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Rename file", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\oplocki1a"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Create hardlink", [&]() { set_link_information(h.get(), false, nullptr, dir + u"\\oplocki1b"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set zero data", [&]() { set_zero_data(h.get(), 0, 100); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set disposition information", [&]() { set_disposition_information(h.get(), true); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplocki2", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file with FILE_SYNCHRONOUS_IO_NONALERT", [&]() { h = create_file(dir + u"\\oplocki3", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Try to get level 1 oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb, oplock_type::level1); }, STATUS_OPLOCK_NOT_GRANTED); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\oplocki4", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Try to get level 1 oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb, oplock_type::level1); }, STATUS_INVALID_PARAMETER); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplocki5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Open second handle on file", [&]() { h2 = create_file(dir + u"\\oplocki5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h2) { test("Try to get level 1 oplock with two handles open", [&]() { exp_status([&]() { req_oplock(h2.get(), iosb, oplock_type::level1); }, STATUS_OPLOCK_NOT_GRANTED); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplocki6", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev, ev2; IO_STATUS_BLOCK iosb2; test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Get level 1 oplock", [&]() { ev2 = req_oplock(h.get(), iosb2, oplock_type::level1); }); test("Check first oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplocki7", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; IO_STATUS_BLOCK iosb2; test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get level 1 oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::level1); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplocki8", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; IO_STATUS_BLOCK iosb2; test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get level 1 oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::level1); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplocki9", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; IO_STATUS_BLOCK iosb2; test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get level 1 oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::level1); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplocki10", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; IO_STATUS_BLOCK iosb2; test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get level 1 oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::level1); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplocki11", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get level 1 oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::level1); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplocki12", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get level 1 oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::level1); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplocki13", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get level 1 oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::level1); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } disable_token_privileges(token); } void test_oplocks_rw(HANDLE token, const u16string& dir) { unique_handle h, h2; IO_STATUS_BLOCK iosb; REQUEST_OPLOCK_OUTPUT_BUFFER roob; auto data = random_data(4096); // needed to set valid data length test("Add SeManageVolumePrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file", [&]() { h = create_file(dir + u"\\oplockrw1", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\oplockrw1", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h) { unique_handle ev; test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock_win7(ev.get(), h.get()); test("Try to open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)", [&]() { exp_status([&]() { create_file(dir + u"\\oplockrw1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED); }, STATUS_CANNOT_BREAK_OPLOCK); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OPEN)", [&]() { h2 = create_file(dir + u"\\oplockrw1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != OPLOCK_LEVEL_CACHE_READ) throw formatted_error("NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ", roob.NewOplockLevel); }); h2.reset(); } test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); if (ev) { ack_oplock_win7(ev.get(), h.get()); test("Try to open second handle with FILE_RESERVE_OPFILTER", [&]() { exp_status([&]() { create_file(dir + u"\\oplockrw1", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); } test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); if (ev) { ack_oplock_win7(ev.get(), h.get()); test("Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)", [&]() { exp_status([&]() { create_file(dir + u"\\oplockrw1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED); }, STATUS_OPLOCK_BREAK_IN_PROGRESS); }); test("Check oplock is broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != OPLOCK_LEVEL_CACHE_READ) throw formatted_error("NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ", roob.NewOplockLevel); }); } test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock_win7(ev.get(), h.get()); test("Open second handle (FILE_SUPERSEDE)", [&]() { h2 = create_file(dir + u"\\oplockrw1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); } test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock_win7(ev.get(), h.get()); test("Open second handle (FILE_OVERWRITE)", [&]() { h2 = create_file(dir + u"\\oplockrw1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); } test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock_win7(ev.get(), h.get()); test("Open second handle (FILE_OVERWRITE_IF)", [&]() { h2 = create_file(dir + u"\\oplockrw1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); } test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Read from file", [&]() { read_file_wait(h.get(), 4096, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Unlock file", [&]() { unlock_file(h.get(), 0, 4096); }); test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set allocation", [&]() { set_allocation(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set valid data length", [&]() { set_valid_data_length(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Rename file", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\oplockrw1a"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Create hardlink", [&]() { set_link_information(h.get(), false, nullptr, dir + u"\\oplockrw1b"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set zero data", [&]() { set_zero_data(h.get(), 0, 100); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set disposition information", [&]() { set_disposition_information(h.get(), true); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrw2", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file with FILE_SYNCHRONOUS_IO_NONALERT", [&]() { h = create_file(dir + u"\\oplockrw3", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Try to get read-write oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\oplockrw4", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Try to get read-write oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }, STATUS_INVALID_PARAMETER); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrw5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Open second handle on file", [&]() { h2 = create_file(dir + u"\\oplockrw5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h2) { test("Try to get read-write oplock with two handles open", [&]() { exp_status([&]() { req_oplock_win7(h2.get(), iosb, oplock_type::read_write, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrw6", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-write oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrw7", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev, ev2; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; IO_STATUS_BLOCK iosb2; test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Get read-write oplock", [&]() { ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob2); }); test("Check first oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE)) throw formatted_error("NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE", roob.NewOplockLevel); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrw8", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; IO_STATUS_BLOCK iosb2; test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-write oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob2); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrw9", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev, ev2; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; IO_STATUS_BLOCK iosb2; test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Get read-write oplock", [&]() { ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob2); }); test("Check first oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE)) throw formatted_error("NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE", roob.NewOplockLevel); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrw10", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; IO_STATUS_BLOCK iosb2; test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-write oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob2); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrw11", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-write oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrw12", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-write oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrw13", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-write oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } disable_token_privileges(token); } void test_oplocks_batch(HANDLE token, const u16string& dir) { unique_handle h, h2; IO_STATUS_BLOCK iosb; auto data = random_data(4096); // needed to set valid data length test("Add SeManageVolumePrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file", [&]() { h = create_file(dir + u"\\oplockb1", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\oplockb1", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h) { unique_handle ev; test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock(ev.get(), h.get()); test("Try to open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)", [&]() { exp_status([&]() { create_file(dir + u"\\oplockb1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED); }, STATUS_CANNOT_BREAK_OPLOCK); }); test("Open second handle (FILE_OPEN)", [&]() { h2 = create_file(dir + u"\\oplockb1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_LEVEL_2) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_LEVEL_2", iosb.Information); }); h2.reset(); } test("Try to open second handle with FILE_RESERVE_OPFILTER", [&]() { exp_status([&]() { create_file(dir + u"\\oplockb1", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)", [&]() { exp_status([&]() { create_file(dir + u"\\oplockb1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED); }, STATUS_OPLOCK_BREAK_IN_PROGRESS); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_LEVEL_2) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_LEVEL_2", iosb.Information); }); } h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\oplockb1", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h) { unique_handle ev; test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); if (ev) { ack_oplock(ev.get(), h.get()); test("Open second handle (FILE_SUPERSEDE)", [&]() { h2 = create_file(dir + u"\\oplockb1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); } test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock(ev.get(), h.get()); test("Open second handle (FILE_OVERWRITE)", [&]() { h2 = create_file(dir + u"\\oplockb1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); } test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock(ev.get(), h.get()); test("Open second handle (FILE_OVERWRITE_IF)", [&]() { h2 = create_file(dir + u"\\oplockb1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); } test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Read from file", [&]() { read_file_wait(h.get(), 4096, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Unlock file", [&]() { unlock_file(h.get(), 0, 4096); }); test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set allocation", [&]() { set_allocation(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set valid data length", [&]() { set_valid_data_length(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Rename file", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\oplockb1a"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Create hardlink", [&]() { set_link_information(h.get(), false, nullptr, dir + u"\\oplockb1b"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set zero data", [&]() { set_zero_data(h.get(), 0, 100); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set disposition information", [&]() { set_disposition_information(h.get(), true); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockb2", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file with FILE_SYNCHRONOUS_IO_NONALERT", [&]() { h = create_file(dir + u"\\oplockb3", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Try to get batch oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb, oplock_type::batch); }, STATUS_OPLOCK_NOT_GRANTED); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\oplockb4", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Try to get batch oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb, oplock_type::batch); }, STATUS_INVALID_PARAMETER); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockb5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Open second handle on file", [&]() { h2 = create_file(dir + u"\\oplockb5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h2) { test("Try to get batch oplock with two handles open", [&]() { exp_status([&]() { req_oplock(h2.get(), iosb, oplock_type::batch); }, STATUS_OPLOCK_NOT_GRANTED); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockb6", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev, ev2; IO_STATUS_BLOCK iosb2; test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Get batch oplock", [&]() { ev2 = req_oplock(h.get(), iosb2, oplock_type::batch); }); test("Check first oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockb7", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; IO_STATUS_BLOCK iosb2; test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get batch oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::batch); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockb8", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; IO_STATUS_BLOCK iosb2; test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get batch oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::batch); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockb9", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; IO_STATUS_BLOCK iosb2; test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get batch oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::batch); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockb10", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; IO_STATUS_BLOCK iosb2; test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get batch oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::batch); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockb11", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get batch oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::batch); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockb12", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get batch oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::batch); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockb13", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get batch oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::batch); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } disable_token_privileges(token); } void test_oplocks_rwh(HANDLE token, const u16string& dir) { unique_handle h, h2; IO_STATUS_BLOCK iosb; REQUEST_OPLOCK_OUTPUT_BUFFER roob; auto data = random_data(4096); // needed to set valid data length test("Add SeManageVolumePrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file", [&]() { h = create_file(dir + u"\\oplockrwh1", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\oplockrwh1", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h) { unique_handle ev; test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock_win7(ev.get(), h.get()); test("Open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)", [&]() { exp_status([&]() { create_file(dir + u"\\oplockrwh1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED); }, STATUS_CANNOT_BREAK_OPLOCK); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OPEN)", [&]() { h2 = create_file(dir + u"\\oplockrwh1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE)) throw formatted_error("NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE", roob.NewOplockLevel); }); h2.reset(); } test("Try to open second handle with FILE_RESERVE_OPFILTER", [&]() { exp_status([&]() { create_file(dir + u"\\oplockrwh1", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); if (ev) { ack_oplock_win7(ev.get(), h.get()); test("Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)", [&]() { exp_status([&]() { create_file(dir + u"\\oplockrwh1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED); }, STATUS_OPLOCK_BREAK_IN_PROGRESS); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE)) throw formatted_error("NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE", roob.NewOplockLevel); }); } test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock_win7(ev.get(), h.get()); test("Open second handle (FILE_SUPERSEDE)", [&]() { h2 = create_file(dir + u"\\oplockrwh1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); } test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock_win7(ev.get(), h.get()); test("Open second handle (FILE_OVERWRITE)", [&]() { h2 = create_file(dir + u"\\oplockrwh1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); } test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock_win7(ev.get(), h.get()); test("Open second handle (FILE_OVERWRITE_IF)", [&]() { h2 = create_file(dir + u"\\oplockrwh1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); } test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Read from file", [&]() { read_file_wait(h.get(), 4096, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Unlock file", [&]() { unlock_file(h.get(), 0, 4096); }); test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set allocation", [&]() { set_allocation(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set valid data length", [&]() { set_valid_data_length(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Rename file", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\oplockrwh1a"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Create hardlink", [&]() { set_link_information(h.get(), false, nullptr, dir + u"\\oplockrwh1b"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set zero data", [&]() { set_zero_data(h.get(), 0, 100); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set disposition information", [&]() { set_disposition_information(h.get(), true); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrwh2", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file with FILE_SYNCHRONOUS_IO_NONALERT", [&]() { h = create_file(dir + u"\\oplockrwh3", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Try to get read-write-handle oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\oplockrwh4", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Try to get read-write-handle oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }, STATUS_INVALID_PARAMETER); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrwh5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Open second handle on file", [&]() { h2 = create_file(dir + u"\\oplockrwh5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h2) { test("Try to get read-write-handle oplock with two handles open", [&]() { exp_status([&]() { req_oplock_win7(h2.get(), iosb, oplock_type::read_write_handle, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrwh6", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-write-handle oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrwh7", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev, ev2; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; IO_STATUS_BLOCK iosb2; test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Get read-write-handle oplock", [&]() { ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob2); }); test("Check first oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE)) throw formatted_error("NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE", roob.NewOplockLevel); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrwh8", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev, ev2; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; IO_STATUS_BLOCK iosb2; test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Get read-write-handle oplock", [&]() { ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob2); }); test("Check first oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE)) throw formatted_error("NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE", roob.NewOplockLevel); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrwh9", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev, ev2; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; IO_STATUS_BLOCK iosb2; test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Get read-write-handle oplock", [&]() { ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob2); }); test("Check first oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE)) throw formatted_error("NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE", roob.NewOplockLevel); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrwh10", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev, ev2; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; IO_STATUS_BLOCK iosb2; test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Get read-write-handle oplock", [&]() { ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob2); }); test("Check first oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE)) throw formatted_error("NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE", roob.NewOplockLevel); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrwh11", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-write-handle oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrwh12", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-write-handle oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrwh13", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get batch oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } disable_token_privileges(token); } void test_oplocks_filter(HANDLE token, const u16string& dir) { unique_handle h, h2; IO_STATUS_BLOCK iosb; auto data = random_data(4096); // needed to set valid data length test("Add SeManageVolumePrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file", [&]() { h = create_file(dir + u"\\oplockf1", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\oplockf1", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h) { unique_handle ev; test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock(ev.get(), h.get()); test("Try to open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)", [&]() { exp_status([&]() { create_file(dir + u"\\oplockf1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED); }, STATUS_CANNOT_BREAK_OPLOCK); }); test("Open second handle (FILE_OPEN, FILE_READ_DATA)", [&]() { h2 = create_file(dir + u"\\oplockf1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OPEN, FILE_WRITE_DATA)", [&]() { h2 = create_file(dir + u"\\oplockf1", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); } test("Try to open second handle with FILE_RESERVE_OPFILTER", [&]() { exp_status([&]() { create_file(dir + u"\\oplockf1", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)", [&]() { create_file(dir + u"\\oplockf1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED); }); ack_oplock(ev.get(), h.get()); test("Open second handle (FILE_SUPERSEDE)", [&]() { h2 = create_file(dir + u"\\oplockf1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); } test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock(ev.get(), h.get()); test("Open second handle (FILE_OVERWRITE)", [&]() { h2 = create_file(dir + u"\\oplockf1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); } test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock(ev.get(), h.get()); test("Open second handle (FILE_OVERWRITE_IF)", [&]() { h2 = create_file(dir + u"\\oplockf1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); h2.reset(); } test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Read from file", [&]() { read_file_wait(h.get(), 4096, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Unlock file", [&]() { unlock_file(h.get(), 0, 4096); }); test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set allocation", [&]() { set_allocation(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set valid data length", [&]() { set_valid_data_length(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Rename file", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\oplockf1a"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Create hardlink", [&]() { set_link_information(h.get(), false, nullptr, dir + u"\\oplockf1b"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set zero data", [&]() { set_zero_data(h.get(), 0, 100); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set disposition information", [&]() { set_disposition_information(h.get(), true); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockf2", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file with FILE_SYNCHRONOUS_IO_NONALERT", [&]() { h = create_file(dir + u"\\oplockf3", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Try to get filter oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb, oplock_type::filter); }, STATUS_OPLOCK_NOT_GRANTED); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\oplockf4", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Try to get filter oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb, oplock_type::filter); }, STATUS_INVALID_PARAMETER); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockf5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Open second handle on file", [&]() { h2 = create_file(dir + u"\\oplockf5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h2) { test("Try to get filter oplock with two handles open", [&]() { exp_status([&]() { req_oplock(h2.get(), iosb, oplock_type::batch); }, STATUS_OPLOCK_NOT_GRANTED); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockf6", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev, ev2; IO_STATUS_BLOCK iosb2; test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Get filter oplock", [&]() { ev2 = req_oplock(h.get(), iosb2, oplock_type::filter); }); test("Check first oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE) throw formatted_error("iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE", iosb.Information); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockf7", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; IO_STATUS_BLOCK iosb2; test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get filter oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::filter); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockf8", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; IO_STATUS_BLOCK iosb2; test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get filter oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::filter); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockf9", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; IO_STATUS_BLOCK iosb2; test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get filter oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::filter); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockf10", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob; IO_STATUS_BLOCK iosb2; test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get filter oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::filter); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockf11", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get filter oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::filter); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockf12", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get filter oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::filter); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockf13", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get filter oplock", [&]() { exp_status([&]() { req_oplock(h.get(), iosb2, oplock_type::filter); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } disable_token_privileges(token); } void test_oplocks_rh(HANDLE token, const u16string& dir) { unique_handle h, h2; IO_STATUS_BLOCK iosb; REQUEST_OPLOCK_OUTPUT_BUFFER roob; auto data = random_data(4096); // needed to set valid data length test("Add SeManageVolumePrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file", [&]() { h = create_file(dir + u"\\oplockrh1", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\oplockrh1", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h) { unique_handle ev; test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock_win7(ev.get(), h.get()); test("Open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)", [&]() { create_file(dir + u"\\oplockrh1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Open second handle (FILE_OPEN)", [&]() { h2 = create_file(dir + u"\\oplockrh1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is not broken"); }); h2.reset(); } test("Try to open second handle with FILE_RESERVE_OPFILTER", [&]() { exp_status([&]() { create_file(dir + u"\\oplockrh1", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); if (ev) { ack_oplock_win7(ev.get(), h.get()); test("Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)", [&]() { create_file(dir + u"\\oplockrh1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED); }); test("Check oplock is not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); } test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock_win7(ev.get(), h.get()); test("Open second handle (FILE_SUPERSEDE)", [&]() { h2 = create_file(dir + u"\\oplockrh1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); } test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock_win7(ev.get(), h.get()); test("Open second handle (FILE_OVERWRITE)", [&]() { h2 = create_file(dir + u"\\oplockrh1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); } test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); ack_oplock_win7(ev.get(), h.get()); test("Open second handle (FILE_OVERWRITE_IF)", [&]() { h2 = create_file(dir + u"\\oplockrh1", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); h2.reset(); } test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Read from file", [&]() { read_file_wait(h.get(), 4096, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Unlock file", [&]() { unlock_file(h.get(), 0, 4096); }); test("Set end of file", [&]() { set_end_of_file(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set allocation", [&]() { set_allocation(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set valid data length", [&]() { set_valid_data_length(h.get(), 4096); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Rename file", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\oplockrh1a"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Create hardlink", [&]() { set_link_information(h.get(), false, nullptr, dir + u"\\oplockrh1b"); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set zero data", [&]() { set_zero_data(h.get(), 0, 100); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Set disposition information", [&]() { set_disposition_information(h.get(), true); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrh2", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; test("Write to file", [&]() { write_file_wait(h.get(), data, 0); }); test("Lock file", [&]() { lock_file_wait(h.get(), 0, 4096, false); }); test("Try to get read-handle oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); h.reset(); } test("Create file with FILE_SYNCHRONOUS_IO_NONALERT", [&]() { h = create_file(dir + u"\\oplockrh3", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { test("Try to get read-handle oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\oplockrh4", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { unique_handle ev; // Succeeds on Windows 8 and above (see https://docs.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_request_oplock) test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); if (ev) { test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Create file in directory", [&]() { create_file(dir + u"\\oplockrh4\\file", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Check oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != 0) throw formatted_error("NewOplockLevel was {}, expected 0", roob.NewOplockLevel); }); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrh5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Open second handle on file", [&]() { h2 = create_file(dir + u"\\oplockrh5", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED); }); if (h2) { unique_handle ev; test("Get read-handle oplock with two handles open", [&]() { ev = req_oplock_win7(h2.get(), iosb, oplock_type::read_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h2.reset(); } h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrh6", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get level 2 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level2); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-handle oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrh7", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev, ev2; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; IO_STATUS_BLOCK iosb2; test("Get read oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Get read-handle oplock", [&]() { ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob2); }); test("Check first oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE)) throw formatted_error("NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE", roob.NewOplockLevel); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrh8", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev, ev2; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; IO_STATUS_BLOCK iosb2; test("Get read-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Get read-handle oplock", [&]() { ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob2); }); test("Check first oplock broken", [&]() { if (!check_event(ev.get())) throw runtime_error("Oplock is not broken"); if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE)) throw formatted_error("NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE", roob.NewOplockLevel); }); test("Check second oplock not broken", [&]() { if (check_event(ev2.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrh9", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; IO_STATUS_BLOCK iosb2; test("Get read-write oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-handle oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob2); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrh10", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; REQUEST_OPLOCK_OUTPUT_BUFFER roob2; IO_STATUS_BLOCK iosb2; test("Get read-write-handle oplock", [&]() { ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-handle oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob2); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrh11", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get level 1 oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::level1); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-handle oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrh12", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get filter oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::filter); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get read-handle oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\oplockrh13", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { unique_handle ev; IO_STATUS_BLOCK iosb2; test("Get batch oplock", [&]() { ev = req_oplock(h.get(), iosb, oplock_type::batch); }); test("Check oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); test("Try to get batch oplock", [&]() { exp_status([&]() { req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob); }, STATUS_OPLOCK_NOT_GRANTED); }); test("Check first oplock not broken", [&]() { if (check_event(ev.get())) throw runtime_error("Oplock is broken"); }); h.reset(); } // FIXME - FSCTL_OPLOCK_BREAK_ACK_NO_2 // FIXME - FSCTL_OPBATCH_ACK_CLOSE_PENDING // FIXME - FSCTL_OPLOCK_BREAK_NOTIFY disable_token_privileges(token); } ================================================ FILE: src/tests/overwrite.cpp ================================================ #include "test.h" using namespace std; void test_overwrite(const u16string& dir) { unique_handle h; test("Try overwriting non-existent file", [&]() { exp_status([&]() { create_file(dir + u"\\nonsuch", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }, STATUS_OBJECT_NAME_NOT_FOUND); }); test("Create readonly file", [&]() { h = create_file(dir + u"\\overwritero", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { h.reset(); test("Try overwriting readonly file", [&]() { exp_status([&]() { create_file(dir + u"\\overwritero", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }, STATUS_ACCESS_DENIED); }); } test("Create file", [&]() { h = create_file(dir + u"\\overwrite", MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try overwriting open file", [&]() { create_file(dir + u"\\overwrite", MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); h.reset(); test("Overwrite file", [&]() { h = create_file(dir + u"\\overwrite", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); } if (h) { h.reset(); test("Overwrite file adding readonly flag", [&]() { create_file(dir + u"\\overwrite", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); } test("Create file", [&]() { h = create_file(dir + u"\\overwrite2", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { h.reset(); test("Try overwriting file, changing to directory", [&]() { exp_status([&]() { create_file(dir + u"\\overwrite2", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE, FILE_DIRECTORY_FILE, FILE_OVERWRITTEN); }, STATUS_INVALID_PARAMETER); }); test("Overwrite file adding hidden flag", [&]() { h = create_file(dir + u"\\overwrite2", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN, 0, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); } if (h) { h.reset(); test("Try overwriting file clearing hidden flag", [&]() { exp_status([&]() { create_file(dir + u"\\overwrite2", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }, STATUS_ACCESS_DENIED); }); } test("Overwrite file adding system flag", [&]() { h = create_file(dir + u"\\overwrite2", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM, 0, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); if (h) { h.reset(); test("Try overwriting file clearing system flag", [&]() { exp_status([&]() { create_file(dir + u"\\overwrite2", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN, 0, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }, STATUS_ACCESS_DENIED); }); } test("Create directory", [&]() { h = create_file(dir + u"\\overwritedir", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { h.reset(); test("Try overwriting directory", [&]() { exp_status([&]() { create_file(dir + u"\\overwritedir", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE, FILE_DIRECTORY_FILE, FILE_OVERWRITTEN); }, STATUS_INVALID_PARAMETER); }); test("Try overwriting directory, changing to file", [&]() { exp_status([&]() { create_file(dir + u"\\overwritedir", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE, FILE_NON_DIRECTORY_FILE, FILE_OVERWRITTEN); }, STATUS_FILE_IS_A_DIRECTORY); }); } test("Create file", [&]() { h = create_file(dir + u"\\overwrite3", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { h.reset(); test("Overwrite file with different case", [&]() { h = create_file(dir + u"\\OVERWRITE3", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE, 0, FILE_OVERWRITTEN); }); if (h) { test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\overwrite3"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\overwrite3\"."); }); } } test("Create file with FILE_OPEN_IF", [&]() { h = create_file(dir + u"\\overwriteif", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE_IF, 0, FILE_CREATED); }); if (h) { h.reset(); test("Open file with FILE_OVERWRITE_IF", [&]() { create_file(dir + u"\\overwriteif", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN); }); } } ================================================ FILE: src/tests/rename.cpp ================================================ #include "test.h" using namespace std; void set_rename_information(HANDLE h, bool replace_if_exists, HANDLE root_dir, u16string_view filename) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf(offsetof(FILE_RENAME_INFORMATION, FileName) + (filename.length() * sizeof(char16_t))); auto& fri = *(FILE_RENAME_INFORMATION*)buf.data(); fri.ReplaceIfExists = replace_if_exists; fri.RootDirectory = root_dir; fri.FileNameLength = filename.length() * sizeof(char16_t); memcpy(fri.FileName, filename.data(), fri.FileNameLength); Status = NtSetInformationFile(h, &iosb, &fri, buf.size(), FileRenameInformation); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != 0) throw formatted_error("iosb.Information was {}, expected 0", iosb.Information); } static void set_rename_information_ex(HANDLE h, ULONG flags, HANDLE root_dir, u16string_view filename) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf(offsetof(FILE_RENAME_INFORMATION_EX, FileName) + (filename.length() * sizeof(char16_t))); auto& fri = *(FILE_RENAME_INFORMATION_EX*)buf.data(); fri.Flags = flags; fri.RootDirectory = root_dir; fri.FileNameLength = filename.length() * sizeof(char16_t); memcpy(fri.FileName, filename.data(), fri.FileNameLength); Status = NtSetInformationFile(h, &iosb, &fri, buf.size(), FileRenameInformationEx); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != 0) throw formatted_error("iosb.Information was {}, expected 0", iosb.Information); } void test_rename(const u16string& dir) { unique_handle h, h2; test("Create file", [&]() { h = create_file(dir + u"\\renamefile1", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamefile1"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamefile1\"."); }); test("Check directory entry", [&]() { u16string_view name = u"renamefile1"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Rename file", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamefile1b"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamefile1b"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamefile1b\"."); }); test("Check directory entry", [&]() { u16string_view name = u"renamefile1b"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check old directory entry not there", [&]() { u16string_view name = u"renamefile1"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\renamedir1", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamedir1"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamefile1\"."); }); test("Check directory entry", [&]() { u16string_view name = u"renamedir1"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Rename file", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamedir1b"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamedir1b"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamedir1b\"."); }); test("Check directory entry", [&]() { u16string_view name = u"renamedir1b"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check old directory entry not there", [&]() { u16string_view name = u"renamedir1"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\renamefile2", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamefile2"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamefile2\"."); }); test("Check directory entry", [&]() { u16string_view name = u"renamefile2"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Rename file to same name", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamefile2"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamefile2"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamefile2\"."); }); test("Check directory entry", [&]() { u16string_view name = u"renamefile2"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\renamefile3", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamefile3"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamefile3\"."); }); test("Check directory entry", [&]() { u16string_view name = u"renamefile3"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Rename file to different case", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\RENAMEFILE3"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\RENAMEFILE3"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\RENAMEFILE3\"."); }); test("Check directory entry", [&]() { u16string_view name = u"RENAMEFILE3"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); h.reset(); } test("Create file 1", [&]() { create_file(dir + u"\\renamefile4a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create file 2", [&]() { h = create_file(dir + u"\\renamefile4b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try renaming file 2 to file 1 without ReplaceIfExists set", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamefile4a"); }, STATUS_OBJECT_NAME_COLLISION); }); test("Rename file 2 to file 1", [&]() { set_rename_information(h.get(), true, nullptr, dir + u"\\renamefile4a"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamefile4a"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamefile4a\"."); }); test("Check directory entry", [&]() { u16string_view name = u"renamefile4a"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); h.reset(); } test("Create file 1", [&]() { h2 = create_file(dir + u"\\renamefile5a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create file 2", [&]() { h = create_file(dir + u"\\renamefile5b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try renaming file 2 to file 1 without ReplaceIfExists set", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamefile5a"); }, STATUS_OBJECT_NAME_COLLISION); }); test("Try renaming file 2 to file 1 with file 1 open", [&]() { exp_status([&]() { set_rename_information(h.get(), true, nullptr, dir + u"\\renamefile5a"); }, STATUS_ACCESS_DENIED); }); h.reset(); } h2.reset(); test("Create file 1", [&]() { create_file(dir + u"\\renamefile6a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create file 2", [&]() { h = create_file(dir + u"\\renamefile6b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try renaming file 2 to file 1 uppercase without ReplaceIfExists set", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\RENAMEFILE6A"); }, STATUS_OBJECT_NAME_COLLISION); }); test("Rename file 2 to file 1 uppercase", [&]() { set_rename_information(h.get(), true, nullptr, dir + u"\\RENAMEFILE6A"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\RENAMEFILE6A"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\RENAMEFILE6A\"."); }); test("Check directory entry", [&]() { u16string_view name = u"RENAMEFILE6A"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); h.reset(); } test("Create directory", [&]() { create_file(dir + u"\\renamedir7", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create file", [&]() { h = create_file(dir + u"\\renamefile7", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamefile7"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamefile7\"."); }); test("Check directory entry", [&]() { u16string_view name = u"renamefile7"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Move file to subdir", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamedir7\\renamefile7a"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamedir7\\renamefile7a"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamedir7\\renamefile7a\"."); }); test("Check old directory entry gone", [&]() { u16string_view name = u"renamefile7"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); test("Check new directory entry", [&]() { u16string_view name = u"renamefile7a"; auto items = query_dir(dir + u"\\renamedir7", name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Try overwriting directory with file without ReplaceIfExists set", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamedir7"); }, STATUS_OBJECT_NAME_COLLISION); }); test("Try overwriting directory with file with ReplaceIfExists set", [&]() { exp_status([&]() { set_rename_information(h.get(), true, nullptr, dir + u"\\renamedir7"); }, STATUS_ACCESS_DENIED); }); } test("Create directory 1", [&]() { create_file(dir + u"\\renamedir8", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create directory 2", [&]() { h = create_file(dir + u"\\renamedir8a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create file", [&]() { create_file(dir + u"\\renamefile8", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Check directory entry", [&]() { u16string_view name = u"renamedir8a"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamedir8a"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamedir8a\"."); }); test("Move directory to subdir", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamedir8\\renamedir8b"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamedir8\\renamedir8b"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamedir8\\renamedir8b\"."); }); test("Check old directory entry gone", [&]() { u16string_view name = u"renamedir8a"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); test("Check new directory entry", [&]() { u16string_view name = u"renamedir8b"; auto items = query_dir(dir + u"\\renamedir8", name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Try overwriting file with directory without ReplaceIfExists set", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamefile8"); }, STATUS_OBJECT_NAME_COLLISION); }); test("Try overwriting file with directory with ReplaceIfExists set", [&]() { exp_status([&]() { set_rename_information(h.get(), true, nullptr, dir + u"\\renamefile8"); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\renamefile9", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create directory", [&]() { 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); }); if (h && h2) { test("Check directory entry", [&]() { u16string_view name = u"renamefile9"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamefile9"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamefile9\"."); }); test("Move file via RootDirectory handle", [&]() { set_rename_information(h.get(), false, h2.get(), u"renamefile9"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamedir9\\renamefile9"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamedir9\\renamefile9\"."); }); test("Check old directory entry gone", [&]() { u16string_view name = u"renamefile9"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); test("Try checking new directory entry with handle still open", [&]() { u16string_view name = u"renamefile9"; exp_status([&]() { query_dir(dir + u"\\renamedir9", name); }, STATUS_SHARING_VIOLATION); }); h2.reset(); test("Check new directory entry", [&]() { u16string_view name = u"renamefile9"; auto items = query_dir(dir + u"\\renamedir9", name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); h.reset(); } test("Create directory", [&]() { 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); }); test("Create file", [&]() { h = create_file(dir + u"\\renamedir10\\renamefile10", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try checking directory entry with handle open", [&]() { u16string_view name = u"renamefile10"; exp_status([&]() { query_dir(dir + u"\\renamedir10", name); }, STATUS_SHARING_VIOLATION); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamedir10\\renamefile10"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamedir10\\renamefile10\"."); }); test("Rename file via RootDirectory handle", [&]() { set_rename_information(h.get(), false, h2.get(), u"renamefile10a"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamedir10\\renamefile10a"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamedir10\\renamefile10a\"."); }); h2.reset(); test("Check old directory entry gone", [&]() { u16string_view name = u"renamefile10"; exp_status([&]() { query_dir(dir + u"\\renamedir10", name); }, STATUS_NO_SUCH_FILE); }); test("Check new directory entry", [&]() { u16string_view name = u"renamefile10a"; auto items = query_dir(dir + u"\\renamedir10", name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); } test("Create directory", [&]() { h2 = create_file(dir + u"\\renamedir11", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create file", [&]() { h = create_file(dir + u"\\renamedir11\\renamefile11", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h && h2) { test("Set directory permissions", [&]() { set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_FILE); }); h2.reset(); test("Rename file", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamedir11\\renamefile11a"); }); h.reset(); } test("Create directory", [&]() { h2 = create_file(dir + u"\\renamedir12", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create file", [&]() { h = create_file(dir + u"\\renamedir12\\renamefile12", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h && h2) { test("Clear directory permissions", [&]() { set_dacl(h2.get(), 0); }); h2.reset(); test("Try to rename file", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamedir12\\renamefile12a"); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create directory", [&]() { h2 = create_file(dir + u"\\renamedir13", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create subdir", [&]() { h = create_file(dir + u"\\renamedir13\\renamesubdir13", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h && h2) { test("Set directory permissions", [&]() { set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_SUBDIRECTORY); }); h2.reset(); test("Rename subdir", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamedir13\\renamesubdir13a"); }); h.reset(); } test("Create directory", [&]() { h2 = create_file(dir + u"\\renamedir14", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create subdir", [&]() { h = create_file(dir + u"\\renamedir14\\renamesubdir14", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h && h2) { test("Clear directory permissions", [&]() { set_dacl(h2.get(), 0); }); h2.reset(); test("Try to rename subdir", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamedir14\\renamefile14a"); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\renamefile15", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try renaming file without DELETE access", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamefile15a"); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\renamedir16", FILE_LIST_DIRECTORY, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Try renaming directory without DELETE access", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamedir16a"); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create directory 1", [&]() { h2 = create_file(dir + u"\\renamedir17a", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create file", [&]() { h = create_file(dir + u"\\renamedir17a\\file", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h && h2) { test("Clear directory 1 permissions", [&]() { set_dacl(h2.get(), 0); }); h2.reset(); test("Create directory 2", [&]() { h2 = create_file(dir + u"\\renamedir17b", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h2) { test("Set directory 2 permissions", [&]() { set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_FILE); }); h2.reset(); } test("Move file to directory 2", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamedir17b\\file"); }); test("Try to move back to directory 1", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamedir17a\\file"); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create directory 1", [&]() { h2 = create_file(dir + u"\\renamedir18a", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create subdir", [&]() { h = create_file(dir + u"\\renamedir18a\\subdir", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h && h2) { test("Clear directory 1 permissions", [&]() { set_dacl(h2.get(), 0); }); h2.reset(); test("Create directory 2", [&]() { h2 = create_file(dir + u"\\renamedir18b", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h2) { test("Set directory 2 permissions", [&]() { set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_SUBDIRECTORY); }); h2.reset(); } test("Move file to directory 2", [&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamedir18b\\subdir"); }); test("Try to move back to directory 1", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamedir18a\\subdir"); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\renamedir19", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Set directory permissions", [&]() { set_dacl(h.get(), SYNCHRONIZE | FILE_TRAVERSE | FILE_ADD_FILE); }); h.reset(); test("Create file 1", [&]() { h = create_file(dir + u"\\renamedir19\\file1", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create file 2", [&]() { h2 = create_file(dir + u"\\renamedir19\\file2", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h2) { test("Clear file 2 permissions", [&]() { set_dacl(h2.get(), 0); }); h2.reset(); test("Try to overwrite file 2 with file 1", [&]() { exp_status([&]() { set_rename_information(h.get(), true, nullptr, dir + u"\\renamedir19\\file2"); }, STATUS_ACCESS_DENIED); }); } h.reset(); } } test("Create directory", [&]() { h = create_file(dir + u"\\renamedir20", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Set directory permissions (inc. FILE_DELETE_CHILD)", [&]() { set_dacl(h.get(), SYNCHRONIZE | FILE_TRAVERSE | FILE_ADD_FILE | FILE_DELETE_CHILD); }); h.reset(); test("Create file 1", [&]() { h = create_file(dir + u"\\renamedir20\\file1", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create file 2", [&]() { h2 = create_file(dir + u"\\renamedir20\\file2", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h2) { test("Clear file 2 permissions", [&]() { set_dacl(h2.get(), 0); }); h2.reset(); test("Overwrite file 2 with file 1", [&]() { set_rename_information(h.get(), true, nullptr, dir + u"\\renamedir20\\file2"); }); } h.reset(); } } test("Create directory", [&]() { h = create_file(dir + u"\\renamedir21", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Set directory permissions", [&]() { set_dacl(h.get(), SYNCHRONIZE | FILE_TRAVERSE | FILE_ADD_FILE); }); h.reset(); test("Create file 1", [&]() { h = create_file(dir + u"\\renamedir21\\file1", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create file 2", [&]() { h2 = create_file(dir + u"\\renamedir21\\file2", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h2) { test("Set file 2 permissions to DELETE", [&]() { set_dacl(h2.get(), DELETE); }); h2.reset(); test("Overwrite file 2 with file 1", [&]() { set_rename_information(h.get(), true, nullptr, dir + u"\\renamedir21\\file2"); }); } h.reset(); } } test("Create file", [&]() { h = create_file(dir + u"\\renamefile22a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create readonly file", [&]() { create_file(dir + u"\\renamefile22b", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Try to overwrite readonly file", [&]() { exp_status([&]() { set_rename_information(h.get(), true, nullptr, dir + u"\\renamefile22b"); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\renamefile23a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create system file", [&]() { create_file(dir + u"\\renamefile23b", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_SYSTEM, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Overwrite system file", [&]() { set_rename_information(h.get(), true, nullptr, dir + u"\\renamefile23b"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\renamefile24", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { struct { u16string name; string desc; } invalid_names[] = { { u"/", "slash" }, { u":", "colon" }, { u"<", "less than" }, { u">", "greater than" }, { u"\"", "quote" }, { u"|", "pipe" }, { u"?", "question mark" }, { u"*", "asterisk" } }; for (const auto& n : invalid_names) { test("Try renaming to invalid name (" + n.desc + ")", [&]() { auto fn = dir + u"\\renamefile24" + n.name; exp_status([&]() { set_rename_information(h.get(), false, nullptr, fn); }, STATUS_OBJECT_NAME_INVALID); }); } bool is_ntfs = fstype == fs_type::ntfs; test("Rename to file with more than 255 UTF-8 characters", [&]() { auto fn = dir + u"\\rename24"; for (unsigned int i = 0; i < 64; i++) { fn += u"\U0001f525"; } exp_status([&]() { set_rename_information(h.get(), false, nullptr, fn); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); test("Rename to file with WTF-16 (1)", [&]() { auto fn = dir + u"\\rename24"; fn += (char16_t)0xd83d; exp_status([&]() { set_rename_information(h.get(), false, nullptr, fn); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); test("Rename to file with WTF-16 (2)", [&]() { auto fn = dir + u"\\rename24"; fn += (char16_t)0xdd25; exp_status([&]() { set_rename_information(h.get(), false, nullptr, fn); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); test("Rename to file with WTF-16 (3)", [&]() { auto fn = dir + u"\\rename24"; fn += (char16_t)0xdd25; fn += (char16_t)0xd83d; exp_status([&]() { set_rename_information(h.get(), false, nullptr, fn); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); h.reset(); } test("Create directory 1", [&]() { h = create_file(dir + u"\\renamedir25a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create directory 2", [&]() { h2 = create_file(dir + u"\\renamedir25b", MAXIMUM_ALLOWED, 0, FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h && h2) { test("Try to overwrite directory 2", [&]() { exp_status([&]() { set_rename_information(h.get(), true, nullptr, dir + u"\\renamedir25b"); }, STATUS_ACCESS_DENIED); }); h.reset(); h2.reset(); } test("Create file 1", [&]() { create_file(dir + u"\\renamefile26a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create file 2", [&]() { h = create_file(dir + u"\\renamefile26b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try to move file within other file", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\renamefile26a\\file"); }, STATUS_INVALID_PARAMETER); }); h.reset(); } // FIXME - does SD change when file moved across directories? // FIXME - check can't rename root directory? } void test_rename_ex(HANDLE token, const u16string& dir) { unique_handle h, h2; // FileRenameInformationEx introduced with Windows 10 1709 test("Create file 1", [&]() { create_file(dir + u"\\renamefileex1a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create file 2", [&]() { h = create_file(dir + u"\\renamefileex1b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try renaming file 2 to file 1 without FILE_RENAME_REPLACE_IF_EXISTS set", [&]() { exp_status([&]() { set_rename_information_ex(h.get(), 0, nullptr, dir + u"\\renamefileex1a"); }, STATUS_OBJECT_NAME_COLLISION); }); test("Rename file 2 to file 1 with FILE_RENAME_REPLACE_IF_EXISTS", [&]() { set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS, nullptr, dir + u"\\renamefileex1a"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamefileex1a"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamefileex1a\"."); }); test("Check directory entry", [&]() { u16string_view name = u"renamefileex1a"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); h.reset(); } test("Create file 1", [&]() { h2 = create_file(dir + u"\\renamefileex2a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create file 2", [&]() { h = create_file(dir + u"\\renamefileex2b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try renaming file 2 to file 1 without FILE_RENAME_REPLACE_IF_EXISTS", [&]() { exp_status([&]() { set_rename_information_ex(h.get(), 0, nullptr, dir + u"\\renamefileex2a"); }, STATUS_OBJECT_NAME_COLLISION); }); test("Try renaming file 2 to file 1 with FILE_RENAME_REPLACE_IF_EXISTS and file 1 open", [&]() { exp_status([&]() { set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS, nullptr, dir + u"\\renamefileex2a"); }, STATUS_ACCESS_DENIED); }); h.reset(); } h2.reset(); test("Create file 1", [&]() { create_file(dir + u"\\renamefileex3a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create file 2", [&]() { h = create_file(dir + u"\\renamefileex3b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try renaming file 2 to file 1 uppercase without FILE_RENAME_REPLACE_IF_EXISTS", [&]() { exp_status([&]() { set_rename_information_ex(h.get(), 0, nullptr, dir + u"\\RENAMEFILEEX3A"); }, STATUS_OBJECT_NAME_COLLISION); }); test("Rename file 2 to file 1 uppercase with FILE_RENAME_REPLACE_IF_EXISTS", [&]() { set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS, nullptr, dir + u"\\RENAMEFILEEX3A"); }); test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\RENAMEFILEEX3A"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\RENAMEFILEEX3A\"."); }); test("Check directory entry", [&]() { u16string_view name = u"RENAMEFILEEX3A"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\renamefileex4a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create readonly file", [&]() { create_file(dir + u"\\renamefileex4b", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Try to overwrite readonly file", [&]() { exp_status([&]() { set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS, nullptr, dir + u"\\renamefileex4b"); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\renamefileex5a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Create readonly file", [&]() { create_file(dir + u"\\renamefileex5b", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Overwrite readonly file using FILE_RENAME_IGNORE_READONLY_ATTRIBUTE", [&]() { set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_IGNORE_READONLY_ATTRIBUTE, nullptr, dir + u"\\renamefileex5b"); }); h.reset(); } // traverse privilege needed to query hard links test("Add SeChangeNotifyPrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file 1", [&]() { h = create_file(dir + u"\\renamefileex6a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create file 2", [&]() { h2 = create_file(dir + u"\\renamefileex6b", SYNCHRONIZE | DELETE | FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h && h2) { test("Overwrite file 2 using FILE_RENAME_POSIX_SEMANTICS", [&]() { set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS, nullptr, dir + u"\\renamefileex6b"); }); test("Check name of file 1", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\renamefileex6b"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\renamefileex6b\"."); }); test("Check name of file 2", [&]() { auto fn = query_file_name_information(h2.get()); static const u16string_view ends_with = u"\\renamefileex6b"; // NTFS moves this to \$Extend\$Deleted directory if (fn.size() >= ends_with.size() && fn.substr(fn.size() - ends_with.size()) == ends_with) throw runtime_error("Name ended with \"\\renamefileex6b\"."); }); test("Check standard information of file 1", [&]() { auto fsi = query_information(h.get()); if (fsi.NumberOfLinks != 1) throw formatted_error("NumberOfLinks was {}, expected 1", fsi.NumberOfLinks); if (fsi.DeletePending) throw runtime_error("DeletePending was true, expected false"); }); test("Check standard link information of file 1", [&]() { auto fsli = query_information(h.get()); if (fsli.NumberOfAccessibleLinks != 1) throw formatted_error("NumberOfAccessibleLinks was {}, expected 1", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (fsli.DeletePending) throw runtime_error("DeletePending was true, expected false"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Check standard information of file 2", [&]() { auto fsi = query_information(h2.get()); if (fsi.NumberOfLinks != 0) throw formatted_error("NumberOfLinks was {}, expected 0", fsi.NumberOfLinks); if (!fsi.DeletePending) throw runtime_error("DeletePending was false, expected true"); }); test("Check standard link information of file 2", [&]() { auto fsli = query_information(h2.get()); if (fsli.NumberOfAccessibleLinks != 0) throw formatted_error("NumberOfAccessibleLinks was {}, expected 0", fsli.NumberOfAccessibleLinks); if (fsli.TotalNumberOfLinks != 1) throw formatted_error("TotalNumberOfLinks was {}, expected 1", fsli.TotalNumberOfLinks); if (!fsli.DeletePending) throw runtime_error("DeletePending was false, expected true"); if (fsli.Directory) throw runtime_error("Directory was true, expected false"); }); test("Check new directory entry", [&]() { u16string_view name = u"renamefileex6b"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); test("Check old directory entry gone", [&]() { u16string_view name = u"renamefileex6a"; exp_status([&]() { query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); test("Try to clear delete bit on file 2", [&]() { exp_status([&]() { set_disposition_information(h2.get(), false); }, STATUS_FILE_DELETED); }); test("Write to file 2", [&]() { static const vector data = {'h','e','l','l','o'}; write_file(h2.get(), data); }); test("Read from file 2", [&]() { static const vector exp = {'h','e','l','l','o'}; auto buf = read_file(h2.get(), exp.size(), 0); if (buf.size() != exp.size()) throw formatted_error("Read {} bytes, expected {}", buf.size(), exp.size()); if (buf != exp) throw runtime_error("Data read did not match data written"); }); int64_t dir_id; test("Check file 1 hardlinks", [&]() { auto items = query_links(h.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& item = items.front(); if (item.second != u"renamefileex6b") throw formatted_error("Link was called {}, expected renamefileex6b", u16string_to_string(item.second)); dir_id = item.first; }); test("Check file 2 hardlinks", [&]() { auto items = query_links(h2.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& item = items.front(); if (item.second == u"renamefileex6b") throw formatted_error("Link was called renamefileex6b, expected something else", u16string_to_string(item.second)); if (item.first == dir_id) throw runtime_error("Dir ID of orphaned inode is same as before"); }); h.reset(); h2.reset(); } test("Disable token privileges", [&]() { disable_token_privileges(token); }); test("Create file 1", [&]() { h = create_file(dir + u"\\renamefileex7a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Create file 2 without FILE_SHARE_DELETE", [&]() { h2 = create_file(dir + u"\\renamefileex7b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h && h2) { test("Try to overwrite file 2 using FILE_RENAME_POSIX_SEMANTICS", [&]() { exp_status([&]() { set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS, nullptr, dir + u"\\renamefileex7b"); }, STATUS_SHARING_VIOLATION); }); h.reset(); h2.reset(); } test("Create directory 1", [&]() { h = create_file(dir + u"\\renamedirex8a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create directory 2", [&]() { h2 = create_file(dir + u"\\renamedirex8b", MAXIMUM_ALLOWED, 0, FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h && h2) { test("Try to overwrite directory 2", [&]() { exp_status([&]() { set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS, nullptr, dir + u"\\renamedirex8b"); }, STATUS_ACCESS_DENIED); }); h.reset(); h2.reset(); } test("Create directory 1", [&]() { h = create_file(dir + u"\\renamedirex9a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create directory 2", [&]() { h2 = create_file(dir + u"\\renamedirex9b", MAXIMUM_ALLOWED, 0, FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h && h2) { test("Overwrite directory 2 using FILE_RENAME_POSIX_SEMANTICS", [&]() { set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS, nullptr, dir + u"\\renamedirex9b"); }); h.reset(); h2.reset(); } test("Create directory 1", [&]() { h = create_file(dir + u"\\renamedirex10a", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create directory 2", [&]() { h2 = create_file(dir + u"\\renamedirex10b", MAXIMUM_ALLOWED, 0, FILE_SHARE_DELETE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create file in directory 2", [&]() { create_file(dir + u"\\renamedirex10b\\file", MAXIMUM_ALLOWED, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED); }); if (h && h2) { test("Try overwriting non-empty directory using FILE_RENAME_POSIX_SEMANTICS", [&]() { exp_status([&]() { set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS, nullptr, dir + u"\\renamedirex10b"); }, STATUS_DIRECTORY_NOT_EMPTY); }); h.reset(); h2.reset(); } test("Create image file", [&]() { h = create_file(dir + u"\\renamefileex11a", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); test("Create file", [&]() { h2 = create_file(dir + u"\\renamefileex11b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); auto img = pe_image(as_bytes(span("hello"))); if (h && h2) { unique_handle sect; test("Write to file", [&]() { write_file(h.get(), img); }); test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get()); }); h.reset(); if (sect) { test("Try overwriting mapped image file by rename", [&]() { exp_status([&]() { set_rename_information_ex(h2.get(), FILE_RENAME_REPLACE_IF_EXISTS, nullptr, dir + u"\\renamefileex11a"); }, STATUS_ACCESS_DENIED); }); } h2.reset(); } test("Create image file", [&]() { h = create_file(dir + u"\\renamefileex12a", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); test("Create file", [&]() { h2 = create_file(dir + u"\\renamefileex12b", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h && h2) { unique_handle sect; test("Write to file", [&]() { write_file(h.get(), img); }); test("Create section", [&]() { sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get()); }); h.reset(); if (sect) { test("Try overwriting mapped image file by POSIX rename", [&]() { exp_status([&]() { set_rename_information_ex(h2.get(), FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS, nullptr, dir + u"\\renamefileex12a"); }, STATUS_ACCESS_DENIED); }); } h2.reset(); } // FIXME - FILE_RENAME_SUPPRESS_PIN_STATE_INHERITANCE // FIXME - FILE_RENAME_SUPPRESS_STORAGE_RESERVE_INHERITANCE // FIXME - FILE_RENAME_NO_INCREASE_AVAILABLE_SPACE // FIXME - FILE_RENAME_NO_DECREASE_AVAILABLE_SPACE // FIXME - FILE_RENAME_FORCE_RESIZE_TARGET_SR // FIXME - FILE_RENAME_FORCE_RESIZE_SOURCE_SR } ================================================ FILE: src/tests/reparse.cpp ================================================ #include "test.h" #define FSCTL_SET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) #define FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_DELETE_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 43, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) #define IO_REPARSE_TAG_FAKE_MICROSOFT 0x8000DEAD #define IO_REPARSE_TAG_FAKE 0x0000BEEF #define IO_REPARSE_TAG_FAKE_MICROSOFT_DIR 0x9000CAFE #ifdef _MSC_VER #define SYMLINK_FLAG_RELATIVE 1 #endif static const uint8_t reparse_guid[] = { 0xc5, 0xcc, 0x8b, 0xf2, 0xdc, 0xc3, 0x88, 0x42, 0xa1, 0xe2, 0x50, 0x43, 0x97, 0xeb, 0x26, 0xa6 }; static const uint8_t wrong_guid[] = { 0x61, 0x81, 0x36, 0x76, 0x32, 0xa6, 0xbc, 0x4d, 0xb7, 0xe0, 0x3a, 0xb6, 0x60, 0x03, 0x9e, 0x4e }; using namespace std; static void set_symlink(HANDLE h, u16string_view substitute_name, u16string_view print_name, bool relative) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf; buf.resize(offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + ((substitute_name.length() + print_name.length()) * sizeof(char16_t))); auto& rdb = *(REPARSE_DATA_BUFFER*)buf.data(); rdb.ReparseTag = IO_REPARSE_TAG_SYMLINK; rdb.ReparseDataLength = buf.size() - offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer); rdb.Reserved = 0; rdb.SymbolicLinkReparseBuffer.SubstituteNameOffset = 0; rdb.SymbolicLinkReparseBuffer.SubstituteNameLength = substitute_name.length() * sizeof(char16_t); rdb.SymbolicLinkReparseBuffer.PrintNameOffset = rdb.SymbolicLinkReparseBuffer.SubstituteNameLength; rdb.SymbolicLinkReparseBuffer.PrintNameLength = print_name.length() * sizeof(char16_t); rdb.SymbolicLinkReparseBuffer.Flags = relative ? SYMLINK_FLAG_RELATIVE : 0; memcpy((char*)rdb.SymbolicLinkReparseBuffer.PathBuffer + rdb.SymbolicLinkReparseBuffer.SubstituteNameOffset, substitute_name.data(), rdb.SymbolicLinkReparseBuffer.SubstituteNameLength); memcpy((char*)rdb.SymbolicLinkReparseBuffer.PathBuffer + rdb.SymbolicLinkReparseBuffer.PrintNameOffset, print_name.data(), rdb.SymbolicLinkReparseBuffer.PrintNameLength); auto ev = create_event(); Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb, FSCTL_SET_REPARSE_POINT, buf.data(), buf.size(), nullptr, 0); if (Status == STATUS_PENDING) { Status = NtWaitForSingleObject(ev.get(), false, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); Status = iosb.Status; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static void set_mount_point(HANDLE h, u16string_view substitute_name, u16string_view print_name) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf; // both substitute and print strings need to be null-terminated buf.resize(offsetof(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) + ((substitute_name.length() + 1 + print_name.length() + 1) * sizeof(char16_t))); auto& rdb = *(REPARSE_DATA_BUFFER*)buf.data(); rdb.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; rdb.ReparseDataLength = buf.size() - offsetof(REPARSE_DATA_BUFFER, MountPointReparseBuffer); rdb.Reserved = 0; auto& mprb = rdb.MountPointReparseBuffer; mprb.SubstituteNameOffset = 0; mprb.SubstituteNameLength = substitute_name.length() * sizeof(char16_t); mprb.PrintNameOffset = mprb.SubstituteNameLength + sizeof(char16_t); mprb.PrintNameLength = print_name.length() * sizeof(char16_t); memcpy((char*)mprb.PathBuffer + mprb.SubstituteNameOffset, substitute_name.data(), mprb.SubstituteNameLength); mprb.PathBuffer[(mprb.SubstituteNameOffset + mprb.SubstituteNameLength) / sizeof(char16_t)] = 0; memcpy((char*)mprb.PathBuffer + mprb.PrintNameOffset, print_name.data(), mprb.PrintNameLength); mprb.PathBuffer[(mprb.PrintNameOffset + mprb.PrintNameLength) / sizeof(char16_t)] = 0; auto ev = create_event(); Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb, FSCTL_SET_REPARSE_POINT, buf.data(), buf.size(), nullptr, 0); if (Status == STATUS_PENDING) { Status = NtWaitForSingleObject(ev.get(), false, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); Status = iosb.Status; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static void set_ms_reparse_point(HANDLE h, uint32_t tag, span data) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf; buf.resize(offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + data.size()); auto& rdb = *(REPARSE_DATA_BUFFER*)buf.data(); rdb.ReparseTag = tag; rdb.ReparseDataLength = buf.size() - offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer); rdb.Reserved = 0; memcpy(rdb.GenericReparseBuffer.DataBuffer, data.data(), data.size()); auto ev = create_event(); Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb, FSCTL_SET_REPARSE_POINT, buf.data(), buf.size(), nullptr, 0); if (Status == STATUS_PENDING) { Status = NtWaitForSingleObject(ev.get(), false, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); Status = iosb.Status; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static void set_reparse_point_guid(HANDLE h, uint32_t tag, const uint8_t* guid, span data) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf; buf.resize(offsetof(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + data.size()); auto& rgdb = *(REPARSE_GUID_DATA_BUFFER*)buf.data(); rgdb.ReparseTag = tag; rgdb.ReparseDataLength = buf.size() - offsetof(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer.DataBuffer); rgdb.Reserved = 0; memcpy(&rgdb.ReparseGuid, guid, sizeof(rgdb.ReparseGuid)); memcpy(rgdb.GenericReparseBuffer.DataBuffer, data.data(), data.size()); auto ev = create_event(); Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb, FSCTL_SET_REPARSE_POINT, buf.data(), buf.size(), nullptr, 0); if (Status == STATUS_PENDING) { Status = NtWaitForSingleObject(ev.get(), false, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); Status = iosb.Status; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static varbuf query_reparse_point(HANDLE h) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf; buf.resize(4096); auto ev = create_event(); Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb, FSCTL_GET_REPARSE_POINT, nullptr, 0, buf.data(), buf.size()); if (Status == STATUS_PENDING) { Status = NtWaitForSingleObject(ev.get(), false, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); Status = iosb.Status; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); auto& rdb = *(REPARSE_DATA_BUFFER*)buf.data(); varbuf ret; ret.buf.resize(offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer) + rdb.ReparseDataLength); memcpy(ret.buf.data(), buf.data(), ret.buf.size()); return ret; } static varbuf query_reparse_point_guid(HANDLE h) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf; buf.resize(4096); auto ev = create_event(); Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb, FSCTL_GET_REPARSE_POINT, nullptr, 0, buf.data(), buf.size()); if (Status == STATUS_PENDING) { Status = NtWaitForSingleObject(ev.get(), false, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); Status = iosb.Status; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); auto& rgdb = *(REPARSE_GUID_DATA_BUFFER*)buf.data(); varbuf ret; ret.buf.resize(offsetof(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + rgdb.ReparseDataLength); memcpy(ret.buf.data(), buf.data(), ret.buf.size()); return ret; } static void delete_reparse_point(HANDLE h, uint32_t tag) { NTSTATUS Status; IO_STATUS_BLOCK iosb; REPARSE_DATA_BUFFER rdb; auto ev = create_event(); rdb.ReparseTag = tag; rdb.ReparseDataLength = 0; rdb.Reserved = 0; Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb, FSCTL_DELETE_REPARSE_POINT, &rdb, offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer), nullptr, 0); if (Status == STATUS_PENDING) { Status = NtWaitForSingleObject(ev.get(), false, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); Status = iosb.Status; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static void delete_reparse_point_guid(HANDLE h, uint32_t tag, const uint8_t* guid) { NTSTATUS Status; IO_STATUS_BLOCK iosb; REPARSE_GUID_DATA_BUFFER rgdb; auto ev = create_event(); rgdb.ReparseTag = tag; rgdb.ReparseDataLength = 0; rgdb.Reserved = 0; memcpy(&rgdb.ReparseGuid, guid, sizeof(rgdb.ReparseGuid)); Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb, FSCTL_DELETE_REPARSE_POINT, &rgdb, offsetof(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer), nullptr, 0); if (Status == STATUS_PENDING) { Status = NtWaitForSingleObject(ev.get(), false, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); Status = iosb.Status; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } template static void check_reparse_dirent(const u16string& dir, u16string_view name, uint32_t tag) { auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); if constexpr (requires { T::ReparsePointTag; }) { if (fdi.EaSize != 0) throw formatted_error("EaSize was {:08x}, expected 0", fdi.EaSize); if (fdi.ReparsePointTag != tag) throw formatted_error("ReparsePointTag was {:08x}, expected {:08x}", fdi.ReparsePointTag, tag); } else { if (fdi.EaSize != tag) throw formatted_error("EaSize was {:08x}, expected {:08x}", fdi.EaSize, tag); } } void test_reparse(HANDLE token, const u16string& dir) { unique_handle h; int64_t file1id = 0, file2id = 0; test("Add SeCreateSymbolicLinkPrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_CREATE_SYMBOLIC_LINK_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create file", [&]() { h = create_file(dir + u"\\reparse1", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); file1id = fii.IndexNumber.QuadPart; }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\reparse2", FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_READ_EA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); file2id = fii.IndexNumber.QuadPart; }); test("Clear archive flag", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL", fbi.FileAttributes); }); test("Set as symlink", [&]() { set_symlink(h.get(), u"reparse1", u"reparse1", true); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_REPARSE_POINT) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT", fbi.FileAttributes); }); test("Query reparse point", [&]() { auto buf = query_reparse_point(h.get()); auto& rdb = *static_cast(buf); if (rdb.ReparseTag != IO_REPARSE_TAG_SYMLINK) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_SYMLINK", rdb.ReparseTag); auto& slrb = rdb.SymbolicLinkReparseBuffer; auto dest = u16string_view((char16_t*)((char*)slrb.PathBuffer + slrb.SubstituteNameOffset), slrb.SubstituteNameLength / sizeof(char16_t)); if (dest != u"reparse1") throw formatted_error("Destination was \"{}\", expected \"reparse1\"", u16string_to_string(dest)); if (slrb.Flags != SYMLINK_FLAG_RELATIVE) throw formatted_error("Flags value was {}, expected SYMLINK_FLAG_RELATIVE", slrb.Flags); }); // Only works on specific NTFS metadata file - see section 2.1.5.5.2 of [MS-FSA] test("Try checking FileReparsePointInformation", [&]() { exp_status([&]() { query_dir(dir, u"reparse2"); }, STATUS_INVALID_INFO_CLASS); }); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != 0) throw formatted_error("EaSize was {:08x}, expected 0", feai.EaSize); }); // needs FILE_READ_ATTRIBUTES test("Query FileStatInformation", [&]() { auto fsi = query_information(h.get()); if (fsi.ReparseTag != IO_REPARSE_TAG_SYMLINK) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_SYMLINK", fsi.ReparseTag); }); // needs FILE_READ_EA as well test("Query FileStatLxInformation", [&]() { auto fsli = query_information(h.get()); if (fsli.ReparseTag != IO_REPARSE_TAG_SYMLINK) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_SYMLINK", fsli.ReparseTag); }); test("Query FileAttributeTagInformation", [&]() { auto fati = query_information(h.get()); if (fati.ReparseTag != IO_REPARSE_TAG_SYMLINK) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_SYMLINK", fati.ReparseTag); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\reparse2", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Check ID", [&]() { auto fii = query_information(h.get()); if (fii.IndexNumber.QuadPart != file1id) throw runtime_error("File ID was not expected value"); }); h.reset(); } test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse2", IO_REPARSE_TAG_SYMLINK); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse2", IO_REPARSE_TAG_SYMLINK); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse2", IO_REPARSE_TAG_SYMLINK); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse2", IO_REPARSE_TAG_SYMLINK); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse2", IO_REPARSE_TAG_SYMLINK); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse2", IO_REPARSE_TAG_SYMLINK); }); test("Open file with FILE_OPEN_REPARSE_POINT", [&]() { h = create_file(dir + u"\\reparse2", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED); }); if (h) { test("Check ID", [&]() { auto fii = query_information(h.get()); if (fii.IndexNumber.QuadPart != file2id) throw runtime_error("File ID was not expected value"); }); test("Try deleting reparse point with wrong tag", [&]() { exp_status([&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_MOUNT_POINT); }, STATUS_IO_REPARSE_TAG_MISMATCH); }); test("Clear archive flag", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_REPARSE_POINT) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT", fbi.FileAttributes); }); test("Delete reparse point", [&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_SYMLINK); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL", fbi.FileAttributes); }); test("Try to delete reparse point again", [&]() { exp_status([&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_SYMLINK); }, STATUS_NOT_A_REPARSE_POINT); }); test("Try to query reparse point", [&]() { exp_status([&]() { query_reparse_point_guid(h.get()); }, STATUS_NOT_A_REPARSE_POINT); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\reparse2", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Check ID", [&]() { auto fii = query_information(h.get()); if (fii.IndexNumber.QuadPart != file2id) throw runtime_error("File ID was not expected value"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\reparse3", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Set as symlink", [&]() { set_symlink(h.get(), u"reparse1", u"reparse1", true); }); h.reset(); } test("Overwrite file through symlink", [&]() { create_file(dir + u"\\reparse3", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OVERWRITE, FILE_NON_DIRECTORY_FILE, FILE_OVERWRITTEN); }); test("Check target rather than symlink overwritten", [&]() { check_reparse_dirent(dir, u"reparse3", IO_REPARSE_TAG_SYMLINK); }); test("Overwrite symlink", [&]() { create_file(dir + u"\\reparse3", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OVERWRITE, FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, FILE_OVERWRITTEN); }); test("Check symlink overwritten", [&]() { check_reparse_dirent(dir, u"reparse3", 0); }); test("Create file", [&]() { h = create_file(dir + u"\\reparse4", FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Write to file", [&]() { write_file_wait(h.get(), random_data(4096), 0); }); test("Set as symlink", [&]() { exp_status([&]() { set_symlink(h.get(), u"reparse1", u"reparse1", true); }, STATUS_IO_REPARSE_DATA_INVALID); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\reparse5", FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Set as symlink", [&]() { set_symlink(h.get(), u"reparse1", u"reparse1", true); }); test("Write to file", [&]() { write_file_wait(h.get(), random_data(4096), 0); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\reparse6", FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Set as symlink", [&]() { set_symlink(h.get(), u"reparse1", u"reparse1", true); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\reparse7", FILE_WRITE_DATA | FILE_WRITE_EA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Write EA", [&]() { write_ea(h.get(), "hello", "world"); }); test("Set as symlink", [&]() { set_symlink(h.get(), u"reparse1", u"reparse1", true); }); test("Write another EA", [&]() { write_ea(h.get(), "lemon", "curry"); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\reparse8", FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Set as symlink with invalid target", [&]() { set_symlink(h.get(), u"reparsenonsuch", u"reparsenonsuch", true); }); h.reset(); } test("Try to open invalid file through symlink", [&]() { exp_status([&]() { create_file(dir + u"\\reparse8", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }, STATUS_OBJECT_NAME_NOT_FOUND); }); test("Create file", [&]() { h = create_file(dir + u"\\reparse9a", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); file1id = fii.IndexNumber.QuadPart; }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\reparse9b", FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Set as absolute symlink", [&]() { set_symlink(h.get(), dir + u"\\reparse9a", u"reparse9a", false); }); h.reset(); } test("Open file through absolute symlink", [&]() { h = create_file(dir + u"\\reparse9b", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); if (fii.IndexNumber.QuadPart != file1id) throw runtime_error("File ID had unexpected value"); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\reparse10a", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Get directory ID", [&]() { auto fii = query_information(h.get()); file1id = fii.IndexNumber.QuadPart; }); h.reset(); } test("Create file within directory", [&]() { create_file(dir + u"\\reparse10a\\file", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); test("Create directory", [&]() { h = create_file(dir + u"\\reparse10b", FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_READ_EA, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Get directory ID", [&]() { auto fii = query_information(h.get()); file2id = fii.IndexNumber.QuadPart; }); test("Clear archive flag", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY", fbi.FileAttributes); }); test("Set as mount point", [&]() { set_mount_point(h.get(), dir + u"\\reparse10a", u"reparse10a"); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY", fbi.FileAttributes); }); test("Query reparse point", [&]() { auto buf = query_reparse_point(h.get()); auto& rdb = *static_cast(buf); if (rdb.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_MOUNT_POINT", rdb.ReparseTag); auto& mprb = rdb.MountPointReparseBuffer; auto dest = u16string_view((char16_t*)((char*)mprb.PathBuffer + mprb.SubstituteNameOffset), mprb.SubstituteNameLength / sizeof(char16_t)); if (dest != dir + u"\\reparse10a") throw formatted_error("Destination was \"{}\", expected \"{}\"", u16string_to_string(dest), u16string_to_string(dir + u"\\reparse10a")); }); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != 0) throw formatted_error("EaSize was {:08x}, expected 0", feai.EaSize); }); // needs FILE_READ_ATTRIBUTES test("Query FileStatInformation", [&]() { auto fsi = query_information(h.get()); if (fsi.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_MOUNT_POINT", fsi.ReparseTag); }); // needs FILE_READ_EA as well test("Query FileStatLxInformation", [&]() { auto fsli = query_information(h.get()); if (fsli.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_MOUNT_POINT", fsli.ReparseTag); }); test("Query FileAttributeTagInformation", [&]() { auto fati = query_information(h.get()); if (fati.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_MOUNT_POINT", fati.ReparseTag); }); h.reset(); } test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse10b", IO_REPARSE_TAG_MOUNT_POINT); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse10b", IO_REPARSE_TAG_MOUNT_POINT); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse10b", IO_REPARSE_TAG_MOUNT_POINT); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse10b", IO_REPARSE_TAG_MOUNT_POINT); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse10b", IO_REPARSE_TAG_MOUNT_POINT); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse10b", IO_REPARSE_TAG_MOUNT_POINT); }); test("Open mount point without FILE_OPEN_REPARSE_POINT", [&]() { h = create_file(dir + u"\\reparse10b", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Get directory ID", [&]() { auto fii = query_information(h.get()); if (fii.IndexNumber.QuadPart != file1id) throw runtime_error("Directory ID had unexpected value"); }); h.reset(); } test("Open mount point with FILE_OPEN_REPARSE_POINT", [&]() { h = create_file(dir + u"\\reparse10b", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED); }); if (h) { test("Get directory ID", [&]() { auto fii = query_information(h.get()); if (fii.IndexNumber.QuadPart != file2id) throw runtime_error("Directory ID had unexpected value"); }); h.reset(); } test("Open file through mount point (without FILE_OPEN_REPARSE_POINT)", [&]() { create_file(dir + u"\\reparse10b\\file", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); test("Open file through mount point (with FILE_OPEN_REPARSE_POINT)", [&]() { create_file(dir + u"\\reparse10b\\file", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED); }); test("Open mount point with FILE_OPEN_REPARSE_POINT", [&]() { h = create_file(dir + u"\\reparse10b", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED); }); if (h) { test("Clear archive flag", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY", fbi.FileAttributes); }); test("Delete reparse point", [&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_MOUNT_POINT); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY", fbi.FileAttributes); }); test("Try to delete reparse point again", [&]() { exp_status([&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_MOUNT_POINT); }, STATUS_NOT_A_REPARSE_POINT); }); test("Try to query reparse point", [&]() { exp_status([&]() { query_reparse_point_guid(h.get()); }, STATUS_NOT_A_REPARSE_POINT); }); h.reset(); } test("Open directory with children", [&]() { h = create_file(dir + u"\\reparse10a", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Try to set as mount point", [&]() { exp_status([&]() { set_mount_point(h.get(), dir + u"\\reparse10b", u"reparse10b"); }, STATUS_DIRECTORY_NOT_EMPTY); }); h.reset(); } test("Open old mount point directory", [&]() { h = create_file(dir + u"\\reparse10b", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Set as mount point", [&]() { set_mount_point(h.get(), dir + u"\\reparse10a", u"reparse10a"); }); test("Set as mount point again", [&]() { set_mount_point(h.get(), dir + u"\\reparse10c", u"reparse10c"); }); test("Set as symlink", [&]() { exp_status([&]() { set_symlink(h.get(), dir + u"\\reparse10a", u"reparse10a", false); }, STATUS_IO_REPARSE_TAG_MISMATCH); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\reparse11", FILE_WRITE_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Try to set as mount point", [&]() { exp_status([&]() { set_mount_point(h.get(), dir + u"\\reparse10a", u"reparse10a"); }, STATUS_NOT_A_DIRECTORY); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\reparse12", FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_READ_EA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); file1id = fii.IndexNumber.QuadPart; }); test("Clear archive flag", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL", fbi.FileAttributes); }); test("Set with Microsoft reparse tag", [&]() { static const string_view data = "hello"; set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT, span((uint8_t*)data.data(), data.size())); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_REPARSE_POINT) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT", fbi.FileAttributes); }); test("Query reparse point", [&]() { auto buf = query_reparse_point(h.get()); auto& rdb = *static_cast(buf); if (rdb.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT", rdb.ReparseTag); auto sv = string_view((char*)rdb.GenericReparseBuffer.DataBuffer, rdb.ReparseDataLength); if (sv != "hello") throw runtime_error("Reparse point data was not as expected"); }); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != 0) throw formatted_error("EaSize was {:08x}, expected 0", feai.EaSize); }); // needs FILE_READ_ATTRIBUTES test("Query FileStatInformation", [&]() { auto fsi = query_information(h.get()); if (fsi.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT", fsi.ReparseTag); }); // needs FILE_READ_EA as well test("Query FileStatLxInformation", [&]() { auto fsli = query_information(h.get()); if (fsli.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT", fsli.ReparseTag); }); test("Query FileAttributeTagInformation", [&]() { auto fati = query_information(h.get()); if (fati.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT", fati.ReparseTag); }); h.reset(); } test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse12", IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse12", IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse12", IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse12", IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse12", IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse12", IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Open file without FILE_OPEN_REPARSE_POINT", [&]() { exp_status([&]() { create_file(dir + u"\\reparse12", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_IO_REPARSE_TAG_NOT_HANDLED); }); test("Open file with FILE_OPEN_REPARSE_POINT", [&]() { h = create_file(dir + u"\\reparse12", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); if (fii.IndexNumber.QuadPart != file1id) throw runtime_error("File ID was not as expected."); }); test("Set with same Microsoft reparse tag", [&]() { static const string_view data = "world"; set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT, span((uint8_t*)data.data(), data.size())); }); test("Try to set with different Microsoft reparse tag", [&]() { exp_status([&]() { static const string_view data = "world"; set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT + 1, span((uint8_t*)data.data(), data.size())); }, STATUS_IO_REPARSE_TAG_MISMATCH); }); test("Clear archive flag", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_REPARSE_POINT) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT", fbi.FileAttributes); }); test("Delete reparse point", [&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL", fbi.FileAttributes); }); test("Try to delete reparse point again", [&]() { exp_status([&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT); }, STATUS_NOT_A_REPARSE_POINT); }); test("Try to query reparse point", [&]() { exp_status([&]() { query_reparse_point_guid(h.get()); }, STATUS_NOT_A_REPARSE_POINT); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\reparse13", FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_READ_EA, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Clear archive flag", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY", fbi.FileAttributes); }); test("Set with Microsoft reparse tag", [&]() { static const string_view data = "hello"; set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT, span((uint8_t*)data.data(), data.size())); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT", fbi.FileAttributes); }); test("Query reparse point", [&]() { auto buf = query_reparse_point(h.get()); auto& rdb = *static_cast(buf); if (rdb.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT", rdb.ReparseTag); auto sv = string_view((char*)rdb.GenericReparseBuffer.DataBuffer, rdb.ReparseDataLength); if (sv != "hello") throw runtime_error("Reparse point data was not as expected"); }); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != 0) throw formatted_error("EaSize was {:08x}, expected 0", feai.EaSize); }); // needs FILE_READ_ATTRIBUTES test("Query FileStatInformation", [&]() { auto fsi = query_information(h.get()); if (fsi.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT", fsi.ReparseTag); }); // needs FILE_READ_EA as well test("Query FileStatLxInformation", [&]() { auto fsli = query_information(h.get()); if (fsli.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT", fsli.ReparseTag); }); test("Query FileAttributeTagInformation", [&]() { auto fati = query_information(h.get()); if (fati.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT", fati.ReparseTag); }); h.reset(); } test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse13", IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse13", IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse13", IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse13", IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse13", IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse13", IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Open directory without FILE_OPEN_REPARSE_POINT", [&]() { exp_status([&]() { create_file(dir + u"\\reparse13", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_IO_REPARSE_TAG_NOT_HANDLED); }); test("Open directory with FILE_OPEN_REPARSE_POINT", [&]() { create_file(dir + u"\\reparse13", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED); }); test("Create file", [&]() { h = create_file(dir + u"\\reparse14", FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_READ_EA, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); file1id = fii.IndexNumber.QuadPart; }); test("Clear archive flag", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL", fbi.FileAttributes); }); test("Try to set with generic reparse tag without GUID", [&]() { exp_status([&]() { static const string_view data = "hello"; set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE, span((uint8_t*)data.data(), data.size())); }, STATUS_IO_REPARSE_DATA_INVALID); }); test("Set with generic reparse tag", [&]() { static const string_view data = "hello"; set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid, span((uint8_t*)data.data(), data.size())); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_REPARSE_POINT) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT", fbi.FileAttributes); }); test("Query reparse point", [&]() { auto buf = query_reparse_point_guid(h.get()); auto& rgdb = *static_cast(buf); if (rgdb.ReparseTag != IO_REPARSE_TAG_FAKE) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE", rgdb.ReparseTag); if (memcmp(&rgdb.ReparseGuid, reparse_guid, sizeof(rgdb.ReparseGuid))) throw runtime_error("Returned GUID was not as expected"); auto sv = string_view((char*)rgdb.GenericReparseBuffer.DataBuffer, rgdb.ReparseDataLength); if (sv != "hello") throw runtime_error("Reparse point data was not as expected"); }); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != 0) throw formatted_error("EaSize was {:08x}, expected 0", feai.EaSize); }); // needs FILE_READ_ATTRIBUTES test("Query FileStatInformation", [&]() { auto fsi = query_information(h.get()); if (fsi.ReparseTag != IO_REPARSE_TAG_FAKE) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE", fsi.ReparseTag); }); // needs FILE_READ_EA as well test("Query FileStatLxInformation", [&]() { auto fsli = query_information(h.get()); if (fsli.ReparseTag != IO_REPARSE_TAG_FAKE) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE", fsli.ReparseTag); }); test("Query FileAttributeTagInformation", [&]() { auto fati = query_information(h.get()); if (fati.ReparseTag != IO_REPARSE_TAG_FAKE) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE", fati.ReparseTag); }); h.reset(); } test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse14", IO_REPARSE_TAG_FAKE); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse14", IO_REPARSE_TAG_FAKE); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse14", IO_REPARSE_TAG_FAKE); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse14", IO_REPARSE_TAG_FAKE); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse14", IO_REPARSE_TAG_FAKE); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse14", IO_REPARSE_TAG_FAKE); }); test("Open file without FILE_OPEN_REPARSE_POINT", [&]() { exp_status([&]() { create_file(dir + u"\\reparse14", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_IO_REPARSE_TAG_NOT_HANDLED); }); test("Open file with FILE_OPEN_REPARSE_POINT", [&]() { h = create_file(dir + u"\\reparse14", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); if (fii.IndexNumber.QuadPart != file1id) throw runtime_error("File ID was not as expected."); }); test("Set with same reparse tag", [&]() { static const string_view data = "world"; set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid, span((uint8_t*)data.data(), data.size())); }); test("Set with same reparse tag but wrong GUID", [&]() { exp_status([&]() { static const string_view data = "world"; set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, wrong_guid, span((uint8_t*)data.data(), data.size())); }, STATUS_REPARSE_ATTRIBUTE_CONFLICT); }); test("Try to set with different reparse tag", [&]() { exp_status([&]() { static const string_view data = "world"; set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE + 1, reparse_guid, span((uint8_t*)data.data(), data.size())); }, STATUS_IO_REPARSE_TAG_MISMATCH); }); test("Clear archive flag", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_REPARSE_POINT) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT", fbi.FileAttributes); }); test("Try to delete reparse point without GUID", [&]() { exp_status([&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_FAKE); }, STATUS_IO_REPARSE_DATA_INVALID); }); test("Try to delete reparse point with wrong GUID", [&]() { exp_status([&]() { delete_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, wrong_guid); }, STATUS_REPARSE_ATTRIBUTE_CONFLICT); }); test("Delete reparse point with correct GUID", [&]() { delete_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL", fbi.FileAttributes); }); test("Try to delete reparse point again", [&]() { exp_status([&]() { delete_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid); }, STATUS_NOT_A_REPARSE_POINT); }); test("Try to query reparse point", [&]() { exp_status([&]() { query_reparse_point_guid(h.get()); }, STATUS_NOT_A_REPARSE_POINT); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\reparse15", FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_READ_EA, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Clear archive flag", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY", fbi.FileAttributes); }); test("Set with generic reparse tag", [&]() { static const string_view data = "hello"; set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid, span((uint8_t*)data.data(), data.size())); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT", fbi.FileAttributes); }); test("Query reparse point", [&]() { auto buf = query_reparse_point_guid(h.get()); auto& rgdb = *static_cast(buf); if (rgdb.ReparseTag != IO_REPARSE_TAG_FAKE) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE", rgdb.ReparseTag); if (memcmp(&rgdb.ReparseGuid, reparse_guid, sizeof(rgdb.ReparseGuid))) throw runtime_error("Returned GUID was not as expected"); auto sv = string_view((char*)rgdb.GenericReparseBuffer.DataBuffer, rgdb.ReparseDataLength); if (sv != "hello") throw runtime_error("Reparse point data was not as expected"); }); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != 0) throw formatted_error("EaSize was {:08x}, expected 0", feai.EaSize); }); // needs FILE_READ_ATTRIBUTES test("Query FileStatInformation", [&]() { auto fsi = query_information(h.get()); if (fsi.ReparseTag != IO_REPARSE_TAG_FAKE) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE", fsi.ReparseTag); }); // needs FILE_READ_EA as well test("Query FileStatLxInformation", [&]() { auto fsli = query_information(h.get()); if (fsli.ReparseTag != IO_REPARSE_TAG_FAKE) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE", fsli.ReparseTag); }); test("Query FileAttributeTagInformation", [&]() { auto fati = query_information(h.get()); if (fati.ReparseTag != IO_REPARSE_TAG_FAKE) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE", fati.ReparseTag); }); h.reset(); } test("Check directory entry (FILE_FULL_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse15", IO_REPARSE_TAG_FAKE); }); test("Check directory entry (FILE_ID_FULL_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse15", IO_REPARSE_TAG_FAKE); }); test("Check directory entry (FILE_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse15", IO_REPARSE_TAG_FAKE); }); test("Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse15", IO_REPARSE_TAG_FAKE); }); test("Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse15", IO_REPARSE_TAG_FAKE); }); test("Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)", [&]() { check_reparse_dirent(dir, u"reparse15", IO_REPARSE_TAG_FAKE); }); test("Open directory without FILE_OPEN_REPARSE_POINT", [&]() { exp_status([&]() { create_file(dir + u"\\reparse15", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_IO_REPARSE_TAG_NOT_HANDLED); }); test("Open directory with FILE_OPEN_REPARSE_POINT", [&]() { create_file(dir + u"\\reparse15", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED); }); test("Create file", [&]() { h = create_file(dir + u"\\reparse16", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try to set as symlink without permissions", [&]() { exp_status([&]() { set_symlink(h.get(), u"reparse1", u"reparse1", true); }, STATUS_ACCESS_DENIED); }); h.reset(); } test("Open file with FILE_WRITE_ATTRIBUTES", [&]() { h = create_file(dir + u"\\reparse16", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Set as symlink", [&]() { set_symlink(h.get(), u"reparse1", u"reparse1", true); }); test("Delete reparse point", [&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_SYMLINK); }); h.reset(); } test("Open file with FILE_WRITE_DATA", [&]() { h = create_file(dir + u"\\reparse16", FILE_WRITE_DATA, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Set as symlink", [&]() { set_symlink(h.get(), u"reparse1", u"reparse1", true); }); test("Delete reparse point", [&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_SYMLINK); }); h.reset(); } test("Create directory", [&]() { create_file(dir + u"\\reparse17", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create file within directory", [&]() { create_file(dir + u"\\reparse17\\file", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Open directory", [&]() { h = create_file(dir + u"\\reparse17", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | FILE_READ_EA, 0, 0, FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED); }); if (h) { test("Clear archive flag", [&]() { set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY", fbi.FileAttributes); }); // This works because the reparse tag has the "D bit" (0x10000000) set test("Create reparse point on non-empty directory", [&]() { static const string_view data = "hello"; set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT_DIR, span((uint8_t*)data.data(), data.size())); }); test("Query attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) throw formatted_error("FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT", fbi.FileAttributes); }); test("Query reparse point", [&]() { auto buf = query_reparse_point(h.get()); auto& rdb = *static_cast(buf); if (rdb.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT_DIR) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT_DIR", rdb.ReparseTag); auto sv = string_view((char*)rdb.GenericReparseBuffer.DataBuffer, rdb.ReparseDataLength); if (sv != "hello") throw runtime_error("Reparse point data was not as expected"); }); test("Query FileEaInformation", [&]() { auto feai = query_information(h.get()); if (feai.EaSize != 0) throw formatted_error("EaSize was {:08x}, expected 0", feai.EaSize); }); // needs FILE_READ_ATTRIBUTES test("Query FileStatInformation", [&]() { auto fsi = query_information(h.get()); if (fsi.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT_DIR) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT_DIR", fsi.ReparseTag); }); // needs FILE_READ_EA as well test("Query FileStatLxInformation", [&]() { auto fsli = query_information(h.get()); if (fsli.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT_DIR) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT_DIR", fsli.ReparseTag); }); test("Query FileAttributeTagInformation", [&]() { auto fati = query_information(h.get()); if (fati.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT_DIR) throw formatted_error("ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT_DIR", fati.ReparseTag); }); h.reset(); } // Succeeds rather than returning STATUS_IO_REPARSE_TAG_NOT_HANDLED, because of D bit test("Open directory without FILE_OPEN_REPARSE_POINT", [&]() { create_file(dir + u"\\reparse17", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); test("Create stream", [&]() { h = create_file(dir + u"\\reparse18:stream", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Set as symlink", [&]() { set_symlink(h.get(), u"reparse1", u"reparse1", true); }); test("Delete reparse point", [&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_SYMLINK); }); test("Try to set as mount point", [&]() { exp_status([&]() { set_mount_point(h.get(), u"reparse1", u"reparse1"); }, STATUS_NOT_A_DIRECTORY); }); test("Set with Microsoft reparse tag", [&]() { static const string_view data = "hello"; set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT, span((uint8_t*)data.data(), data.size())); }); test("Delete reparse point", [&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Set with generic reparse tag", [&]() { static const string_view data = "hello"; set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid, span((uint8_t*)data.data(), data.size())); }); h.reset(); } test("Open file without FILE_OPEN_REPARSE_POINT", [&]() { exp_status([&]() { create_file(dir + u"\\reparse18", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_IO_REPARSE_TAG_NOT_HANDLED); }); test("Open stream without FILE_OPEN_REPARSE_POINT", [&]() { exp_status([&]() { create_file(dir + u"\\reparse18:stream", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_IO_REPARSE_TAG_NOT_HANDLED); }); test("Create directory", [&]() { create_file(dir + u"\\reparse19", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create stream on directory", [&]() { h = create_file(dir + u"\\reparse19:stream", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Set as symlink", [&]() { set_symlink(h.get(), u"reparse1", u"reparse1", true); }); test("Delete reparse point", [&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_SYMLINK); }); test("Try to set as mount point", [&]() { exp_status([&]() { set_mount_point(h.get(), u"reparse1", u"reparse1"); }, STATUS_NOT_A_DIRECTORY); }); test("Set with Microsoft reparse tag", [&]() { static const string_view data = "hello"; set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT, span((uint8_t*)data.data(), data.size())); }); test("Delete reparse point", [&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Set with generic reparse tag", [&]() { static const string_view data = "hello"; set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid, span((uint8_t*)data.data(), data.size())); }); h.reset(); } test("Open directory without FILE_OPEN_REPARSE_POINT", [&]() { exp_status([&]() { create_file(dir + u"\\reparse19", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_IO_REPARSE_TAG_NOT_HANDLED); }); test("Open stream without FILE_OPEN_REPARSE_POINT", [&]() { exp_status([&]() { create_file(dir + u"\\reparse19:stream", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_IO_REPARSE_TAG_NOT_HANDLED); }); // disable SeCreateSymbolicLinkPrivilege disable_token_privileges(token); test("Create directory", [&]() { h = create_file(dir + u"\\reparse20", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Try to set as symlink without SeCreateSymbolicLinkPrivilege", [&]() { exp_status([&]() { set_symlink(h.get(), u"reparse1", u"reparse1", true); }, STATUS_PRIVILEGE_NOT_HELD); }); test("Set as mount point without SeCreateSymbolicLinkPrivilege", [&]() { set_mount_point(h.get(), u"reparse1", u"reparse1"); }); test("Delete reparse point", [&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_MOUNT_POINT); }); test("Set with Microsoft reparse tag", [&]() { static const string_view data = "hello"; set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT, span((uint8_t*)data.data(), data.size())); }); test("Delete reparse point", [&]() { delete_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT); }); test("Set with generic reparse tag", [&]() { static const string_view data = "hello"; set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid, span((uint8_t*)data.data(), data.size())); }); test("Delete reparse point", [&]() { delete_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid); }); h.reset(); } // FIXME - FSCTL_SET_REPARSE_POINT_EX } ================================================ FILE: src/tests/security.cpp ================================================ #include "test.h" #include using namespace std; #ifdef _MSC_VER #define ThreadImpersonationToken ((THREADINFOCLASS)5) #endif // S-1-1-0 static const uint8_t sid_everyone[] = { 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 }; // S-1-5-21-2463132441-2848149277-1773138504-1001 static const uint8_t sid_test[] = { 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x15, 0x00, 0x00, 0x00, 0x19, 0x6b, 0xd0, 0x92, 0x1d, 0x4f, 0xc3, 0xa9, 0x48, 0xf2, 0xaf, 0x69, 0xe9, 0x03, 0x00, 0x00 }; // S-1-5-21-2463132441-2848149277-1773138504-2001 static const uint8_t sid_test2[] = { 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x15, 0x00, 0x00, 0x00, 0x19, 0x6b, 0xd0, 0x92, 0x1d, 0x4f, 0xc3, 0xa9, 0x48, 0xf2, 0xaf, 0x69, 0xd1, 0x07, 0x00, 0x00 }; // S-1-16-12288 static const uint8_t sid_high[] = { 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x00 }; // S-1-16-8192 static const uint8_t sid_medium[] = { 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x00 }; static unique_handle create_file_sd(u16string_view path, ACCESS_MASK access, ULONG atts, ULONG share, ULONG dispo, ULONG options, ULONG_PTR exp_info, const SECURITY_DESCRIPTOR& sd) { NTSTATUS Status; HANDLE h; IO_STATUS_BLOCK iosb; UNICODE_STRING us; OBJECT_ATTRIBUTES oa; memset(&oa, 0, sizeof(oa)); oa.Length = sizeof(oa); oa.RootDirectory = nullptr; us.Length = us.MaximumLength = path.length() * sizeof(char16_t); us.Buffer = (WCHAR*)path.data(); oa.ObjectName = &us; oa.Attributes = OBJ_CASE_INSENSITIVE; oa.SecurityDescriptor = (void*)&sd; oa.SecurityQualityOfService = nullptr; iosb.Information = 0xdeadbeef; Status = NtCreateFile(&h, access, &oa, &iosb, nullptr, atts, share, dispo, options, nullptr, 0); if (Status != STATUS_SUCCESS) { if (NT_SUCCESS(Status)) // STATUS_OPLOCK_BREAK_IN_PROGRESS etc. NtClose(h); throw ntstatus_error(Status); } if (iosb.Information != exp_info) throw formatted_error("iosb.Information was {}, expected {}", iosb.Information, exp_info); return unique_handle(h); } static unique_handle create_file_with_acl(u16string_view path, ACCESS_MASK access, ULONG atts, ULONG share, ULONG dispo, ULONG options, ULONG_PTR exp_info, ACCESS_MASK ace_access, uint8_t ace_flags) { SECURITY_DESCRIPTOR sd; array aclbuf; if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) throw formatted_error("InitializeSecurityDescriptor failed (error {})", GetLastError()); auto& acl = *(ACL*)aclbuf.data(); if (!InitializeAcl(&acl, aclbuf.size(), ACL_REVISION)) throw formatted_error("InitializeAcl failed (error {})", GetLastError()); acl.AceCount = 1; auto& ace = *(ACCESS_ALLOWED_ACE*)((uint8_t*)aclbuf.data() + sizeof(ACL)); ace.Header.AceType = ACCESS_ALLOWED_ACE_TYPE; ace.Header.AceFlags = ace_flags; ace.Header.AceSize = offsetof(ACCESS_ALLOWED_ACE, SidStart) + sizeof(sid_everyone); ace.Mask = ace_access; memcpy(&ace.SidStart, sid_everyone, sizeof(sid_everyone)); if (!SetSecurityDescriptorDacl(&sd, true, &acl, false)) throw formatted_error("SetSecurityDescriptorDacl failed (error {})", GetLastError()); return create_file_sd(path, access, atts, share, dispo, options, exp_info, sd); } void set_dacl(HANDLE h, ACCESS_MASK access) { NTSTATUS Status; SECURITY_DESCRIPTOR sd; array aclbuf; if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) throw formatted_error("InitializeSecurityDescriptor failed (error {})", GetLastError()); auto& acl = *(ACL*)aclbuf.data(); if (!InitializeAcl(&acl, aclbuf.size(), ACL_REVISION)) throw formatted_error("InitializeAcl failed (error {})", GetLastError()); if (access != 0) { acl.AceCount = 1; auto& ace = *(ACCESS_ALLOWED_ACE*)((uint8_t*)aclbuf.data() + sizeof(ACL)); ace.Header.AceType = ACCESS_ALLOWED_ACE_TYPE; ace.Header.AceFlags = 0; ace.Header.AceSize = offsetof(ACCESS_ALLOWED_ACE, SidStart) + sizeof(sid_everyone); ace.Mask = access; memcpy(&ace.SidStart, sid_everyone, sizeof(sid_everyone)); } if (!SetSecurityDescriptorDacl(&sd, true, &acl, false)) throw formatted_error("SetSecurityDescriptorDacl failed (error {})", GetLastError()); Status = NtSetSecurityObject(h, DACL_SECURITY_INFORMATION, &sd); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static vector> get_acl(HANDLE h, unsigned int type) { NTSTATUS Status; ULONG needed = 0; vector buf; vector> ret; Status = NtQuerySecurityObject(h, type, nullptr, 0, &needed); if (Status != STATUS_BUFFER_TOO_SMALL) throw ntstatus_error(Status); buf.resize(needed); Status = NtQuerySecurityObject(h, type, buf.data(), buf.size(), &needed); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (buf.size() < sizeof(SECURITY_DESCRIPTOR_RELATIVE)) throw formatted_error("SD was {} bytes, expected at least {}", buf.size(), sizeof(SECURITY_DESCRIPTOR_RELATIVE)); auto& sd = *(SECURITY_DESCRIPTOR_RELATIVE*)buf.data(); if (sd.Revision != 1) throw formatted_error("SD revision was {}, expected 1", sd.Revision); auto off = type == DACL_SECURITY_INFORMATION ? sd.Dacl : sd.Sacl; if (off == 0) return {}; if (off + sizeof(ACL) > buf.size()) throw runtime_error("ACL extended beyond end of SD"); auto& acl = *(ACL*)(buf.data() + off); if (acl.AclRevision != ACL_REVISION) throw formatted_error("ACL revision was {}, expected {}", acl.AclRevision, ACL_REVISION); if (acl.AclSize < sizeof(ACL)) throw formatted_error("ACL size was {}, expected at least {}", acl.AclSize, sizeof(ACL)); ret.resize(acl.AceCount); auto aclsp = span((uint8_t*)&acl + sizeof(ACL), acl.AclSize - sizeof(ACL)); for (unsigned int i = 0; i < acl.AceCount; i++) { auto& ace = *(ACE_HEADER*)aclsp.data(); if (aclsp.size() < sizeof(ACE_HEADER)) throw formatted_error("Not enough bytes left for ACE ({} < {})", aclsp.size(), sizeof(ACE_HEADER)); if (aclsp.size() < ace.AceSize) throw formatted_error("ACE overflowed end of SD ({} < {})", aclsp.size(), ace.AceSize); auto& b = ret[i].buf; b.resize(ace.AceSize); memcpy(b.data(), &ace, ace.AceSize); aclsp = aclsp.subspan(ace.AceSize); } return ret; } static string sid_to_string(span sid) { string s; auto& ss = *(SID*)sid.data(); if (sid.size() < offsetof(SID, SubAuthority) || ss.Revision != SID_REVISION || sid.size() < offsetof(SID, SubAuthority) + (ss.SubAuthorityCount * sizeof(ULONG))) { for (auto b : sid) { if (!s.empty()) s += " "; s += std::format("{:02x}", b); } return "Malformed SID (" + s + ")"; } uint64_t auth; auth = (uint64_t)sid[2] << 40; auth |= (uint64_t)sid[3] << 32; auth |= (uint64_t)sid[4] << 24; auth |= (uint64_t)sid[5] << 16; auth |= (uint64_t)sid[6] << 8; auth |= sid[7]; s = std::format("S-1-{}", auth); auto sub = span(ss.SubAuthority, ss.SubAuthorityCount); for (auto n : sub) { s += std::format("-{}", n); } return s; } static bool compare_sid(span sid1, span sid2) { if (sid1.size() < offsetof(SID, SubAuthority) || sid2.size() < offsetof(SID, SubAuthority)) throw runtime_error("Malformed SID"); auto& ss1 = *(SID*)sid1.data(); auto& ss2 = *(SID*)sid2.data(); if (ss1.Revision != 1 || ss2.Revision != 1) throw runtime_error("Unknown SID revision"); auto len1 = offsetof(SID, SubAuthority) + (ss1.SubAuthorityCount * sizeof(ULONG)); auto len2 = offsetof(SID, SubAuthority) + (ss2.SubAuthorityCount * sizeof(ULONG)); if (len1 != len2) return false; return !memcmp(sid1.data(), sid2.data(), len1); } static void set_owner(HANDLE h, span sid) { NTSTATUS Status; SECURITY_DESCRIPTOR sd; if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) throw formatted_error("InitializeSecurityDescriptor failed (error {})", GetLastError()); if (!SetSecurityDescriptorOwner(&sd, (PSID)sid.data(), false)) throw formatted_error("SetSecurityDescriptorOwner failed (error {})", GetLastError()); Status = NtSetSecurityObject(h, OWNER_SECURITY_INFORMATION, &sd); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static vector get_owner(HANDLE h) { NTSTATUS Status; ULONG needed = 0; vector buf; Status = NtQuerySecurityObject(h, OWNER_SECURITY_INFORMATION, nullptr, 0, &needed); if (Status != STATUS_BUFFER_TOO_SMALL) throw ntstatus_error(Status); buf.resize(needed); Status = NtQuerySecurityObject(h, OWNER_SECURITY_INFORMATION, buf.data(), buf.size(), &needed); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (buf.size() < sizeof(SECURITY_DESCRIPTOR_RELATIVE)) throw formatted_error("SD was {} bytes, expected at least {}", buf.size(), sizeof(SECURITY_DESCRIPTOR_RELATIVE)); auto& sd = *(SECURITY_DESCRIPTOR_RELATIVE*)buf.data(); if (sd.Revision != 1) throw formatted_error("SD revision was {}, expected 1", sd.Revision); if (sd.Owner == 0) throw runtime_error("No owner returned"); if (sd.Owner + offsetof(SID, SubAuthority) > buf.size()) throw runtime_error("SID extended beyond end of SD"); auto& sid = *(SID*)(buf.data() + sd.Owner); if (sid.Revision != SID_REVISION) throw formatted_error("SID revision was {}, expected {}", sid.Revision, SID_REVISION); auto sp = span(buf.data() + sd.Owner, offsetof(SID, SubAuthority) + (sizeof(ULONG) * sid.SubAuthorityCount)); vector ret; ret.resize(sp.size()); memcpy(ret.data(), sp.data(), sp.size()); return ret; } static void set_group(HANDLE h, span sid) { NTSTATUS Status; SECURITY_DESCRIPTOR sd; if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) throw formatted_error("InitializeSecurityDescriptor failed (error {})", GetLastError()); if (!SetSecurityDescriptorGroup(&sd, (PSID)sid.data(), false)) throw formatted_error("SetSecurityDescriptorGroup failed (error {})", GetLastError()); Status = NtSetSecurityObject(h, GROUP_SECURITY_INFORMATION, &sd); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static vector get_group(HANDLE h) { NTSTATUS Status; ULONG needed = 0; vector buf; Status = NtQuerySecurityObject(h, GROUP_SECURITY_INFORMATION, nullptr, 0, &needed); if (Status != STATUS_BUFFER_TOO_SMALL) throw ntstatus_error(Status); buf.resize(needed); Status = NtQuerySecurityObject(h, GROUP_SECURITY_INFORMATION, buf.data(), buf.size(), &needed); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (buf.size() < sizeof(SECURITY_DESCRIPTOR_RELATIVE)) throw formatted_error("SD was {} bytes, expected at least {}", buf.size(), sizeof(SECURITY_DESCRIPTOR_RELATIVE)); auto& sd = *(SECURITY_DESCRIPTOR_RELATIVE*)buf.data(); if (sd.Revision != 1) throw formatted_error("SD revision was {}, expected 1", sd.Revision); if (sd.Group == 0) throw runtime_error("No group returned"); if (sd.Group + offsetof(SID, SubAuthority) > buf.size()) throw runtime_error("SID extended beyond end of SD"); auto& sid = *(SID*)(buf.data() + sd.Group); if (sid.Revision != SID_REVISION) throw formatted_error("SID revision was {}, expected {}", sid.Revision, SID_REVISION); auto sp = span(buf.data() + sd.Group, offsetof(SID, SubAuthority) + (sizeof(ULONG) * sid.SubAuthorityCount)); vector ret; ret.resize(sp.size()); memcpy(ret.data(), sp.data(), sp.size()); return ret; } template static void set_audit(HANDLE h, ACCESS_MASK access, span sid) { NTSTATUS Status; SECURITY_DESCRIPTOR sd; array aclbuf; if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) throw formatted_error("InitializeSecurityDescriptor failed (error {})", GetLastError()); auto& acl = *(ACL*)aclbuf.data(); if (!InitializeAcl(&acl, aclbuf.size(), ACL_REVISION)) throw formatted_error("InitializeAcl failed (error {})", GetLastError()); if (access != 0) { acl.AceCount = 1; auto& ace = *(SYSTEM_AUDIT_ACE*)((uint8_t*)aclbuf.data() + sizeof(ACL)); ace.Header.AceType = SYSTEM_AUDIT_ACE_TYPE; ace.Header.AceFlags = 0; ace.Header.AceSize = offsetof(SYSTEM_AUDIT_ACE, SidStart) + sid.size(); ace.Mask = access; memcpy(&ace.SidStart, sid.data(), sid.size()); } if (!SetSecurityDescriptorSacl(&sd, true, &acl, false)) throw formatted_error("SetSecurityDescriptorSacl failed (error {})", GetLastError()); Status = NtSetSecurityObject(h, SACL_SECURITY_INFORMATION, &sd); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } template static void set_mandatory_access(HANDLE h, ACCESS_MASK access, span sid) { NTSTATUS Status; SECURITY_DESCRIPTOR sd; array aclbuf; if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) throw formatted_error("InitializeSecurityDescriptor failed (error {})", GetLastError()); auto& acl = *(ACL*)aclbuf.data(); if (!InitializeAcl(&acl, aclbuf.size(), ACL_REVISION)) throw formatted_error("InitializeAcl failed (error {})", GetLastError()); if (access != 0) { acl.AceCount = 1; auto& ace = *(SYSTEM_MANDATORY_LABEL_ACE*)((uint8_t*)aclbuf.data() + sizeof(ACL)); ace.Header.AceType = SYSTEM_MANDATORY_LABEL_ACE_TYPE; ace.Header.AceFlags = 0; ace.Header.AceSize = offsetof(SYSTEM_MANDATORY_LABEL_ACE, SidStart) + sid.size(); ace.Mask = access; memcpy(&ace.SidStart, sid.data(), sid.size()); } if (!SetSecurityDescriptorSacl(&sd, true, &acl, false)) throw formatted_error("SetSecurityDescriptorSacl failed (error {})", GetLastError()); Status = NtSetSecurityObject(h, LABEL_SECURITY_INFORMATION, &sd); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static unique_handle duplicate_token(HANDLE token) { NTSTATUS Status; OBJECT_ATTRIBUTES oa; HANDLE h; SECURITY_QUALITY_OF_SERVICE qos; memset(&qos, 0, sizeof(qos)); qos.Length = sizeof(qos); qos.ImpersonationLevel = SecurityImpersonation; memset(&oa, 0, sizeof(oa)); oa.Length = sizeof(oa); oa.SecurityQualityOfService = &qos; Status = NtDuplicateToken(token, MAXIMUM_ALLOWED, &oa, false, TokenImpersonation, &h); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); return unique_handle{h}; } static void adjust_token_level(HANDLE token, const void* sid) { TOKEN_MANDATORY_LABEL label; NTSTATUS Status; label.Label.Sid = (PSID)sid; label.Label.Attributes = 0; Status = NtSetInformationToken(token, TokenIntegrityLevel, &label, sizeof(label)); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } static void set_thread_token(HANDLE token) { NTSTATUS Status; Status = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &token, sizeof(token)); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } void test_security(HANDLE token, const u16string& dir) { unique_handle h, medium_token; test("Create file", [&]() { h = create_file(dir + u"\\sec1", GENERIC_READ, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Query FileAccessInformation", [&]() { auto fai = query_information(h.get()); ACCESS_MASK exp = SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_READ_EA | FILE_READ_DATA; if (fai.AccessFlags != exp) throw formatted_error("AccessFlags was {:x}, expected {:x}", fai.AccessFlags, exp); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\sec1", GENERIC_WRITE, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Query FileAccessInformation", [&]() { auto fai = query_information(h.get()); ACCESS_MASK exp = SYNCHRONIZE | READ_CONTROL | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | FILE_WRITE_DATA; if (fai.AccessFlags != exp) throw formatted_error("AccessFlags was {:x}, expected {:x}", fai.AccessFlags, exp); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\sec1", GENERIC_EXECUTE, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Query FileAccessInformation", [&]() { auto fai = query_information(h.get()); ACCESS_MASK exp = SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_EXECUTE; if (fai.AccessFlags != exp) throw formatted_error("AccessFlags was {:x}, expected {:x}", fai.AccessFlags, exp); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\sec1", READ_CONTROL | WRITE_DAC, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { ACCESS_MASK access = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD | FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA | FILE_WRITE_DATA | FILE_READ_DATA; test("Set DACL to maximum for Everyone", [&]() { set_dacl(h.get(), access); }); test("Query DACL", [&]() { auto items = get_acl(h.get(), DACL_SECURITY_INFORMATION); if (items.size() != 1) throw formatted_error("{} items returned, expected 1", items.size()); auto& ace = *static_cast(items.front()); if (ace.AceType != ACCESS_ALLOWED_ACE_TYPE) throw formatted_error("ACE type was {}, expected ACCESS_ALLOWED_ACE_TYPE", ace.AceType); if (ace.AceFlags != 0) throw formatted_error("AceFlags was {:x}, expected 0", ace.AceFlags); auto& aaa = *reinterpret_cast(&ace); if (aaa.Mask != access) throw formatted_error("Mask was {:x}, expected {:x}", aaa.Mask, access); auto sid = span((uint8_t*)&aaa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart)); if (!compare_sid(sid, sid_everyone)) throw formatted_error("SID was {}, expected {}", sid_to_string(sid), sid_to_string(sid_everyone)); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\sec1", READ_CONTROL | WRITE_OWNER, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Try to set owner without SeRestorePrivilege", [&]() { exp_status([&]() { set_owner(h.get(), sid_test); }, STATUS_INVALID_OWNER); }); test("Set group", [&]() { set_group(h.get(), sid_test2); }); test("Query group", [&]() { auto sid = get_group(h.get()); if (!compare_sid(sid, sid_test2)) throw formatted_error("SID was {}, expected {}", sid_to_string(sid), sid_to_string(sid_test2)); }); h.reset(); } test("Add SeRestorePrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_RESTORE_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Open file", [&]() { h = create_file(dir + u"\\sec1", READ_CONTROL | WRITE_OWNER, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Set owner", [&]() { set_owner(h.get(), sid_test); }); test("Query owner", [&]() { auto sid = get_owner(h.get()); if (!compare_sid(sid, sid_test)) throw formatted_error("SID was {}, expected {}", sid_to_string(sid), sid_to_string(sid_test)); }); h.reset(); } disable_token_privileges(token); test("Try to open file with ACCESS_SYSTEM_SECURITY without SeSecurityPrivilege", [&]() { exp_status([&]() { create_file(dir + u"\\sec1", ACCESS_SYSTEM_SECURITY, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_PRIVILEGE_NOT_HELD); }); test("Add SeSecurityPrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_SECURITY_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Open file", [&]() { h = create_file(dir + u"\\sec1", ACCESS_SYSTEM_SECURITY, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Set audit", [&]() { set_audit(h.get(), SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG, span(sid_everyone)); }); test("Query SACL", [&]() { auto items = get_acl(h.get(), SACL_SECURITY_INFORMATION); if (items.size() != 1) throw formatted_error("{} items returned, expected 1", items.size()); auto& ace = *static_cast(items.front()); if (ace.AceType != SYSTEM_AUDIT_ACE_TYPE) throw formatted_error("ACE type was {}, expected SYSTEM_AUDIT_ACE_TYPE", ace.AceType); if (ace.AceFlags != 0) throw formatted_error("AceFlags was {:x}, expected 0", ace.AceFlags); auto& saa = *reinterpret_cast(&ace); if (saa.Mask != (SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG)) throw formatted_error("Mask was {:x}, expected {:x}", saa.Mask, SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG); auto sid = span((uint8_t*)&saa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart)); if (!compare_sid(sid, sid_everyone)) throw formatted_error("SID was {}, expected {}", sid_to_string(sid), sid_to_string(sid_everyone)); }); h.reset(); } disable_token_privileges(token); test("Open file", [&]() { h = create_file(dir + u"\\sec1", READ_CONTROL | WRITE_OWNER, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Set mandatory access label", [&]() { set_mandatory_access(h.get(), SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, span(sid_high)); }); test("Query label", [&]() { auto items = get_acl(h.get(), LABEL_SECURITY_INFORMATION); if (items.size() != 1) throw formatted_error("{} items returned, expected 1", items.size()); auto& ace = *static_cast(items.front()); if (ace.AceType != SYSTEM_MANDATORY_LABEL_ACE_TYPE) throw formatted_error("ACE type was {}, expected SYSTEM_MANDATORY_LABEL_ACE_TYPE", ace.AceType); if (ace.AceFlags != 0) throw formatted_error("AceFlags was {:x}, expected 0", ace.AceFlags); auto& smla = *reinterpret_cast(&ace); if (smla.Mask != SYSTEM_MANDATORY_LABEL_NO_WRITE_UP) throw formatted_error("Mask was {:x}, expected {:x}", smla.Mask, SYSTEM_MANDATORY_LABEL_NO_WRITE_UP); auto sid = span((uint8_t*)&smla.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart)); if (!compare_sid(sid, sid_high)) throw formatted_error("SID was {}, expected {}", sid_to_string(sid), sid_to_string(sid_high)); }); h.reset(); } test("Add SeSecurityPrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_SECURITY_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Open file", [&]() { h = create_file(dir + u"\\sec1", ACCESS_SYSTEM_SECURITY, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Check SACL still there", [&]() { auto items = get_acl(h.get(), SACL_SECURITY_INFORMATION); if (items.size() != 1) throw formatted_error("{} items returned, expected 1", items.size()); auto& ace = *static_cast(items.front()); if (ace.AceType != SYSTEM_AUDIT_ACE_TYPE) throw formatted_error("ACE type was {}, expected SYSTEM_AUDIT_ACE_TYPE", ace.AceType); if (ace.AceFlags != 0) throw formatted_error("AceFlags was {:x}, expected 0", ace.AceFlags); auto& saa = *reinterpret_cast(&ace); if (saa.Mask != (SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG)) throw formatted_error("Mask was {:x}, expected {:x}", saa.Mask, SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG); auto sid = span((uint8_t*)&saa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart)); if (!compare_sid(sid, sid_everyone)) throw formatted_error("SID was {}, expected {}", sid_to_string(sid), sid_to_string(sid_everyone)); }); h.reset(); } disable_token_privileges(token); test("Duplicate token", [&]() { medium_token = duplicate_token(token); }); if (medium_token) { test("Adjust token label", [&]() { adjust_token_level(medium_token.get(), sid_medium); }); test("Switch to new token", [&]() { set_thread_token(medium_token.get()); }); } test("Try to open file for writing with medium label", [&]() { exp_status([&]() { create_file(dir + u"\\sec1", FILE_WRITE_DATA, 0, 0, FILE_OPEN, 0, FILE_OPENED); }, STATUS_ACCESS_DENIED); }); test("Open file with MAXIMUM_ALLOWED", [&]() { h = create_file(dir + u"\\sec1", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Query FileAccessInformation", [&]() { auto fai = query_information(h.get()); ACCESS_MASK exp = SYNCHRONIZE | READ_CONTROL | DELETE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | FILE_READ_EA | FILE_READ_DATA; if (fai.AccessFlags != exp) throw formatted_error("AccessFlags was {:x}, expected {:x}", fai.AccessFlags, exp); }); h.reset(); } if (medium_token) { test("Switch back to old token", [&]() { set_thread_token(nullptr); }); medium_token.reset(); } test("Create file with SD", [&]() { h = create_file_with_acl(dir + u"\\sec2", READ_CONTROL, 0, 0, FILE_CREATE, 0, FILE_CREATED, FILE_READ_DATA, 0); }); if (h) { test("Query FileAccessInformation", [&]() { auto fai = query_information(h.get()); ACCESS_MASK exp = READ_CONTROL; if (fai.AccessFlags != exp) throw formatted_error("AccessFlags was {:x}, expected {:x}", fai.AccessFlags, exp); }); test("Query DACL", [&]() { auto items = get_acl(h.get(), DACL_SECURITY_INFORMATION); if (items.size() != 1) throw formatted_error("{} items returned, expected 1", items.size()); auto& ace = *static_cast(items.front()); if (ace.AceType != ACCESS_ALLOWED_ACE_TYPE) throw formatted_error("ACE type was {}, expected ACCESS_ALLOWED_ACE_TYPE", ace.AceType); if (ace.AceFlags != 0) throw formatted_error("AceFlags was {:x}, expected 0", ace.AceFlags); auto& aaa = *reinterpret_cast(&ace); if (aaa.Mask != FILE_READ_DATA) throw formatted_error("Mask was {:x}, expected FILE_READ_DATA", aaa.Mask); auto sid = span((uint8_t*)&aaa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart)); if (!compare_sid(sid, sid_everyone)) throw formatted_error("SID was {}, expected {}", sid_to_string(sid), sid_to_string(sid_everyone)); }); h.reset(); } test("Try to create file with other user as owner", [&]() { SECURITY_DESCRIPTOR sd; if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) throw formatted_error("InitializeSecurityDescriptor failed (error {})", GetLastError()); if (!SetSecurityDescriptorOwner(&sd, (PSID)sid_test, false)) throw formatted_error("SetSecurityDescriptorOwner failed (error {})", GetLastError()); exp_status([&]() { create_file_sd(dir + u"\\sec3", READ_CONTROL, 0, 0, FILE_CREATE, 0, FILE_CREATED, sd); }, STATUS_INVALID_OWNER); }); test("Create directory with OBJECT_INHERIT_ACE", [&]() { create_file_with_acl(dir + u"\\sec4", READ_CONTROL, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED, FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY, OBJECT_INHERIT_ACE); }); // READ_CONTROL gets given because we're the owner test("Create file", [&]() { h = create_file(dir + u"\\sec4\\file", READ_CONTROL, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Query DACL", [&]() { auto items = get_acl(h.get(), DACL_SECURITY_INFORMATION); if (items.size() != 1) throw formatted_error("{} items returned, expected 1", items.size()); auto& ace = *static_cast(items.front()); if (ace.AceType != ACCESS_ALLOWED_ACE_TYPE) throw formatted_error("ACE type was {}, expected ACCESS_ALLOWED_ACE_TYPE", ace.AceType); if (ace.AceFlags != 0) throw formatted_error("AceFlags was {:x}, expected 0", ace.AceFlags); auto& aaa = *reinterpret_cast(&ace); if (aaa.Mask != (FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY)) throw formatted_error("Mask was {:x}, expected FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY", aaa.Mask); auto sid = span((uint8_t*)&aaa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart)); if (!compare_sid(sid, sid_everyone)) throw formatted_error("SID was {}, expected {}", sid_to_string(sid), sid_to_string(sid_everyone)); }); h.reset(); } test("Create subdirectory", [&]() { h = create_file(dir + u"\\sec4\\dir", READ_CONTROL, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Query DACL", [&]() { auto items = get_acl(h.get(), DACL_SECURITY_INFORMATION); if (items.size() != 1) throw formatted_error("{} items returned, expected 1", items.size()); auto& ace = *static_cast(items.front()); if (ace.AceType != ACCESS_ALLOWED_ACE_TYPE) throw formatted_error("ACE type was {}, expected ACCESS_ALLOWED_ACE_TYPE", ace.AceType); if (ace.AceFlags != (INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE)) throw formatted_error("AceFlags was {:x}, expected INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE", ace.AceFlags); auto& aaa = *reinterpret_cast(&ace); if (aaa.Mask != (FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY)) throw formatted_error("Mask was {:x}, expected FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY", aaa.Mask); auto sid = span((uint8_t*)&aaa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart)); if (!compare_sid(sid, sid_everyone)) throw formatted_error("SID was {}, expected {}", sid_to_string(sid), sid_to_string(sid_everyone)); }); h.reset(); } test("Create directory with CONTAINER_INHERIT_ACE", [&]() { create_file_with_acl(dir + u"\\sec5", READ_CONTROL, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED, FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY, OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE); }); test("Create file", [&]() { h = create_file(dir + u"\\sec5\\file", READ_CONTROL, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Query DACL", [&]() { auto items = get_acl(h.get(), DACL_SECURITY_INFORMATION); if (items.size() != 1) throw formatted_error("{} items returned, expected 1", items.size()); auto& ace = *static_cast(items.front()); if (ace.AceType != ACCESS_ALLOWED_ACE_TYPE) throw formatted_error("ACE type was {}, expected ACCESS_ALLOWED_ACE_TYPE", ace.AceType); if (ace.AceFlags != 0) throw formatted_error("AceFlags was {:x}, expected 0", ace.AceFlags); auto& aaa = *reinterpret_cast(&ace); if (aaa.Mask != (FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY)) throw formatted_error("Mask was {:x}, expected FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY", aaa.Mask); auto sid = span((uint8_t*)&aaa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart)); if (!compare_sid(sid, sid_everyone)) throw formatted_error("SID was {}, expected {}", sid_to_string(sid), sid_to_string(sid_everyone)); }); h.reset(); } test("Create subdirectory", [&]() { h = create_file(dir + u"\\sec5\\dir", READ_CONTROL, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Query DACL", [&]() { auto items = get_acl(h.get(), DACL_SECURITY_INFORMATION); if (items.size() != 1) throw formatted_error("{} items returned, expected 1", items.size()); auto& ace = *static_cast(items.front()); if (ace.AceType != ACCESS_ALLOWED_ACE_TYPE) throw formatted_error("ACE type was {}, expected ACCESS_ALLOWED_ACE_TYPE", ace.AceType); if (ace.AceFlags != (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE)) throw formatted_error("AceFlags was {:x}, expected OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE", ace.AceFlags); auto& aaa = *reinterpret_cast(&ace); if (aaa.Mask != (FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY)) throw formatted_error("Mask was {:x}, expected FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY", aaa.Mask); auto sid = span((uint8_t*)&aaa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart)); if (!compare_sid(sid, sid_everyone)) throw formatted_error("SID was {}, expected {}", sid_to_string(sid), sid_to_string(sid_everyone)); }); h.reset(); } test("Create directory without FILE_TRAVERSE", [&]() { create_file_with_acl(dir + u"\\sec6", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED, FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | WRITE_DAC, 0); }); test("Try to create file within directory", [&]() { exp_status([&]() { create_file(dir + u"\\sec6\\file", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, STATUS_ACCESS_DENIED); }); test("Open directory", [&]() { h = create_file(dir + u"\\sec6", WRITE_DAC, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Add FILE_TRAVERSE", [&]() { set_dacl(h.get(), FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | WRITE_DAC); }); h.reset(); } test("Create file within directory", [&]() { create_file(dir + u"\\sec6\\file", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); test("Open directory", [&]() { h = create_file(dir + u"\\sec6", WRITE_DAC, 0, 0, FILE_OPEN, 0, FILE_OPENED); }); if (h) { test("Remove FILE_TRAVERSE", [&]() { set_dacl(h.get(), FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | WRITE_DAC); }); h.reset(); } test("Add SeChangeNotifyPrivilege to token", [&]() { LUID_AND_ATTRIBUTES laa; laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE; laa.Luid.HighPart = 0; laa.Attributes = SE_PRIVILEGE_ENABLED; adjust_token_privileges(token, laa); }); test("Create another file within directory", [&]() { create_file(dir + u"\\sec6\\file2", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); disable_token_privileges(token); test("Create file", [&]() { h = create_file(dir + u"\\sec7", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { test("Try to query owner", [&]() { exp_status([&]() { get_owner(h.get()); }, STATUS_ACCESS_DENIED); }); test("Try to query group", [&]() { exp_status([&]() { get_group(h.get()); }, STATUS_ACCESS_DENIED); }); test("Try to query SACL", [&]() { exp_status([&]() { get_acl(h.get(), SACL_SECURITY_INFORMATION); }, STATUS_ACCESS_DENIED); }); test("Try to query DACL", [&]() { exp_status([&]() { get_acl(h.get(), DACL_SECURITY_INFORMATION); }, STATUS_ACCESS_DENIED); }); test("Try to query label", [&]() { exp_status([&]() { get_acl(h.get(), LABEL_SECURITY_INFORMATION); }, STATUS_ACCESS_DENIED); }); test("Try to set owner", [&]() { exp_status([&]() { set_owner(h.get(), sid_test); }, STATUS_ACCESS_DENIED); }); test("Try to set group", [&]() { exp_status([&]() { set_group(h.get(), sid_test); }, STATUS_ACCESS_DENIED); }); test("Try to set SACL", [&]() { exp_status([&]() { set_audit(h.get(), SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG, span(sid_everyone)); }, STATUS_ACCESS_DENIED); }); test("Try to set DACL", [&]() { exp_status([&]() { set_dacl(h.get(), FILE_READ_DATA); }, STATUS_ACCESS_DENIED); }); test("Try to set label", [&]() { exp_status([&]() { set_mandatory_access(h.get(), SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, span(sid_high)); }, STATUS_ACCESS_DENIED); }); h.reset(); } } ================================================ FILE: src/tests/streams.cpp ================================================ #include "test.h" using namespace std; #ifdef _MSC_VER typedef struct _FILE_STREAM_INFORMATION { ULONG NextEntryOffset; ULONG StreamNameLength; LARGE_INTEGER StreamSize; LARGE_INTEGER StreamAllocationSize; WCHAR StreamName[1]; } FILE_STREAM_INFORMATION, *PFILE_STREAM_INFORMATION; #endif static vector> query_streams(HANDLE h) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf(4096); vector> ret; while (true) { Status = NtQueryInformationFile(h, &iosb, buf.data(), buf.size(), FileStreamInformation); if (Status == STATUS_BUFFER_OVERFLOW) { buf.resize(buf.size() + 4096); continue; } if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); break; } auto ptr = (FILE_STREAM_INFORMATION*)buf.data(); do { varbuf item; item.buf.resize(offsetof(FILE_STREAM_INFORMATION, StreamName) + ptr->StreamNameLength); memcpy(item.buf.data(), ptr, item.buf.size()); ret.emplace_back(item); if (ptr->NextEntryOffset == 0) break; ptr = (FILE_STREAM_INFORMATION*)((uint8_t*)ptr + ptr->NextEntryOffset); } while (true); return ret; } void test_streams(const u16string& dir) { unique_handle h; int64_t fileid; test("Create file", [&]() { h = create_file(dir + u"\\stream1", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); fileid = fii.IndexNumber.QuadPart; }); test("Query streams", [&]() { auto items = query_streams(h.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& fsi = *static_cast(items.front()); if (fsi.StreamSize.QuadPart != 0) throw formatted_error("StreamSize was {}, expected 0", fsi.StreamSize.QuadPart); if (fsi.StreamAllocationSize.QuadPart != 0) throw formatted_error("StreamAllocationSize was {}, expected 0", fsi.StreamAllocationSize.QuadPart); auto name = u16string_view((char16_t*)fsi.StreamName, fsi.StreamNameLength / sizeof(char16_t)); if (name != u"::$DATA") throw formatted_error("StreamName was {}, expected ::$DATA", u16string_to_string(name)); }); test("Check FILE_STANDARD_INFORMATION_EX", [&]() { auto fsix = query_information(h.get()); if (fsix.AlternateStream) throw runtime_error("AlternateStream was true, expected false"); }); h.reset(); } test("Create stream", [&]() { h = create_file(dir + u"\\stream1:stream", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Get file ID", [&]() { auto fii = query_information(h.get()); if (fii.IndexNumber.QuadPart != fileid) throw runtime_error("File IDs did not match."); }); test("Check FILE_STANDARD_INFORMATION_EX", [&]() { auto fsix = query_information(h.get()); if (!fsix.AlternateStream) throw runtime_error("AlternateStream was false, expected true"); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\stream1", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); if (h) { test("Query streams", [&]() { auto items = query_streams(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2", items.size()); auto& fsi1 = *static_cast(items[0]); if (fsi1.StreamSize.QuadPart != 0) throw formatted_error("StreamSize was {}, expected 0", fsi1.StreamSize.QuadPart); if (fsi1.StreamAllocationSize.QuadPart != 0) throw formatted_error("StreamAllocationSize was {}, expected 0", fsi1.StreamAllocationSize.QuadPart); auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t)); if (name1 != u"::$DATA") throw formatted_error("StreamName was {}, expected ::$DATA", u16string_to_string(name1)); auto& fsi2 = *static_cast(items[1]); if (fsi2.StreamSize.QuadPart != 0) throw formatted_error("StreamSize was {}, expected 0", fsi2.StreamSize.QuadPart); if (fsi2.StreamAllocationSize.QuadPart != 0) throw formatted_error("StreamAllocationSize was {}, expected 0", fsi2.StreamAllocationSize.QuadPart); auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t)); if (name2 != u":stream:$DATA") throw formatted_error("StreamName was {}, expected :stream:$DATA", u16string_to_string(name2)); }); h.reset(); } test("Create stream on non-existent file", [&]() { h = create_file(dir + u"\\stream2:stream", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Query streams", [&]() { auto items = query_streams(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2", items.size()); auto& fsi1 = *static_cast(items[0]); if (fsi1.StreamSize.QuadPart != 0) throw formatted_error("StreamSize was {}, expected 0", fsi1.StreamSize.QuadPart); if (fsi1.StreamAllocationSize.QuadPart != 0) throw formatted_error("StreamAllocationSize was {}, expected 0", fsi1.StreamAllocationSize.QuadPart); auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t)); if (name1 != u"::$DATA") throw formatted_error("StreamName was {}, expected ::$DATA", u16string_to_string(name1)); auto& fsi2 = *static_cast(items[1]); if (fsi2.StreamSize.QuadPart != 0) throw formatted_error("StreamSize was {}, expected 0", fsi2.StreamSize.QuadPart); if (fsi2.StreamAllocationSize.QuadPart != 0) throw formatted_error("StreamAllocationSize was {}, expected 0", fsi2.StreamAllocationSize.QuadPart); auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t)); if (name2 != u":stream:$DATA") throw formatted_error("StreamName was {}, expected :stream:$DATA", u16string_to_string(name2)); }); h.reset(); } test("Check file created for stream", [&]() { create_file(dir + u"\\stream2", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); test("Try to create stream with FILE_DIRECTORY_FILE", [&]() { exp_status([&]() { create_file(dir + u"\\stream2:stream", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }, STATUS_NOT_A_DIRECTORY); }); test("Create directory", [&]() { h = create_file(dir + u"\\stream3", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Query streams", [&]() { auto items = query_streams(h.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& fsi = *static_cast(items.front()); if (fsi.StreamSize.QuadPart != 0) throw formatted_error("StreamSize was {}, expected 0", fsi.StreamSize.QuadPart); if (fsi.StreamAllocationSize.QuadPart != 0) throw formatted_error("StreamAllocationSize was {}, expected 0", fsi.StreamAllocationSize.QuadPart); auto name = u16string_view((char16_t*)fsi.StreamName, fsi.StreamNameLength / sizeof(char16_t)); if (name != u"") throw formatted_error("StreamName was {}, expected empty string", u16string_to_string(name)); }); test("Check FILE_STANDARD_INFORMATION_EX", [&]() { auto fsix = query_information(h.get()); if (fsix.AlternateStream) throw runtime_error("AlternateStream was true, expected false"); }); h.reset(); } test("Try to create stream with FILE_DIRECTORY_FILE", [&]() { exp_status([&]() { create_file(dir + u"\\stream3:stream", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }, STATUS_NOT_A_DIRECTORY); }); test("Create stream on directory", [&]() { h = create_file(dir + u"\\stream3:stream", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Query streams", [&]() { auto items = query_streams(h.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& fsi = *static_cast(items.front()); if (fsi.StreamSize.QuadPart != 0) throw formatted_error("StreamSize was {}, expected 0", fsi.StreamSize.QuadPart); if (fsi.StreamAllocationSize.QuadPart != 0) throw formatted_error("StreamAllocationSize was {}, expected 0", fsi.StreamAllocationSize.QuadPart); auto name = u16string_view((char16_t*)fsi.StreamName, fsi.StreamNameLength / sizeof(char16_t)); if (name != u":stream:$DATA") throw formatted_error("StreamName was {}, expected :stream:$DATA", u16string_to_string(name)); }); test("Check FILE_STANDARD_INFORMATION_EX", [&]() { auto fsix = query_information(h.get()); if (!fsix.AlternateStream) throw runtime_error("AlternateStream was false, expected true"); }); h.reset(); } test("Try to create ::$DATA stream on directory", [&]() { exp_status([&]() { create_file(dir + u"\\stream3::$DATA", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, STATUS_FILE_IS_A_DIRECTORY); }); test("Create stream", [&]() { h = create_file(dir + u"\\stream4:stream", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED); }); if (h) { static const string_view data = "hello"; test("Write to stream", [&]() { write_file(h.get(), span((uint8_t*)data.data(), data.size())); }); test("Read from stream", [&]() { auto buf = read_file(h.get(), data.size(), 0); if (buf.size() != data.size() || memcmp(buf.data(), data.data(), data.size())) throw runtime_error("Data read did not match data written"); }); test("Query streams", [&]() { auto items = query_streams(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2", items.size()); auto& fsi1 = *static_cast(items[0]); if (fsi1.StreamSize.QuadPart != 0) throw formatted_error("StreamSize was {}, expected 0", fsi1.StreamSize.QuadPart); if (fsi1.StreamAllocationSize.QuadPart != 0) throw formatted_error("StreamAllocationSize was {}, expected 0", fsi1.StreamAllocationSize.QuadPart); auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t)); if (name1 != u"::$DATA") throw formatted_error("StreamName was {}, expected ::$DATA", u16string_to_string(name1)); auto& fsi2 = *static_cast(items[1]); if ((size_t)fsi2.StreamSize.QuadPart != data.size()) throw formatted_error("StreamSize was {}, expected {}", fsi2.StreamSize.QuadPart, data.size()); if (fsi2.StreamAllocationSize.QuadPart < fsi2.StreamSize.QuadPart) { throw formatted_error("StreamAllocationSize was less than StreamSize ({} < {})", fsi2.StreamAllocationSize.QuadPart, fsi2.StreamSize.QuadPart); } auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t)); if (name2 != u":stream:$DATA") throw formatted_error("StreamName was {}, expected :stream:$DATA", u16string_to_string(name2)); }); test("Set zero data", [&]() { set_zero_data(h.get(), 2, 4); }); test("Read from stream", [&]() { static const string_view exp("he\0\0o", 5); auto buf = read_file(h.get(), data.size(), 0); if (buf.size() != exp.size() || memcmp(buf.data(), exp.data(), exp.size())) throw runtime_error("Data read was not as expected"); }); test("Query streams", [&]() { auto items = query_streams(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2", items.size()); auto& fsi1 = *static_cast(items[0]); if (fsi1.StreamSize.QuadPart != 0) throw formatted_error("StreamSize was {}, expected 0", fsi1.StreamSize.QuadPart); if (fsi1.StreamAllocationSize.QuadPart != 0) throw formatted_error("StreamAllocationSize was {}, expected 0", fsi1.StreamAllocationSize.QuadPart); auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t)); if (name1 != u"::$DATA") throw formatted_error("StreamName was {}, expected ::$DATA", u16string_to_string(name1)); auto& fsi2 = *static_cast(items[1]); if ((size_t)fsi2.StreamSize.QuadPart != data.size()) throw formatted_error("StreamSize was {}, expected {}", fsi2.StreamSize.QuadPart, data.size()); if (fsi2.StreamAllocationSize.QuadPart < fsi2.StreamSize.QuadPart) { throw formatted_error("StreamAllocationSize was less than StreamSize ({} < {})", fsi2.StreamAllocationSize.QuadPart, fsi2.StreamSize.QuadPart); } auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t)); if (name2 != u":stream:$DATA") throw formatted_error("StreamName was {}, expected :stream:$DATA", u16string_to_string(name2)); }); test("Truncate stream", [&]() { set_end_of_file(h.get(), 3); }); test("Read from stream", [&]() { static const string_view exp("he\0", 3); auto buf = read_file(h.get(), data.size(), 0); if (buf.size() != exp.size() || memcmp(buf.data(), exp.data(), exp.size())) throw runtime_error("Data read was not as expected"); }); test("Query streams", [&]() { auto items = query_streams(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2", items.size()); auto& fsi1 = *static_cast(items[0]); if (fsi1.StreamSize.QuadPart != 0) throw formatted_error("StreamSize was {}, expected 0", fsi1.StreamSize.QuadPart); if (fsi1.StreamAllocationSize.QuadPart != 0) throw formatted_error("StreamAllocationSize was {}, expected 0", fsi1.StreamAllocationSize.QuadPart); auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t)); if (name1 != u"::$DATA") throw formatted_error("StreamName was {}, expected ::$DATA", u16string_to_string(name1)); auto& fsi2 = *static_cast(items[1]); if (fsi2.StreamSize.QuadPart != 3) throw formatted_error("StreamSize was {}, expected 3", fsi2.StreamSize.QuadPart); if (fsi2.StreamAllocationSize.QuadPart < fsi2.StreamSize.QuadPart) { throw formatted_error("StreamAllocationSize was less than StreamSize ({} < {})", fsi2.StreamAllocationSize.QuadPart, fsi2.StreamSize.QuadPart); } auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t)); if (name2 != u":stream:$DATA") throw formatted_error("StreamName was {}, expected :stream:$DATA", u16string_to_string(name2)); }); h.reset(); } test("Create stream", [&]() { create_file(dir + u"\\stream5:stream", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); test("Open stream (FILE_OPEN)", [&]() { create_file(dir + u"\\stream5:stream", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); test("Open stream (FILE_OPEN_IF)", [&]() { create_file(dir + u"\\stream5:stream", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); test("Overwrite stream (FILE_OVERWRITE)", [&]() { create_file(dir + u"\\stream5:stream", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE, FILE_NON_DIRECTORY_FILE, FILE_OVERWRITTEN); }); test("Overwrite stream (FILE_OVERWRITE_IF)", [&]() { create_file(dir + u"\\stream5:stream", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE_IF, FILE_NON_DIRECTORY_FILE, FILE_OVERWRITTEN); }); test("Supersede stream", [&]() { create_file(dir + u"\\stream5:stream", MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE, FILE_NON_DIRECTORY_FILE, FILE_SUPERSEDED); }); test("Create stream", [&]() { h = create_file(dir + u"\\stream6:stream", DELETE, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Delete stream", [&]() { set_disposition_information(h.get(), true); }); h.reset(); test("Check directory entry for file", [&]() { u16string_view name = u"stream6"; auto items = query_dir(dir, name); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1.", items.size()); auto& fdi = *static_cast(items.front()); if (fdi.FileNameLength != name.size() * sizeof(char16_t)) throw formatted_error("FileNameLength was {}, expected {}.", fdi.FileNameLength, name.size() * sizeof(char16_t)); if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t))) throw runtime_error("FileName did not match."); }); } test("Create stream", [&]() { create_file(dir + u"\\stream7:stream", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); test("Open file", [&]() { h = create_file(dir + u"\\stream7", DELETE, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); if (h) { test("Delete file", [&]() { set_disposition_information(h.get(), true); }); h.reset(); test("Check directory entry for file gone", [&]() { exp_status([&]() { u16string_view name = u"stream7"; query_dir(dir, name); }, STATUS_NO_SUCH_FILE); }); } test("Create stream", [&]() { h = create_file(dir + u"\\stream8:stream", FILE_WRITE_DATA | DELETE, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Write to stream", [&]() { static const string_view data = "hello"; write_file_wait(h.get(), span((uint8_t*)data.data(), data.size()), 0); }); test("Check FILE_STANDARD_INFORMATION_EX", [&]() { auto fsix = query_information(h.get()); if (!fsix.AlternateStream) throw runtime_error("AlternateStream was false, expected true"); }); test("Try to rename stream using full path", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, dir + u"\\stream8:stream2"); }, STATUS_INVALID_PARAMETER); }); test("Rename stream", [&]() { set_rename_information(h.get(), false, nullptr, u":stream2"); }); test("Check FILE_STANDARD_INFORMATION_EX", [&]() { auto fsix = query_information(h.get()); if (!fsix.AlternateStream) throw runtime_error("AlternateStream was false, expected true"); }); h.reset(); } test("Open stream", [&]() { h = create_file(dir + u"\\stream8:stream2", FILE_READ_DATA, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); if (h) { static const string_view data = "hello"; test("Read from stream", [&]() { auto buf = read_file_wait(h.get(), data.size(), 0); if (buf.size() != data.size() || memcmp(buf.data(), data.data(), data.size())) throw runtime_error("Data read did not match data written"); }); test("Query streams", [&]() { auto items = query_streams(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2", items.size()); auto& fsi1 = *static_cast(items[0]); if (fsi1.StreamSize.QuadPart != 0) throw formatted_error("StreamSize was {}, expected 0", fsi1.StreamSize.QuadPart); if (fsi1.StreamAllocationSize.QuadPart != 0) throw formatted_error("StreamAllocationSize was {}, expected 0", fsi1.StreamAllocationSize.QuadPart); auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t)); if (name1 != u"::$DATA") throw formatted_error("StreamName was {}, expected ::$DATA", u16string_to_string(name1)); auto& fsi2 = *static_cast(items[1]); if ((size_t)fsi2.StreamSize.QuadPart != data.size()) throw formatted_error("StreamSize was {}, expected {}", fsi2.StreamSize.QuadPart, data.size()); if (fsi2.StreamAllocationSize.QuadPart < fsi2.StreamSize.QuadPart) { throw formatted_error("StreamAllocationSize was less than StreamSize ({} < {})", fsi2.StreamAllocationSize.QuadPart, fsi2.StreamSize.QuadPart); } auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t)); if (name2 != u":stream2:$DATA") throw formatted_error("StreamName was {}, expected :stream2:$DATA", u16string_to_string(name2)); }); h.reset(); } test("Create stream", [&]() { h = create_file(dir + u"\\stream9:stream", FILE_WRITE_DATA | DELETE, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { static const string_view data = "hello"; test("Write to stream", [&]() { write_file_wait(h.get(), span((uint8_t*)data.data(), data.size()), 0); }); test("Check FILE_STANDARD_INFORMATION_EX", [&]() { auto fsix = query_information(h.get()); if (!fsix.AlternateStream) throw runtime_error("AlternateStream was false, expected true"); }); test("Rename stream to ::$DATA without ReplaceIfExists", [&]() { exp_status([&]() { set_rename_information(h.get(), false, nullptr, u"::$DATA"); }, STATUS_OBJECT_NAME_COLLISION); }); test("Rename stream to ::$DATA with ReplaceIfExists", [&]() { set_rename_information(h.get(), true, nullptr, u"::$DATA"); }); test("Check FILE_STANDARD_INFORMATION_EX", [&]() { auto fsix = query_information(h.get()); if (fsix.AlternateStream) throw runtime_error("AlternateStream was true, expected false"); }); test("Query streams", [&]() { auto items = query_streams(h.get()); if (items.size() != 1) throw formatted_error("{} entries returned, expected 1", items.size()); auto& fsi = *static_cast(items.front()); if ((size_t)fsi.StreamSize.QuadPart != data.size()) throw formatted_error("StreamSize was {}, expected {}", fsi.StreamSize.QuadPart, data.size()); if (fsi.StreamAllocationSize.QuadPart < fsi.StreamSize.QuadPart) { throw formatted_error("StreamAllocationSize was less than StreamSize ({} < {})", fsi.StreamAllocationSize.QuadPart, fsi.StreamSize.QuadPart); } auto name = u16string_view((char16_t*)fsi.StreamName, fsi.StreamNameLength / sizeof(char16_t)); if (name != u"::$DATA") throw formatted_error("StreamName was {}, expected ::$DATA", u16string_to_string(name)); }); h.reset(); } test("Open file", [&]() { h = create_file(dir + u"\\stream9", FILE_READ_DATA, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); if (h) { test("Read from file", [&]() { static const string_view data = "hello"; auto buf = read_file_wait(h.get(), data.size(), 0); if (buf.size() != data.size() || memcmp(buf.data(), data.data(), data.size())) throw runtime_error("Data read did not match data written"); }); h.reset(); } test("Create directory", [&]() { create_file(dir + u"\\stream10", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); test("Create stream", [&]() { h = create_file(dir + u"\\stream10:stream", FILE_WRITE_DATA | DELETE, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Try to rename stream on directory to ::$DATA", [&]() { exp_status([&]() { set_rename_information(h.get(), true, nullptr, u"::$DATA"); }, STATUS_INVALID_PARAMETER); }); h.reset(); } test("Create file", [&]() { h = create_file(dir + u"\\stream11", FILE_WRITE_DATA | DELETE, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { static const string_view data = "hello"; test("Write to file", [&]() { write_file_wait(h.get(), span((uint8_t*)data.data(), data.size()), 0); }); test("Check FILE_STANDARD_INFORMATION_EX", [&]() { auto fsix = query_information(h.get()); if (fsix.AlternateStream) throw runtime_error("AlternateStream was true, expected false"); }); test("Rename file to :stream", [&]() { set_rename_information(h.get(), false, nullptr, u":stream"); }); test("Query streams", [&]() { auto items = query_streams(h.get()); if (items.size() != 2) throw formatted_error("{} entries returned, expected 2", items.size()); auto& fsi1 = *static_cast(items[0]); if (fsi1.StreamSize.QuadPart != 0) throw formatted_error("StreamSize was {}, expected 0", fsi1.StreamSize.QuadPart); if (fsi1.StreamAllocationSize.QuadPart != 0) throw formatted_error("StreamAllocationSize was {}, expected 0", fsi1.StreamAllocationSize.QuadPart); auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t)); if (name1 != u"::$DATA") throw formatted_error("StreamName was {}, expected ::$DATA", u16string_to_string(name1)); auto& fsi2 = *static_cast(items[1]); if ((size_t)fsi2.StreamSize.QuadPart != data.size()) throw formatted_error("StreamSize was {}, expected {}", fsi2.StreamSize.QuadPart, data.size()); if (fsi2.StreamAllocationSize.QuadPart < fsi2.StreamSize.QuadPart) { throw formatted_error("StreamAllocationSize was less than StreamSize ({} < {})", fsi2.StreamAllocationSize.QuadPart, fsi2.StreamSize.QuadPart); } auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t)); if (name2 != u":stream:$DATA") throw formatted_error("StreamName was {}, expected :stream:$DATA", u16string_to_string(name2)); }); test("Check FILE_STANDARD_INFORMATION_EX", [&]() { auto fsix = query_information(h.get()); if (!fsix.AlternateStream) throw runtime_error("AlternateStream was false, expected true"); }); h.reset(); } test("Open stream", [&]() { h = create_file(dir + u"\\stream11:stream", FILE_READ_DATA, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); if (h) { test("Read from file", [&]() { static const string_view data = "hello"; auto buf = read_file_wait(h.get(), data.size(), 0); if (buf.size() != data.size() || memcmp(buf.data(), data.data(), data.size())) throw runtime_error("Data read did not match data written"); }); h.reset(); } test("Create directory", [&]() { h = create_file(dir + u"\\stream12", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Try to rename directory to :stream", [&]() { exp_status([&]() { set_rename_information(h.get(), true, nullptr, u":stream"); }, STATUS_INVALID_PARAMETER); }); h.reset(); } test("Create file", [&]() { create_file(dir + u"\\stream13", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); test("Open file with ::$DATA suffix", [&]() { create_file(dir + u"\\stream13::$DATA", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); test("Open file with ::$data suffix", [&]() { create_file(dir + u"\\stream13::$data", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); test("Create stream", [&]() { create_file(dir + u"\\stream13:stream", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); test("Open stream with ::$DATA suffix", [&]() { create_file(dir + u"\\stream13:stream:$DATA", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); test("Open stream with ::$data suffix", [&]() { create_file(dir + u"\\stream13:stream:$data", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); test("Create file", [&]() { create_file(dir + u"\\stream14", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); test("Create stream with long name", [&]() { u16string longname(256, u'x'); exp_status([&]() { create_file(dir + u"\\stream14:" + longname, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, STATUS_OBJECT_NAME_INVALID); }); test("Create stream with emoji", [&]() { create_file(dir + u"\\stream14:\U0001f525", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); bool is_ntfs = fstype == fs_type::ntfs; test("Create stream with more than 255 UTF-8 characters", [&]() { auto fn = dir + u"\\stream14:"; for (unsigned int i = 0; i < 64; i++) { fn += u"\U0001f525"; } exp_status([&]() { create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); test("Create stream with WTF-16 (1)", [&]() { auto fn = dir + u"\\stream14:"; fn += (char16_t)0xd83d; exp_status([&]() { create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); test("Create stream with WTF-16 (2)", [&]() { auto fn = dir + u"\\stream14:"; fn += (char16_t)0xdd25; exp_status([&]() { create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); test("Create stream with WTF-16 (3)", [&]() { auto fn = dir + u"\\stream14:"; fn += (char16_t)0xdd25; fn += (char16_t)0xd83d; exp_status([&]() { create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); struct { u16string name; string desc; bool valid; } unusual_names[] = { { u"/", "slash", false }, { u":", "colon", false }, { u"<", "less than", true }, { u">", "greater than", true }, { u"\"", "quote", true }, { u"|", "pipe", true }, { u"?", "question mark", true }, { u"*", "asterisk", true } }; for (const auto& n : unusual_names) { test("Create stream with unusual name (" + n.desc + ")", [&]() { auto fn = dir + u"\\stream14:" + n.name; exp_status([&]() { create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, n.valid ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); } struct { u16string name; string desc; } btrfs_reserved[] = { { u"DOSATTRIB", "DOSATTRIB" }, { u"reparse", "reparse" }, { u"EA", "EA" }, { u"casesensitive", "casesensitive" } }; for (const auto& n : btrfs_reserved) { test("Create stream with reserved name (" + n.desc + ")", [&]() { auto fn = dir + u"\\stream14:" + n.name; exp_status([&]() { create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID); }); } test("Create file", [&]() { h = create_file(dir + u"\\stream15", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); if (h) { test("Rename to stream with long name", [&]() { u16string longname(256, u'x'); exp_status([&]() { set_rename_information(h.get(), false, nullptr, u":" + longname); }, STATUS_INVALID_PARAMETER); }); test("Rename to stream with emoji", [&]() { set_rename_information(h.get(), false, nullptr, u":\U0001f525"); }); test("Rename to stream with more than 255 UTF-8 characters", [&]() { u16string fn = u":"; for (unsigned int i = 0; i < 64; i++) { fn += u"\U0001f525"; } exp_status([&]() { set_rename_information(h.get(), false, nullptr, fn); }, is_ntfs ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER); }); test("Rename to stream with WTF-16 (1)", [&]() { u16string fn = u":"; fn += (char16_t)0xd83d; exp_status([&]() { set_rename_information(h.get(), false, nullptr, fn); }, is_ntfs ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER); }); test("Rename to stream with WTF-16 (2)", [&]() { u16string fn = u":"; fn += (char16_t)0xdd25; exp_status([&]() { set_rename_information(h.get(), false, nullptr, fn); }, is_ntfs ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER); }); test("Rename to stream with WTF-16 (3)", [&]() { u16string fn = u":"; fn += (char16_t)0xdd25; fn += (char16_t)0xd83d; exp_status([&]() { set_rename_information(h.get(), false, nullptr, fn); }, is_ntfs ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER); }); for (const auto& n : unusual_names) { test("Rename to stream with unusual name (" + n.desc + ")", [&]() { auto fn = u":" + n.name; exp_status([&]() { set_rename_information(h.get(), false, nullptr, fn); }, n.valid ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER); }); } for (const auto& n : btrfs_reserved) { test("Rename to stream with reserved name (" + n.desc + ")", [&]() { auto fn = u":" + n.name; exp_status([&]() { set_rename_information(h.get(), false, nullptr, fn); }, is_ntfs ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER); }); } h.reset(); } test("Create stream", [&]() { create_file(dir + u"\\stream16:stream", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED); }); test("Open stream using wrong case", [&]() { create_file(dir + u"\\STREAM16:STREAM", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE, FILE_OPENED); }); } ================================================ FILE: src/tests/supersede.cpp ================================================ #include "test.h" using namespace std; void test_supersede(const u16string& dir) { unique_handle h; test("Create file by FILE_SUPERSEDE", [&]() { h = create_file(dir + u"\\supersede", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SUPERSEDE, 0, FILE_CREATED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_ARCHIVE)) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_ARCHIVE", fbi.FileAttributes); } }); test("Try superseding open file", [&]() { create_file(dir + u"\\supersede", MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); h.reset(); } test("Supersede file", [&]() { h = create_file(dir + u"\\supersede", MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); if (h) { test("Check attributes", [&]() { auto fbi = query_information(h.get()); if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) { throw formatted_error("attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE", fbi.FileAttributes); } }); h.reset(); } test("Supersede adding hidden flag", [&]() { create_file(dir + u"\\supersede", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN, 0, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); test("Try superseding while clearing hidden flag", [&]() { exp_status([&]() { create_file(dir + u"\\supersede", MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }, STATUS_ACCESS_DENIED); }); test("Supersede adding system flag", [&]() { create_file(dir + u"\\supersede", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM, 0, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); test("Try superseding while clearing system flag", [&]() { exp_status([&]() { create_file(dir + u"\\supersede", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN, 0, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }, STATUS_ACCESS_DENIED); }); test("Try creating directory by FILE_SUPERSEDE", [&]() { exp_status([&]() { create_file(dir + u"\\supersededir", MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE, FILE_DIRECTORY_FILE, FILE_CREATED); }, STATUS_INVALID_PARAMETER); }); test("Create file", [&]() { h = create_file(dir + u"\\supersede2", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED); }); if (h) { h.reset(); test("Supersede file with different case", [&]() { h = create_file(dir + u"\\SUPERSEDE2", MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE, 0, FILE_SUPERSEDED); }); if (h) { test("Check name", [&]() { auto fn = query_file_name_information(h.get()); static const u16string_view ends_with = u"\\supersede2"; if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with) throw runtime_error("Name did not end with \"\\supersede2\"."); }); } } } ================================================ FILE: src/tests/test.cpp ================================================ /* Copyright (c) Mark Harmstone 2021 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "test.h" #define NOGDI #include #include #include #include #include using namespace std; enum fs_type fstype; static unsigned int num_tests_run, num_tests_passed; unique_handle create_file(u16string_view path, ACCESS_MASK access, ULONG atts, ULONG share, ULONG dispo, ULONG options, ULONG_PTR exp_info, optional allocation) { NTSTATUS Status; HANDLE h; IO_STATUS_BLOCK iosb; UNICODE_STRING us; OBJECT_ATTRIBUTES oa; LARGE_INTEGER alloc_size; memset(&oa, 0, sizeof(oa)); oa.Length = sizeof(oa); oa.RootDirectory = nullptr; // FIXME - test us.Length = us.MaximumLength = path.length() * sizeof(char16_t); us.Buffer = (WCHAR*)path.data(); oa.ObjectName = &us; oa.Attributes = OBJ_CASE_INSENSITIVE; oa.SecurityDescriptor = nullptr; oa.SecurityQualityOfService = nullptr; if (allocation) alloc_size.QuadPart = allocation.value(); iosb.Information = 0xdeadbeef; Status = NtCreateFile(&h, access, &oa, &iosb, allocation ? &alloc_size : nullptr, atts, share, dispo, options, nullptr, 0); if (Status != STATUS_SUCCESS) { if (NT_SUCCESS(Status)) // STATUS_OPLOCK_BREAK_IN_PROGRESS etc. NtClose(h); throw ntstatus_error(Status); } if (iosb.Information != exp_info) throw formatted_error("iosb.Information was {}, expected {}", iosb.Information, exp_info); return unique_handle(h); } varbuf query_all_information(HANDLE h) { IO_STATUS_BLOCK iosb; NTSTATUS Status; FILE_ALL_INFORMATION fai; fai.NameInformation.FileNameLength = 0; Status = NtQueryInformationFile(h, &iosb, &fai, sizeof(fai), FileAllInformation); if (Status != STATUS_SUCCESS && Status != STATUS_BUFFER_OVERFLOW) throw ntstatus_error(Status); varbuf ret; ret.buf.resize(offsetof(FILE_ALL_INFORMATION, NameInformation.FileName) + fai.NameInformation.FileNameLength); auto& fai2 = *reinterpret_cast(ret.buf.data()); fai2.NameInformation.FileNameLength = ret.buf.size() - offsetof(FILE_ALL_INFORMATION, NameInformation.FileName); Status = NtQueryInformationFile(h, &iosb, &fai2, ret.buf.size(), FileAllInformation); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != ret.buf.size()) throw formatted_error("iosb.Information was {}, expected {}", iosb.Information, ret.buf.size()); return ret; } template T query_information(HANDLE h) { IO_STATUS_BLOCK iosb; NTSTATUS Status; T t; FILE_INFORMATION_CLASS fic; if constexpr (is_same_v) fic = FileBasicInformation; else if constexpr (is_same_v) fic = FileStandardInformation; else if constexpr (is_same_v) fic = FileAccessInformation; else if constexpr (is_same_v) fic = FileModeInformation; else if constexpr (is_same_v) fic = FileAlignmentInformation; else if constexpr (is_same_v) fic = FilePositionInformation; else if constexpr (is_same_v) fic = FileInternalInformation; else if constexpr (is_same_v) fic = FileCaseSensitiveInformation; else if constexpr (is_same_v) fic = FileEaInformation; else if constexpr (is_same_v) fic = FileStatInformation; else if constexpr (is_same_v) fic = FileStatLxInformation; else if constexpr (is_same_v) fic = FileAttributeTagInformation; else if constexpr (is_same_v) fic = FileCompressionInformation; else if constexpr (is_same_v) fic = FileNetworkOpenInformation; else if constexpr (is_same_v) fic = FileStandardLinkInformation; else if constexpr (is_same_v) fic = FileIdInformation; else if constexpr (is_same_v) fic = FileStandardInformation; else throw runtime_error("Unrecognized file information class."); Status = NtQueryInformationFile(h, &iosb, &t, sizeof(t), fic); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != sizeof(t)) throw formatted_error("iosb.Information was {}, expected {}", iosb.Information, sizeof(t)); return t; } template FILE_BASIC_INFORMATION query_information(HANDLE h); template FILE_STANDARD_INFORMATION query_information(HANDLE h); template FILE_ACCESS_INFORMATION query_information(HANDLE h); template FILE_MODE_INFORMATION query_information(HANDLE h); template FILE_ALIGNMENT_INFORMATION query_information(HANDLE h); template FILE_POSITION_INFORMATION query_information(HANDLE h); template FILE_INTERNAL_INFORMATION query_information(HANDLE h); template FILE_CASE_SENSITIVE_INFORMATION query_information(HANDLE h); template FILE_EA_INFORMATION query_information(HANDLE h); template FILE_STAT_INFORMATION query_information(HANDLE h); template FILE_STAT_LX_INFORMATION query_information(HANDLE h); template FILE_ATTRIBUTE_TAG_INFORMATION query_information(HANDLE h); template FILE_COMPRESSION_INFORMATION query_information(HANDLE h); template FILE_NETWORK_OPEN_INFORMATION query_information(HANDLE h); template FILE_STANDARD_LINK_INFORMATION query_information(HANDLE h); template FILE_ID_INFORMATION query_information(HANDLE h); template FILE_STANDARD_INFORMATION_EX query_information(HANDLE h); template vector> query_dir(const u16string& dir, u16string_view filter) { NTSTATUS Status; IO_STATUS_BLOCK iosb; unique_handle dh; vector buf(sizeof(T) + 7); bool first = true; vector> ret; FILE_INFORMATION_CLASS fic; UNICODE_STRING us; size_t off; if constexpr (is_same_v) fic = FileDirectoryInformation; else if constexpr (is_same_v) fic = FileBothDirectoryInformation; else if constexpr (is_same_v) fic = FileFullDirectoryInformation; else if constexpr (is_same_v) fic = FileIdBothDirectoryInformation; else if constexpr (is_same_v) fic = FileIdFullDirectoryInformation; else if constexpr (is_same_v) fic = FileIdExtdDirectoryInformation; else if constexpr (is_same_v) fic = FileIdExtdBothDirectoryInformation; else if constexpr (is_same_v) fic = FileNamesInformation; else if constexpr (is_same_v) fic = FileReparsePointInformation; else throw runtime_error("Unrecognized file information class."); // buffer needs to be aligned to 8 bytes off = 8 - ((uintptr_t)buf.data() % 8); if (off == 8) off = 0; if (!filter.empty()) { us.Buffer = (WCHAR*)filter.data(); us.Length = us.MaximumLength = filter.size() * sizeof(char16_t); } dh = create_file(dir, SYNCHRONIZE | FILE_LIST_DIRECTORY, 0, 0, FILE_OPEN, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPENED); while (true) { Status = NtQueryDirectoryFile(dh.get(), nullptr, nullptr, nullptr, &iosb, buf.data() + off, buf.size() - off, fic, false, !filter.empty() ? &us : nullptr, first); if constexpr (!is_same_v) { if (Status == STATUS_BUFFER_OVERFLOW) { size_t new_size; new_size = offsetof(T, FileName) + (256 * sizeof(WCHAR)); new_size += ((T*)(buf.data() + off))->FileNameLength * sizeof(WCHAR); buf.resize(new_size + 7); off = 8 - ((uintptr_t)buf.data() % 8); if (off == 8) off = 0; Status = NtQueryDirectoryFile(dh.get(), nullptr, nullptr, nullptr, &iosb, buf.data() + off, buf.size() - off, fic, false, !filter.empty() ? &us : nullptr, first); } } if (Status == STATUS_NO_MORE_FILES) break; if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); auto ptr = (T*)buf.data(); do { varbuf item; if constexpr (is_same_v) item.buf.resize(sizeof(T)); else item.buf.resize(offsetof(T, FileName) + (ptr->FileNameLength * sizeof(WCHAR))); memcpy(item.buf.data(), ptr, item.buf.size()); ret.emplace_back(item); if constexpr (is_same_v) break; else { if (ptr->NextEntryOffset == 0) break; ptr = (T*)((uint8_t*)ptr + ptr->NextEntryOffset); } } while (true); first = false; } return ret; } template vector> query_dir(const u16string& dir, u16string_view filter); template vector> query_dir(const u16string& dir, u16string_view filter); template vector> query_dir(const u16string& dir, u16string_view filter); template vector> query_dir(const u16string& dir, u16string_view filter); template vector> query_dir(const u16string& dir, u16string_view filter); template vector> query_dir(const u16string& dir, u16string_view filter); template vector> query_dir(const u16string& dir, u16string_view filter); template vector> query_dir(const u16string& dir, u16string_view filter); template vector> query_dir(const u16string& dir, u16string_view filter); template void print(string_view s, Args&&... args) { auto msg = std::vformat(s, std::make_format_args(args...)); cout << msg; } void test(const string& msg, const function& func) { string err; CONSOLE_SCREEN_BUFFER_INFO csbi; num_tests_run++; try { func(); } catch (const exception& e) { err = e.what(); } catch (...) { err = "Uncaught exception."; } // FIXME - aligned output? print("{}, ", msg); auto col = GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); if (col) SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), err.empty() ? FOREGROUND_GREEN : (FOREGROUND_RED | FOREGROUND_INTENSITY)); print("{}", err.empty() ? "PASS" : "FAIL"); if (col) SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), csbi.wAttributes); if (!err.empty()) print(" ({})", err); else num_tests_passed++; print("\n"); } void exp_status(const function& func, NTSTATUS Status) { try { func(); } catch (const ntstatus_error& e) { if (e.Status != Status) throw formatted_error("Status was {}, expected {}", ntstatus_to_string(e.Status), ntstatus_to_string(Status)); else return; } if (Status != STATUS_SUCCESS) throw formatted_error("Status was STATUS_SUCCESS, expected {}", ntstatus_to_string(Status)); } u16string query_file_name_information(HANDLE h, bool normalized) { IO_STATUS_BLOCK iosb; NTSTATUS Status; FILE_NAME_INFORMATION fni; fni.FileNameLength = 0; Status = NtQueryInformationFile(h, &iosb, &fni, sizeof(fni), normalized ? FileNormalizedNameInformation : FileNameInformation); if (Status != STATUS_SUCCESS && Status != STATUS_BUFFER_OVERFLOW) throw ntstatus_error(Status); vector buf(offsetof(FILE_NAME_INFORMATION, FileName) + fni.FileNameLength); auto& fni2 = *reinterpret_cast(buf.data()); fni2.FileNameLength = buf.size() - offsetof(FILE_NAME_INFORMATION, FileName); Status = NtQueryInformationFile(h, &iosb, &fni2, buf.size(), normalized ? FileNormalizedNameInformation : FileNameInformation); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); if (iosb.Information != buf.size()) throw formatted_error("iosb.Information was {}, expected {}", iosb.Information, buf.size()); u16string ret; ret.resize(fni.FileNameLength / sizeof(char16_t)); memcpy(ret.data(), fni2.FileName, fni.FileNameLength); return ret; } static unique_handle open_process_token(HANDLE process, ACCESS_MASK access) { NTSTATUS Status; HANDLE h; Status = NtOpenProcessToken(process, access, &h); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); return unique_handle(h); } void disable_token_privileges(HANDLE token) { NTSTATUS Status; Status = NtAdjustPrivilegesToken(token, true, nullptr, 0, nullptr, nullptr); if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); } string u16string_to_string(u16string_view sv) { if (sv.empty()) return ""; auto len = WideCharToMultiByte(CP_ACP, 0, (WCHAR*)sv.data(), sv.length(), nullptr, 0, nullptr, nullptr); if (len == 0) throw formatted_error("WideCharToMultiByte failed (error {})", GetLastError()); string s(len, 0); if (WideCharToMultiByte(CP_ACP, 0, (WCHAR*)sv.data(), sv.length(), s.data(), s.length(), nullptr, nullptr) == 0) throw formatted_error("WideCharToMultiByte failed (error {})", GetLastError()); return s; } static void do_tests(u16string_view name, const u16string& dir) { auto token = open_process_token(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_DEFAULT | TOKEN_DUPLICATE | TOKEN_QUERY); disable_token_privileges(token.get()); static const struct { u16string_view name; function func; } testfuncs[] = { { u"create", [&]() { test_create(token.get(), dir); } }, { u"supersede", [&]() { test_supersede(dir); } }, { u"overwrite", [&]() { test_overwrite(dir); } }, { u"open_id", [&]() { test_open_id(token.get(), dir); } }, { u"io", [&]() { test_io(token.get(), dir); } }, { u"mmap", [&]() { test_mmap(dir); } }, { u"rename", [&]() { test_rename(dir); } }, { u"rename_ex", [&]() { test_rename_ex(token.get(), dir); } }, { u"delete", [&]() { test_delete(dir); } }, { u"delete_ex", [&]() { test_delete_ex(token.get(), dir); } }, { u"links", [&]() { test_links(token.get(), dir); } }, { u"links_ex", [&]() { test_links_ex(token.get(), dir); } }, { u"oplock_i", [&]() { test_oplocks_i(token.get(), dir); } }, { u"oplock_ii", [&]() { test_oplocks_ii(token.get(), dir); } }, { u"oplock_batch", [&]() { test_oplocks_batch(token.get(), dir); } }, { u"oplock_filter", [&]() { test_oplocks_filter(token.get(), dir); } }, { u"oplock_r", [&]() { test_oplocks_r(token.get(), dir); } }, { u"oplock_rw", [&]() { test_oplocks_rw(token.get(), dir); } }, { u"oplock_rh", [&]() { test_oplocks_rh(token.get(), dir); } }, { u"oplock_rwh", [&]() { test_oplocks_rwh(token.get(), dir); } }, { u"cs", [&]() { test_cs(dir); } }, { u"reparse", [&]() { test_reparse(token.get(), dir); } }, { u"streams", [&]() { test_streams(dir); } }, { u"ea", [&]() { test_ea(dir); } }, { u"fileinfo", [&]() { test_fileinfo(dir); } }, { u"security", [&]() { test_security(token.get(), dir); } } }; bool first = true; unsigned int total_tests_run = 0, total_tests_passed = 0; for (const auto& tf : testfuncs) { if (name == u"all" || tf.name == name) { CONSOLE_SCREEN_BUFFER_INFO csbi; auto col = GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); if (col) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY); } if (!first) print("\n"); print("Running test {}\n", u16string_to_string(tf.name)); if (col) SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), csbi.wAttributes); num_tests_run = 0; num_tests_passed = 0; tf.func(); total_tests_run += num_tests_run; total_tests_passed += num_tests_passed; print("Passed {}/{}\n", num_tests_passed, num_tests_run); first = false; if (name != u"all") break; } } // FIXME - test that FILE_SYNCHRONOUS_IO_NONALERT and FILE_SYNCHRONOUS_IO_ALERT need SYNCHRONIZE // FIXME - test opening file with RootDirectory handle // FIXME - querying directory (inc. specific files) // FIXME - NtQueryDirectoryFileEx // FIXME - directory notifications // FIXME - IOCTLs and FSCTLs // FIXME - querying volume info // FIXME - setting volume label // FIXME - locking // FIXME - object IDs // FIXME - IO completions? // FIXME - share access // FIXME - reflink copies // FIXME - creating subvols // FIXME - snapshots // FIXME - sending and receiving(?) // FIXME - using mknod etc. to test mapping between Linux and Windows concepts? if (name != u"all" && first) throw runtime_error("Test not supported."); if (name == u"all") print("\nTotal passed {}/{}\n", total_tests_passed, total_tests_run); } static u16string to_u16string(time_t n) { u16string s; while (n > 0) { s += (n % 10) + u'0'; n /= 10; } return u16string(s.rbegin(), s.rend()); } static bool fs_driver_path(HANDLE h, u16string_view driver) { NTSTATUS Status; IO_STATUS_BLOCK iosb; vector buf(offsetof(FILE_FS_DRIVER_PATH_INFORMATION, DriverName) + (driver.size() * sizeof(char16_t))); auto& ffdpi = *(FILE_FS_DRIVER_PATH_INFORMATION*)buf.data(); ffdpi.DriverInPath = false; ffdpi.DriverNameLength = driver.size() * sizeof(char16_t); memcpy(&ffdpi.DriverName, driver.data(), ffdpi.DriverNameLength); Status = NtQueryVolumeInformationFile(h, &iosb, &ffdpi, buf.size(), FileFsDriverPathInformation); if (Status == STATUS_OBJECT_NAME_NOT_FOUND) // driver not loaded return false; if (Status != STATUS_SUCCESS) throw ntstatus_error(Status); return ffdpi.DriverInPath; } class sc_handle_closer { public: typedef SC_HANDLE pointer; void operator()(SC_HANDLE h) { CloseServiceHandle(h); } }; static optional get_environment_variable(const u16string& name) { auto len = GetEnvironmentVariableW((WCHAR*)name.c_str(), nullptr, 0); if (len == 0) { if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) return nullopt; return u""; } u16string ret(len, 0); if (GetEnvironmentVariableW((WCHAR*)name.c_str(), (WCHAR*)ret.data(), len) == 0) throw formatted_error("GetEnvironmentVariable failed (error {})", GetLastError()); while (!ret.empty() && ret.back() == 0) { ret.pop_back(); } return ret; } static u16string get_driver_path(const u16string& driver) { unique_ptr sc_manager, service; sc_manager.reset(OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT)); if (!sc_manager) throw formatted_error("OpenSCManager failed (error {})", GetLastError()); service.reset(OpenServiceW(sc_manager.get(), (WCHAR*)driver.c_str(), SERVICE_QUERY_CONFIG)); if (!service) throw formatted_error("OpenService failed (error {})", GetLastError()); vector buf(sizeof(QUERY_SERVICE_CONFIGW)); DWORD needed; if (!QueryServiceConfigW(service.get(), (QUERY_SERVICE_CONFIGW*)buf.data(), buf.size(), &needed)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) throw formatted_error("QueryServiceConfig failed (error {})", GetLastError()); buf.resize(needed); if (!QueryServiceConfigW(service.get(), (QUERY_SERVICE_CONFIGW*)buf.data(), buf.size(), &needed)) throw formatted_error("QueryServiceConfig failed (error {})", GetLastError()); } auto& qsc = *(QUERY_SERVICE_CONFIGW*)buf.data(); u16string path = (char16_t*)qsc.lpBinaryPathName; if (path.empty()) // if the bootloader has sorted it out path = u"\\SystemRoot\\System32\\drivers\\" + driver + u".sys"; if (path.substr(0, 12) == u"\\SystemRoot\\") { // FIXME - case-insensitive? auto sr = get_environment_variable(u"SystemRoot"); // FIXME - get from \\SystemRoot symlink instead? if (!sr.has_value()) throw runtime_error("SystemRoot environment variable not set."); path = sr.value() + u"\\" + path.substr(12); } if (path.substr(0, 4) == u"\\??\\") path = path.substr(4); return path; } static string get_version(const u16string& fn) { DWORD dummy; auto len = GetFileVersionInfoSizeW((WCHAR*)fn.c_str(), &dummy); if (len == 0) throw formatted_error("GetFileVersionInfoSize failed (error {})", GetLastError()); vector buf(len); if (!GetFileVersionInfoW((WCHAR*)fn.c_str(), 0, buf.size(), buf.data())) throw formatted_error("GetFileVersionInfo failed (error {})", GetLastError()); VS_FIXEDFILEINFO* ver; UINT verlen; if (!VerQueryValueW(buf.data(), L"\\", (void**)&ver, &verlen)) throw runtime_error("VerQueryValue failed"); return std::format("{}.{}.{}.{}", ver->dwFileVersionMS >> 16, ver->dwFileVersionMS & 0xffff, ver->dwFileVersionLS >> 16, ver->dwFileVersionLS & 0xffff); } static string driver_string(const u16string& driver) { try { auto path = get_driver_path(driver); auto version = get_version(path); return u16string_to_string(path) + ", " + version; } catch (const exception& e) { return e.what(); } } int wmain(int argc, wchar_t* argv[]) { if (argc < 2) { cerr << "Usage: test.exe \n test.exe "; return 1; } try { u16string_view dirarg = (char16_t*)(argc < 3 ? argv[1] : argv[2]); while (!dirarg.empty() && dirarg.back() == u'\\') { dirarg.remove_suffix(1); } u16string ntdir = u"\\??\\"s + u16string(dirarg); ntdir += u"\\" + to_u16string(time(nullptr)); unique_handle dirh; try { dirh = create_file(ntdir, GENERIC_WRITE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED); } catch (const exception& e) { throw runtime_error("Error creating directory: "s + e.what()); } bool type_lookup_failed = false; fstype = fs_type::unknown; try { /* See lie_about_fs_type() for why we can't use FileFsAttributeInformation. */ if (fs_driver_path(dirh.get(), u"\\FileSystem\\NTFS")) fstype = fs_type::ntfs; else if (fs_driver_path(dirh.get(), u"\\Driver\\btrfs")) fstype = fs_type::btrfs; } catch (const exception& e) { cerr << "Error getting filesystem type: " << e.what() << endl; type_lookup_failed = true; } dirh.reset(); if (!type_lookup_failed) { switch (fstype) { case fs_type::ntfs: print("Testing on NTFS ({}).\n", driver_string(u"ntfs")); break; case fs_type::btrfs: print("Testing on Btrfs ({}).\n", driver_string(u"btrfs")); break; default: print("Testing on unknown filesystem.\n"); break; } } u16string_view testarg = argc < 3 ? u"all" : (char16_t*)argv[1]; do_tests(testarg, ntdir); } catch (const exception& e) { cerr << e.what() << endl; return 1; } return 0; } ================================================ FILE: src/tests/test.h ================================================ /* Copyright (c) Mark Harmstone 2021 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #pragma once #include #include #include #include #include #define WIN32_NO_STATUS #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum class fs_type { unknown, ntfs, btrfs }; extern "C" NTSTATUS __stdcall NtQueryDirectoryFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, ULONG Length, FILE_INFORMATION_CLASS FileInformationClass, BOOLEAN ReturnSingleEntry, PUNICODE_STRING FileName, BOOLEAN RestartScan); extern "C" NTSTATUS __stdcall NtReadFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key); extern "C" NTSTATUS __stdcall NtWriteFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key); extern "C" NTSTATUS __stdcall NtOpenProcessToken(HANDLE ProcessHandle, ACCESS_MASK DesiredAccess, PHANDLE TokenHandle); extern "C" NTSTATUS __stdcall NtAdjustPrivilegesToken(HANDLE TokenHandle, BOOLEAN DisableAllPrivileges, PTOKEN_PRIVILEGES NewState, ULONG BufferLength, PTOKEN_PRIVILEGES PreviousState, PULONG ReturnLength); typedef enum _EVENT_TYPE { NotificationEvent, SynchronizationEvent } EVENT_TYPE; extern "C" NTSTATUS __stdcall NtCreateEvent(PHANDLE EventHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, EVENT_TYPE EventType, BOOLEAN InitialState); extern "C" NTSTATUS __stdcall NtCreateSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PLARGE_INTEGER MaximumSize, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle); typedef enum _SECTION_INHERIT { ViewShare = 1, ViewUnmap } SECTION_INHERIT; extern "C" NTSTATUS __stdcall NtMapViewOfSection(HANDLE SectionHandle, HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset, PSIZE_T ViewSize, SECTION_INHERIT InheritDisposition, ULONG AllocationType, ULONG Protect); extern "C" NTSTATUS __stdcall NtUnmapViewOfSection(HANDLE ProcessHandle, PVOID BaseAddress); extern "C" NTSTATUS __stdcall NtLockFile(HANDLE FileHandle, HANDLE Event OPTIONAL, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER ByteOffset, PLARGE_INTEGER Length, ULONG Key, BOOLEAN FailImmediately, BOOLEAN ExclusiveLock); extern "C" NTSTATUS __stdcall NtUnlockFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER ByteOffset, PLARGE_INTEGER Length, ULONG Key); extern "C" NTSTATUS __stdcall NtQuerySecurityObject(HANDLE Handle, SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR SecurityDescriptor, ULONG Length, PULONG LengthNeeded); extern "C" NTSTATUS __stdcall NtSetSecurityObject(HANDLE Handle, SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR SecurityDescriptor); typedef enum _EVENT_INFORMATION_CLASS { EventBasicInformation } EVENT_INFORMATION_CLASS, *PEVENT_INFORMATION_CLASS; typedef struct _EVENT_BASIC_INFORMATION { EVENT_TYPE EventType; LONG EventState; } EVENT_BASIC_INFORMATION, *PEVENT_BASIC_INFORMATION; extern "C" NTSTATUS __stdcall NtQueryEvent(HANDLE EventHandle, EVENT_INFORMATION_CLASS EventInformationClass, PVOID EventInformation, ULONG EventInformationLength, PULONG ReturnLength); extern "C" NTSTATUS __stdcall NtQueryEaFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, BOOLEAN ReturnSingleEntry, PVOID EaList, ULONG EaListLength, PULONG EaIndex, BOOLEAN RestartScan); extern "C" NTSTATUS __stdcall NtSetEaFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length); extern "C" NTSTATUS __stdcall NtSetInformationToken(HANDLE TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, PVOID TokenInformation, ULONG TokenInformationLength); extern "C" NTSTATUS __stdcall NtDuplicateToken(HANDLE ExistingTokenHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, BOOLEAN EffectiveOnly, TOKEN_TYPE TokenType, PHANDLE NewTokenHandle); extern "C" NTSTATUS __stdcall NtSetInformationThread(HANDLE ThreadHandle, THREADINFOCLASS ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength); #define FILE_NEED_EA 0x00000080 typedef struct _FILE_GET_EA_INFORMATION { ULONG NextEntryOffset; UCHAR EaNameLength; CHAR EaName[1]; } FILE_GET_EA_INFORMATION, *PFILE_GET_EA_INFORMATION; #define NtCurrentThread() ((HANDLE)(LONG_PTR) -2) #define NtCurrentProcess() ((HANDLE)(LONG_PTR) -1) #define FileIdInformation ((FILE_INFORMATION_CLASS)59) #define FileIdExtdDirectoryInformation ((FILE_INFORMATION_CLASS)60) #define FileIdExtdBothDirectoryInformation ((FILE_INFORMATION_CLASS)63) #define FileDispositionInformationEx ((FILE_INFORMATION_CLASS)64) #define FileRenameInformationEx ((FILE_INFORMATION_CLASS)65) #define FileCaseSensitiveInformation ((FILE_INFORMATION_CLASS)71) #define FileLinkInformationEx ((FILE_INFORMATION_CLASS)72) #define FILE_WORD_ALIGNMENT 0x00000001 #define SE_SECURITY_PRIVILEGE 8 #define SE_RESTORE_PRIVILEGE 18 #define SE_CHANGE_NOTIFY_PRIVILEGE 23 #define SE_MANAGE_VOLUME_PRIVILEGE 28 #define SE_CREATE_SYMBOLIC_LINK_PRIVILEGE 35 #define FILE_USE_FILE_POINTER_POSITION 0xfffffffe #define FILE_WRITE_TO_END_OF_FILE 0xffffffff #define FILE_RENAME_REPLACE_IF_EXISTS 0x00000001 #define FILE_RENAME_POSIX_SEMANTICS 0x00000002 #define FILE_RENAME_IGNORE_READONLY_ATTRIBUTE 0x00000040 #define FILE_LINK_REPLACE_IF_EXISTS 0x00000001 #define FILE_LINK_POSIX_SEMANTICS 0x00000002 #define FILE_LINK_IGNORE_READONLY_ATTRIBUTE 0x00000040 typedef struct _FILE_FS_DRIVER_PATH_INFORMATION { BOOLEAN DriverInPath; ULONG DriverNameLength; WCHAR DriverName[1]; } FILE_FS_DRIVER_PATH_INFORMATION, *PFILE_FS_DRIVER_PATH_INFORMATION; // should be called FILE_RENAME_INFORMATION, version in mingw is outdated typedef struct _FILE_RENAME_INFORMATION_EX { union { BOOLEAN ReplaceIfExists; ULONG Flags; }; HANDLE RootDirectory; ULONG FileNameLength; WCHAR FileName[1]; } FILE_RENAME_INFORMATION_EX, *PFILE_RENAME_INFORMATION_EX; // should be called FILE_RENAME_INFORMATION_EX, version in mingw is outdated typedef struct _FILE_LINK_INFORMATION_EX { union { BOOLEAN ReplaceIfExists; ULONG Flags; }; HANDLE RootDirectory; ULONG FileNameLength; WCHAR FileName[1]; } FILE_LINK_INFORMATION_EX, *PFILE_LINK_INFORMATION_EX; typedef struct _FILE_LINK_ENTRY_INFORMATION { ULONG NextEntryOffset; LONGLONG ParentFileId; ULONG FileNameLength; WCHAR FileName[1]; } FILE_LINK_ENTRY_INFORMATION, *PFILE_LINK_ENTRY_INFORMATION; typedef struct _FILE_LINKS_INFORMATION { ULONG BytesNeeded; ULONG EntriesReturned; FILE_LINK_ENTRY_INFORMATION Entry; } FILE_LINKS_INFORMATION, *PFILE_LINKS_INFORMATION; typedef struct _FILE_DISPOSITION_INFORMATION_EX { ULONG Flags; } FILE_DISPOSITION_INFORMATION_EX, *PFILE_DISPOSITION_INFORMATION_EX; #define FILE_DISPOSITION_DO_NOT_DELETE 0x00000000 #define FILE_DISPOSITION_DELETE 0x00000001 #define FILE_DISPOSITION_POSIX_SEMANTICS 0x00000002 #define FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK 0x00000004 #define FILE_DISPOSITION_ON_CLOSE 0x00000008 #define FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE 0x00000010 typedef struct _FILE_ZERO_DATA_INFORMATION { LARGE_INTEGER FileOffset; LARGE_INTEGER BeyondFinalZero; } FILE_ZERO_DATA_INFORMATION,*PFILE_ZERO_DATA_INFORMATION; typedef struct _FILE_OBJECTID_BUFFER { BYTE ObjectId[16]; union { struct { BYTE BirthVolumeId[16]; BYTE BirthObjectId[16]; BYTE DomainId[16]; }; BYTE ExtendedInfo[48]; }; } FILE_OBJECTID_BUFFER, *PFILE_OBJECTID_BUFFER; #define FILE_CS_FLAG_CASE_SENSITIVE_DIR 0x00000001 typedef struct _FILE_CASE_SENSITIVE_INFORMATION { ULONG Flags; } FILE_CASE_SENSITIVE_INFORMATION, *PFILE_CASE_SENSITIVE_INFORMATION; typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; }; } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; typedef struct _FILE_COMPRESSION_INFORMATION { LARGE_INTEGER CompressedFileSize; USHORT CompressionFormat; UCHAR CompressionUnitShift; UCHAR ChunkShift; UCHAR ClusterShift; UCHAR Reserved[3]; } FILE_COMPRESSION_INFORMATION, *PFILE_COMPRESSION_INFORMATION; typedef struct _FILE_STANDARD_INFORMATION_EX { LARGE_INTEGER AllocationSize; LARGE_INTEGER EndOfFile; ULONG NumberOfLinks; BOOLEAN DeletePending; BOOLEAN Directory; BOOLEAN AlternateStream; BOOLEAN MetadataAttribute; } FILE_STANDARD_INFORMATION_EX, *PFILE_STANDARD_INFORMATION_EX; extern "C" NTSTATUS __stdcall NtDelayExecution(BOOLEAN Alertable, PLARGE_INTEGER DelayInterval); #ifdef _MSC_VER #define FileDirectoryInformation ((FILE_INFORMATION_CLASS)1) #define FileFullDirectoryInformation ((FILE_INFORMATION_CLASS)2) #define FileBothDirectoryInformation ((FILE_INFORMATION_CLASS)3) #define FileBasicInformation ((FILE_INFORMATION_CLASS)4) #define FileStandardInformation ((FILE_INFORMATION_CLASS)5) #define FileInternalInformation ((FILE_INFORMATION_CLASS)6) #define FileEaInformation ((FILE_INFORMATION_CLASS)7) #define FileAccessInformation ((FILE_INFORMATION_CLASS)8) #define FileNameInformation ((FILE_INFORMATION_CLASS)9) #define FileRenameInformation ((FILE_INFORMATION_CLASS)10) #define FileLinkInformation ((FILE_INFORMATION_CLASS)11) #define FileNamesInformation ((FILE_INFORMATION_CLASS)12) #define FileDispositionInformation ((FILE_INFORMATION_CLASS)13) #define FilePositionInformation ((FILE_INFORMATION_CLASS)14) #define FileFullEaInformation ((FILE_INFORMATION_CLASS)15) #define FileModeInformation ((FILE_INFORMATION_CLASS)16) #define FileAlignmentInformation ((FILE_INFORMATION_CLASS)17) #define FileAllInformation ((FILE_INFORMATION_CLASS)18) #define FileAllocationInformation ((FILE_INFORMATION_CLASS)19) #define FileEndOfFileInformation ((FILE_INFORMATION_CLASS)20) #define FileAlternateNameInformation ((FILE_INFORMATION_CLASS)21) #define FileStreamInformation ((FILE_INFORMATION_CLASS)22) #define FilePipeInformation ((FILE_INFORMATION_CLASS)23) #define FilePipeLocalInformation ((FILE_INFORMATION_CLASS)24) #define FilePipeRemoteInformation ((FILE_INFORMATION_CLASS)25) #define FileMailslotQueryInformation ((FILE_INFORMATION_CLASS)26) #define FileMailslotSetInformation ((FILE_INFORMATION_CLASS)27) #define FileCompressionInformation ((FILE_INFORMATION_CLASS)28) #define FileObjectIdInformation ((FILE_INFORMATION_CLASS)29) #define FileCompletionInformation ((FILE_INFORMATION_CLASS)30) #define FileMoveClusterInformation ((FILE_INFORMATION_CLASS)31) #define FileQuotaInformation ((FILE_INFORMATION_CLASS)32) #define FileReparsePointInformation ((FILE_INFORMATION_CLASS)33) #define FileNetworkOpenInformation ((FILE_INFORMATION_CLASS)34) #define FileAttributeTagInformation ((FILE_INFORMATION_CLASS)35) #define FileTrackingInformation ((FILE_INFORMATION_CLASS)36) #define FileIdBothDirectoryInformation ((FILE_INFORMATION_CLASS)37) #define FileIdFullDirectoryInformation ((FILE_INFORMATION_CLASS)38) #define FileValidDataLengthInformation ((FILE_INFORMATION_CLASS)39) #define FileHardLinkInformation ((FILE_INFORMATION_CLASS)46) #define FileNormalizedNameInformation ((FILE_INFORMATION_CLASS)48) #define FileStandardLinkInformation ((FILE_INFORMATION_CLASS)54) extern "C" NTSTATUS NTAPI NtQueryInformationFile(HANDLE hFile, PIO_STATUS_BLOCK io, PVOID ptr, ULONG len, FILE_INFORMATION_CLASS FileInformationClass); typedef struct _FILE_BASIC_INFORMATION { LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; ULONG FileAttributes; } FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION; typedef struct _FILE_STANDARD_INFORMATION { LARGE_INTEGER AllocationSize; LARGE_INTEGER EndOfFile; ULONG NumberOfLinks; BOOLEAN DeletePending; BOOLEAN Directory; } FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION; typedef struct _FILE_NAME_INFORMATION { ULONG FileNameLength; WCHAR FileName[1]; } FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION; typedef struct _FILE_DIRECTORY_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; WCHAR FileName[ANYSIZE_ARRAY]; } FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION; typedef struct _FILE_FULL_DIR_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; ULONG EaSize; WCHAR FileName[ANYSIZE_ARRAY]; } FILE_FULL_DIR_INFORMATION, *PFILE_FULL_DIR_INFORMATION; typedef struct _FILE_ID_FULL_DIR_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; ULONG EaSize; LARGE_INTEGER FileId; WCHAR FileName[ANYSIZE_ARRAY]; } FILE_ID_FULL_DIR_INFORMATION, *PFILE_ID_FULL_DIR_INFORMATION; typedef struct _FILE_BOTH_DIR_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; ULONG EaSize; CHAR ShortNameLength; WCHAR ShortName[12]; WCHAR FileName[ANYSIZE_ARRAY]; } FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION; typedef struct _FILE_ID_BOTH_DIR_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; ULONG EaSize; CHAR ShortNameLength; WCHAR ShortName[12]; LARGE_INTEGER FileId; WCHAR FileName[ANYSIZE_ARRAY]; } FILE_ID_BOTH_DIR_INFORMATION, *PFILE_ID_BOTH_DIR_INFORMATION; typedef struct _FILE_NAMES_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; ULONG FileNameLength; WCHAR FileName[1]; } FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION; typedef struct _OBJECT_BASIC_INFORMATION { ULONG Attributes; ACCESS_MASK GrantedAccess; ULONG HandleCount; ULONG PointerCount; ULONG PagedPoolUsage; ULONG NonPagedPoolUsage; ULONG Reserved[3]; ULONG NameInformationLength; ULONG TypeInformationLength; ULONG SecurityDescriptorLength; LARGE_INTEGER CreateTime; } OBJECT_BASIC_INFORMATION, *POBJECT_BASIC_INFORMATION; typedef struct _FILE_INTERNAL_INFORMATION { LARGE_INTEGER IndexNumber; } FILE_INTERNAL_INFORMATION, *PFILE_INTERNAL_INFORMATION; typedef struct _FILE_EA_INFORMATION { ULONG EaSize; } FILE_EA_INFORMATION, *PFILE_EA_INFORMATION; typedef struct _FILE_ACCESS_INFORMATION { ACCESS_MASK AccessFlags; } FILE_ACCESS_INFORMATION, *PFILE_ACCESS_INFORMATION; typedef struct _FILE_POSITION_INFORMATION { LARGE_INTEGER CurrentByteOffset; } FILE_POSITION_INFORMATION, *PFILE_POSITION_INFORMATION; typedef struct _FILE_MODE_INFORMATION { ULONG Mode; } FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION; typedef struct _FILE_ALIGNMENT_INFORMATION { ULONG AlignmentRequirement; } FILE_ALIGNMENT_INFORMATION, *PFILE_ALIGNMENT_INFORMATION; typedef struct _FILE_ALL_INFORMATION { FILE_BASIC_INFORMATION BasicInformation; FILE_STANDARD_INFORMATION StandardInformation; FILE_INTERNAL_INFORMATION InternalInformation; FILE_EA_INFORMATION EaInformation; FILE_ACCESS_INFORMATION AccessInformation; FILE_POSITION_INFORMATION PositionInformation; FILE_MODE_INFORMATION ModeInformation; FILE_ALIGNMENT_INFORMATION AlignmentInformation; FILE_NAME_INFORMATION NameInformation; } FILE_ALL_INFORMATION, *PFILE_ALL_INFORMATION; typedef struct _FILE_ATTRIBUTE_TAG_INFORMATION { ULONG FileAttributes; ULONG ReparseTag; } FILE_ATTRIBUTE_TAG_INFORMATION, *PFILE_ATTRIBUTE_TAG_INFORMATION; typedef struct _FILE_NETWORK_OPEN_INFORMATION { LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER AllocationSize; LARGE_INTEGER EndOfFile; ULONG FileAttributes; } FILE_NETWORK_OPEN_INFORMATION, *PFILE_NETWORK_OPEN_INFORMATION; typedef enum _FSINFOCLASS { FileFsVolumeInformation = 1, FileFsLabelInformation, FileFsSizeInformation, FileFsDeviceInformation, FileFsAttributeInformation, FileFsControlInformation, FileFsFullSizeInformation, FileFsObjectIdInformation, FileFsDriverPathInformation, FileFsVolumeFlagsInformation, FileFsSectorSizeInformation, FileFsDataCopyInformation, FileFsMetadataSizeInformation, FileFsFullSizeInformationEx, FileFsMaximumInformation } FS_INFORMATION_CLASS, *PFS_INFORMATION_CLASS; typedef struct _FILE_DISPOSITION_INFORMATION { BOOLEAN DoDeleteFile; } FILE_DISPOSITION_INFORMATION, *PFILE_DISPOSITION_INFORMATION; typedef struct _FILE_ALLOCATION_INFORMATION { LARGE_INTEGER AllocationSize; } FILE_ALLOCATION_INFORMATION, *PFILE_ALLOCATION_INFORMATION; typedef struct _FILE_END_OF_FILE_INFORMATION { LARGE_INTEGER EndOfFile; } FILE_END_OF_FILE_INFORMATION, *PFILE_END_OF_FILE_INFORMATION; typedef struct _FILE_RENAME_INFORMATION { BOOLEAN ReplaceIfExists; HANDLE RootDirectory; ULONG FileNameLength; WCHAR FileName[1]; } FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION; typedef struct _FILE_LINK_INFORMATION { BOOLEAN ReplaceIfExists; HANDLE RootDirectory; ULONG FileNameLength; WCHAR FileName[1]; } FILE_LINK_INFORMATION, *PFILE_LINK_INFORMATION; extern "C" NTSTATUS __stdcall NtQueryVolumeInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FsInformation, ULONG Length, FS_INFORMATION_CLASS FsInformationClass); extern "C" NTSTATUS __stdcall NtFsControlFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, ULONG FsControlCode, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength); extern "C" NTSTATUS __stdcall NtSetInformationFile(HANDLE hFile, PIO_STATUS_BLOCK io, PVOID ptr, ULONG len, FILE_INFORMATION_CLASS FileInformationClass); #endif typedef struct _FILE_ID_EXTD_DIR_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; ULONG EaSize; ULONG ReparsePointTag; FILE_ID_128 FileId; WCHAR FileName[1]; } FILE_ID_EXTD_DIR_INFORMATION, *PFILE_ID_EXTD_DIR_INFORMATION; typedef struct _FILE_ID_EXTD_BOTH_DIR_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; ULONG EaSize; ULONG ReparsePointTag; FILE_ID_128 FileId; CCHAR ShortNameLength; WCHAR ShortName[12]; WCHAR FileName[1]; } FILE_ID_EXTD_BOTH_DIR_INFORMATION, *PFILE_ID_EXTD_BOTH_DIR_INFORMATION; typedef struct _FILE_VALID_DATA_LENGTH_INFORMATION { LARGE_INTEGER ValidDataLength; } FILE_VALID_DATA_LENGTH_INFORMATION, *PFILE_VALID_DATA_LENGTH_INFORMATION; typedef struct _FILE_REPARSE_POINT_INFORMATION { LONGLONG FileReference; ULONG Tag; } FILE_REPARSE_POINT_INFORMATION, *PFILE_REPARSE_POINT_INFORMATION; typedef struct _REQUEST_OPLOCK_INPUT_BUFFER { WORD StructureVersion; WORD StructureLength; DWORD RequestedOplockLevel; DWORD Flags; } REQUEST_OPLOCK_INPUT_BUFFER, *PREQUEST_OPLOCK_INPUT_BUFFER; typedef struct _REQUEST_OPLOCK_OUTPUT_BUFFER { WORD StructureVersion; WORD StructureLength; DWORD OriginalOplockLevel; DWORD NewOplockLevel; DWORD Flags; ACCESS_MASK AccessMode; WORD ShareMode; } REQUEST_OPLOCK_OUTPUT_BUFFER, *PREQUEST_OPLOCK_OUTPUT_BUFFER; #define FileStatInformation ((FILE_INFORMATION_CLASS)68) typedef struct _FILE_STAT_INFORMATION { LARGE_INTEGER FileId; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER AllocationSize; LARGE_INTEGER EndOfFile; ULONG FileAttributes; ULONG ReparseTag; ULONG NumberOfLinks; ACCESS_MASK EffectiveAccess; } FILE_STAT_INFORMATION, *PFILE_STAT_INFORMATION; #define FileStatLxInformation ((FILE_INFORMATION_CLASS)70) typedef struct _FILE_STAT_LX_INFORMATION { LARGE_INTEGER FileId; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER AllocationSize; LARGE_INTEGER EndOfFile; ULONG FileAttributes; ULONG ReparseTag; ULONG NumberOfLinks; ACCESS_MASK EffectiveAccess; ULONG LxFlags; ULONG LxUid; ULONG LxGid; ULONG LxMode; ULONG LxDeviceIdMajor; ULONG LxDeviceIdMinor; } FILE_STAT_LX_INFORMATION, *PFILE_STAT_LX_INFORMATION; typedef struct _FILE_STANDARD_LINK_INFORMATION { ULONG NumberOfAccessibleLinks; ULONG TotalNumberOfLinks; BOOLEAN DeletePending; BOOLEAN Directory; } FILE_STANDARD_LINK_INFORMATION, *PFILE_STANDARD_LINK_INFORMATION; typedef struct _FILE_ID_INFORMATION { ULONGLONG VolumeSerialNumber; FILE_ID_128 FileId; } FILE_ID_INFORMATION, *PFILE_ID_INFORMATION; class handle_closer { public: typedef HANDLE pointer; void operator()(HANDLE h) { if (h == INVALID_HANDLE_VALUE) return; NtClose(h); } }; typedef std::unique_ptr unique_handle; static __inline std::string ntstatus_to_string(NTSTATUS s) { switch (s) { case STATUS_KERNEL_APC: return "STATUS_KERNEL_APC"; case STATUS_DEVICE_POWER_FAILURE: return "STATUS_DEVICE_POWER_FAILURE"; case STATUS_ABIOS_NOT_PRESENT: return "STATUS_ABIOS_NOT_PRESENT"; case STATUS_ABIOS_LID_NOT_EXIST: return "STATUS_ABIOS_LID_NOT_EXIST"; case STATUS_ABIOS_LID_ALREADY_OWNED: return "STATUS_ABIOS_LID_ALREADY_OWNED"; case STATUS_ABIOS_NOT_LID_OWNER: return "STATUS_ABIOS_NOT_LID_OWNER"; case STATUS_ABIOS_INVALID_COMMAND: return "STATUS_ABIOS_INVALID_COMMAND"; case STATUS_ABIOS_INVALID_LID: return "STATUS_ABIOS_INVALID_LID"; case STATUS_ABIOS_SELECTOR_NOT_AVAILABLE: return "STATUS_ABIOS_SELECTOR_NOT_AVAILABLE"; case STATUS_ABIOS_INVALID_SELECTOR: return "STATUS_ABIOS_INVALID_SELECTOR"; case STATUS_MULTIPLE_FAULT_VIOLATION: return "STATUS_MULTIPLE_FAULT_VIOLATION"; case STATUS_SUCCESS: return "STATUS_SUCCESS"; case STATUS_WAIT_1: return "STATUS_WAIT_1"; case STATUS_WAIT_2: return "STATUS_WAIT_2"; case STATUS_WAIT_3: return "STATUS_WAIT_3"; case STATUS_WAIT_63: return "STATUS_WAIT_63"; case STATUS_ABANDONED: return "STATUS_ABANDONED"; case STATUS_ABANDONED_WAIT_63: return "STATUS_ABANDONED_WAIT_63"; case STATUS_USER_APC: return "STATUS_USER_APC"; case STATUS_ALERTED: return "STATUS_ALERTED"; case STATUS_TIMEOUT: return "STATUS_TIMEOUT"; case STATUS_PENDING: return "STATUS_PENDING"; case STATUS_REPARSE: return "STATUS_REPARSE"; case STATUS_MORE_ENTRIES: return "STATUS_MORE_ENTRIES"; case STATUS_NOT_ALL_ASSIGNED: return "STATUS_NOT_ALL_ASSIGNED"; case STATUS_SOME_NOT_MAPPED: return "STATUS_SOME_NOT_MAPPED"; case STATUS_OPLOCK_BREAK_IN_PROGRESS: return "STATUS_OPLOCK_BREAK_IN_PROGRESS"; case STATUS_VOLUME_MOUNTED: return "STATUS_VOLUME_MOUNTED"; case STATUS_RXACT_COMMITTED: return "STATUS_RXACT_COMMITTED"; case STATUS_NOTIFY_CLEANUP: return "STATUS_NOTIFY_CLEANUP"; case STATUS_NOTIFY_ENUM_DIR: return "STATUS_NOTIFY_ENUM_DIR"; case STATUS_NO_QUOTAS_FOR_ACCOUNT: return "STATUS_NO_QUOTAS_FOR_ACCOUNT"; case STATUS_PRIMARY_TRANSPORT_CONNECT_FAILED: return "STATUS_PRIMARY_TRANSPORT_CONNECT_FAILED"; case STATUS_PAGE_FAULT_TRANSITION: return "STATUS_PAGE_FAULT_TRANSITION"; case STATUS_PAGE_FAULT_DEMAND_ZERO: return "STATUS_PAGE_FAULT_DEMAND_ZERO"; case STATUS_PAGE_FAULT_COPY_ON_WRITE: return "STATUS_PAGE_FAULT_COPY_ON_WRITE"; case STATUS_PAGE_FAULT_GUARD_PAGE: return "STATUS_PAGE_FAULT_GUARD_PAGE"; case STATUS_PAGE_FAULT_PAGING_FILE: return "STATUS_PAGE_FAULT_PAGING_FILE"; case STATUS_CACHE_PAGE_LOCKED: return "STATUS_CACHE_PAGE_LOCKED"; case STATUS_CRASH_DUMP: return "STATUS_CRASH_DUMP"; case STATUS_BUFFER_ALL_ZEROS: return "STATUS_BUFFER_ALL_ZEROS"; case STATUS_REPARSE_OBJECT: return "STATUS_REPARSE_OBJECT"; case STATUS_RESOURCE_REQUIREMENTS_CHANGED: return "STATUS_RESOURCE_REQUIREMENTS_CHANGED"; case STATUS_TRANSLATION_COMPLETE: return "STATUS_TRANSLATION_COMPLETE"; case STATUS_DS_MEMBERSHIP_EVALUATED_LOCALLY: return "STATUS_DS_MEMBERSHIP_EVALUATED_LOCALLY"; case STATUS_NOTHING_TO_TERMINATE: return "STATUS_NOTHING_TO_TERMINATE"; case STATUS_PROCESS_NOT_IN_JOB: return "STATUS_PROCESS_NOT_IN_JOB"; case STATUS_PROCESS_IN_JOB: return "STATUS_PROCESS_IN_JOB"; case STATUS_VOLSNAP_HIBERNATE_READY: return "STATUS_VOLSNAP_HIBERNATE_READY"; case STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY: return "STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY"; case STATUS_INTERRUPT_VECTOR_ALREADY_CONNECTED: return "STATUS_INTERRUPT_VECTOR_ALREADY_CONNECTED"; case STATUS_INTERRUPT_STILL_CONNECTED: return "STATUS_INTERRUPT_STILL_CONNECTED"; case STATUS_PROCESS_CLONED: return "STATUS_PROCESS_CLONED"; case STATUS_FILE_LOCKED_WITH_ONLY_READERS: return "STATUS_FILE_LOCKED_WITH_ONLY_READERS"; case STATUS_FILE_LOCKED_WITH_WRITERS: return "STATUS_FILE_LOCKED_WITH_WRITERS"; case STATUS_RESOURCEMANAGER_READ_ONLY: return "STATUS_RESOURCEMANAGER_READ_ONLY"; case STATUS_WAIT_FOR_OPLOCK: return "STATUS_WAIT_FOR_OPLOCK"; case DBG_EXCEPTION_HANDLED: return "DBG_EXCEPTION_HANDLED"; case DBG_CONTINUE: return "DBG_CONTINUE"; case STATUS_FLT_IO_COMPLETE: return "STATUS_FLT_IO_COMPLETE"; case STATUS_FILE_NOT_AVAILABLE: return "STATUS_FILE_NOT_AVAILABLE"; case STATUS_OBJECT_NAME_EXISTS: return "STATUS_OBJECT_NAME_EXISTS"; case STATUS_THREAD_WAS_SUSPENDED: return "STATUS_THREAD_WAS_SUSPENDED"; case STATUS_WORKING_SET_LIMIT_RANGE: return "STATUS_WORKING_SET_LIMIT_RANGE"; case STATUS_IMAGE_NOT_AT_BASE: return "STATUS_IMAGE_NOT_AT_BASE"; case STATUS_RXACT_STATE_CREATED: return "STATUS_RXACT_STATE_CREATED"; case STATUS_SEGMENT_NOTIFICATION: return "STATUS_SEGMENT_NOTIFICATION"; case STATUS_LOCAL_USER_SESSION_KEY: return "STATUS_LOCAL_USER_SESSION_KEY"; case STATUS_BAD_CURRENT_DIRECTORY: return "STATUS_BAD_CURRENT_DIRECTORY"; case STATUS_SERIAL_MORE_WRITES: return "STATUS_SERIAL_MORE_WRITES"; case STATUS_REGISTRY_RECOVERED: return "STATUS_REGISTRY_RECOVERED"; case STATUS_FT_READ_RECOVERY_FROM_BACKUP: return "STATUS_FT_READ_RECOVERY_FROM_BACKUP"; case STATUS_FT_WRITE_RECOVERY: return "STATUS_FT_WRITE_RECOVERY"; case STATUS_SERIAL_COUNTER_TIMEOUT: return "STATUS_SERIAL_COUNTER_TIMEOUT"; case STATUS_NULL_LM_PASSWORD: return "STATUS_NULL_LM_PASSWORD"; case STATUS_IMAGE_MACHINE_TYPE_MISMATCH: return "STATUS_IMAGE_MACHINE_TYPE_MISMATCH"; case STATUS_RECEIVE_PARTIAL: return "STATUS_RECEIVE_PARTIAL"; case STATUS_RECEIVE_EXPEDITED: return "STATUS_RECEIVE_EXPEDITED"; case STATUS_RECEIVE_PARTIAL_EXPEDITED: return "STATUS_RECEIVE_PARTIAL_EXPEDITED"; case STATUS_EVENT_DONE: return "STATUS_EVENT_DONE"; case STATUS_EVENT_PENDING: return "STATUS_EVENT_PENDING"; case STATUS_CHECKING_FILE_SYSTEM: return "STATUS_CHECKING_FILE_SYSTEM"; case STATUS_FATAL_APP_EXIT: return "STATUS_FATAL_APP_EXIT"; case STATUS_PREDEFINED_HANDLE: return "STATUS_PREDEFINED_HANDLE"; case STATUS_WAS_UNLOCKED: return "STATUS_WAS_UNLOCKED"; case STATUS_SERVICE_NOTIFICATION: return "STATUS_SERVICE_NOTIFICATION"; case STATUS_WAS_LOCKED: return "STATUS_WAS_LOCKED"; case STATUS_LOG_HARD_ERROR: return "STATUS_LOG_HARD_ERROR"; case STATUS_ALREADY_WIN32: return "STATUS_ALREADY_WIN32"; case STATUS_WX86_UNSIMULATE: return "STATUS_WX86_UNSIMULATE"; case STATUS_WX86_CONTINUE: return "STATUS_WX86_CONTINUE"; case STATUS_WX86_SINGLE_STEP: return "STATUS_WX86_SINGLE_STEP"; case STATUS_WX86_BREAKPOINT: return "STATUS_WX86_BREAKPOINT"; case STATUS_WX86_EXCEPTION_CONTINUE: return "STATUS_WX86_EXCEPTION_CONTINUE"; case STATUS_WX86_EXCEPTION_LASTCHANCE: return "STATUS_WX86_EXCEPTION_LASTCHANCE"; case STATUS_WX86_EXCEPTION_CHAIN: return "STATUS_WX86_EXCEPTION_CHAIN"; case STATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE: return "STATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE"; case STATUS_NO_YIELD_PERFORMED: return "STATUS_NO_YIELD_PERFORMED"; case STATUS_TIMER_RESUME_IGNORED: return "STATUS_TIMER_RESUME_IGNORED"; case STATUS_ARBITRATION_UNHANDLED: return "STATUS_ARBITRATION_UNHANDLED"; case STATUS_CARDBUS_NOT_SUPPORTED: return "STATUS_CARDBUS_NOT_SUPPORTED"; case STATUS_WX86_CREATEWX86TIB: return "STATUS_WX86_CREATEWX86TIB"; case STATUS_MP_PROCESSOR_MISMATCH: return "STATUS_MP_PROCESSOR_MISMATCH"; case STATUS_HIBERNATED: return "STATUS_HIBERNATED"; case STATUS_RESUME_HIBERNATION: return "STATUS_RESUME_HIBERNATION"; case STATUS_FIRMWARE_UPDATED: return "STATUS_FIRMWARE_UPDATED"; case STATUS_DRIVERS_LEAKING_LOCKED_PAGES: return "STATUS_DRIVERS_LEAKING_LOCKED_PAGES"; case STATUS_MESSAGE_RETRIEVED: return "STATUS_MESSAGE_RETRIEVED"; case STATUS_SYSTEM_POWERSTATE_TRANSITION: return "STATUS_SYSTEM_POWERSTATE_TRANSITION"; case STATUS_ALPC_CHECK_COMPLETION_LIST: return "STATUS_ALPC_CHECK_COMPLETION_LIST"; case STATUS_SYSTEM_POWERSTATE_COMPLEX_TRANSITION: return "STATUS_SYSTEM_POWERSTATE_COMPLEX_TRANSITION"; case STATUS_ACCESS_AUDIT_BY_POLICY: return "STATUS_ACCESS_AUDIT_BY_POLICY"; case STATUS_ABANDON_HIBERFILE: return "STATUS_ABANDON_HIBERFILE"; case STATUS_BIZRULES_NOT_ENABLED: return "STATUS_BIZRULES_NOT_ENABLED"; case STATUS_WAKE_SYSTEM: return "STATUS_WAKE_SYSTEM"; case STATUS_DS_SHUTTING_DOWN: return "STATUS_DS_SHUTTING_DOWN"; case DBG_REPLY_LATER: return "DBG_REPLY_LATER"; case DBG_UNABLE_TO_PROVIDE_HANDLE: return "DBG_UNABLE_TO_PROVIDE_HANDLE"; case DBG_TERMINATE_THREAD: return "DBG_TERMINATE_THREAD"; case DBG_TERMINATE_PROCESS: return "DBG_TERMINATE_PROCESS"; case DBG_CONTROL_C: return "DBG_CONTROL_C"; case DBG_PRINTEXCEPTION_C: return "DBG_PRINTEXCEPTION_C"; case DBG_RIPEXCEPTION: return "DBG_RIPEXCEPTION"; case DBG_CONTROL_BREAK: return "DBG_CONTROL_BREAK"; case DBG_COMMAND_EXCEPTION: return "DBG_COMMAND_EXCEPTION"; case DBG_PRINTEXCEPTION_WIDE_C: return "DBG_PRINTEXCEPTION_WIDE_C"; case RPC_NT_UUID_LOCAL_ONLY: return "RPC_NT_UUID_LOCAL_ONLY"; case RPC_NT_SEND_INCOMPLETE: return "RPC_NT_SEND_INCOMPLETE"; case STATUS_CTX_CDM_CONNECT: return "STATUS_CTX_CDM_CONNECT"; case STATUS_CTX_CDM_DISCONNECT: return "STATUS_CTX_CDM_DISCONNECT"; case STATUS_SXS_RELEASE_ACTIVATION_CONTEXT: return "STATUS_SXS_RELEASE_ACTIVATION_CONTEXT"; case STATUS_RECOVERY_NOT_NEEDED: return "STATUS_RECOVERY_NOT_NEEDED"; case STATUS_RM_ALREADY_STARTED: return "STATUS_RM_ALREADY_STARTED"; case STATUS_LOG_NO_RESTART: return "STATUS_LOG_NO_RESTART"; case STATUS_VIDEO_DRIVER_DEBUG_REPORT_REQUEST: return "STATUS_VIDEO_DRIVER_DEBUG_REPORT_REQUEST"; case STATUS_GRAPHICS_PARTIAL_DATA_POPULATED: return "STATUS_GRAPHICS_PARTIAL_DATA_POPULATED"; case STATUS_GRAPHICS_DRIVER_MISMATCH: return "STATUS_GRAPHICS_DRIVER_MISMATCH"; case STATUS_GRAPHICS_MODE_NOT_PINNED: return "STATUS_GRAPHICS_MODE_NOT_PINNED"; case STATUS_GRAPHICS_NO_PREFERRED_MODE: return "STATUS_GRAPHICS_NO_PREFERRED_MODE"; case STATUS_GRAPHICS_DATASET_IS_EMPTY: return "STATUS_GRAPHICS_DATASET_IS_EMPTY"; case STATUS_GRAPHICS_NO_MORE_ELEMENTS_IN_DATASET: return "STATUS_GRAPHICS_NO_MORE_ELEMENTS_IN_DATASET"; case STATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_PINNED: return "STATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_PINNED"; case STATUS_GRAPHICS_UNKNOWN_CHILD_STATUS: return "STATUS_GRAPHICS_UNKNOWN_CHILD_STATUS"; case STATUS_GRAPHICS_LEADLINK_START_DEFERRED: return "STATUS_GRAPHICS_LEADLINK_START_DEFERRED"; case STATUS_GRAPHICS_POLLING_TOO_FREQUENTLY: return "STATUS_GRAPHICS_POLLING_TOO_FREQUENTLY"; case STATUS_GRAPHICS_START_DEFERRED: return "STATUS_GRAPHICS_START_DEFERRED"; case STATUS_NDIS_INDICATION_REQUIRED: return "STATUS_NDIS_INDICATION_REQUIRED"; case STATUS_GUARD_PAGE_VIOLATION: return "STATUS_GUARD_PAGE_VIOLATION"; case STATUS_DATATYPE_MISALIGNMENT: return "STATUS_DATATYPE_MISALIGNMENT"; case STATUS_BREAKPOINT: return "STATUS_BREAKPOINT"; case STATUS_SINGLE_STEP: return "STATUS_SINGLE_STEP"; case STATUS_BUFFER_OVERFLOW: return "STATUS_BUFFER_OVERFLOW"; case STATUS_NO_MORE_FILES: return "STATUS_NO_MORE_FILES"; case STATUS_WAKE_SYSTEM_DEBUGGER: return "STATUS_WAKE_SYSTEM_DEBUGGER"; case STATUS_HANDLES_CLOSED: return "STATUS_HANDLES_CLOSED"; case STATUS_NO_INHERITANCE: return "STATUS_NO_INHERITANCE"; case STATUS_GUID_SUBSTITUTION_MADE: return "STATUS_GUID_SUBSTITUTION_MADE"; case STATUS_PARTIAL_COPY: return "STATUS_PARTIAL_COPY"; case STATUS_DEVICE_PAPER_EMPTY: return "STATUS_DEVICE_PAPER_EMPTY"; case STATUS_DEVICE_POWERED_OFF: return "STATUS_DEVICE_POWERED_OFF"; case STATUS_DEVICE_OFF_LINE: return "STATUS_DEVICE_OFF_LINE"; case STATUS_DEVICE_BUSY: return "STATUS_DEVICE_BUSY"; case STATUS_NO_MORE_EAS: return "STATUS_NO_MORE_EAS"; case STATUS_INVALID_EA_NAME: return "STATUS_INVALID_EA_NAME"; case STATUS_EA_LIST_INCONSISTENT: return "STATUS_EA_LIST_INCONSISTENT"; case STATUS_INVALID_EA_FLAG: return "STATUS_INVALID_EA_FLAG"; case STATUS_VERIFY_REQUIRED: return "STATUS_VERIFY_REQUIRED"; case STATUS_EXTRANEOUS_INFORMATION: return "STATUS_EXTRANEOUS_INFORMATION"; case STATUS_RXACT_COMMIT_NECESSARY: return "STATUS_RXACT_COMMIT_NECESSARY"; case STATUS_NO_MORE_ENTRIES: return "STATUS_NO_MORE_ENTRIES"; case STATUS_FILEMARK_DETECTED: return "STATUS_FILEMARK_DETECTED"; case STATUS_MEDIA_CHANGED: return "STATUS_MEDIA_CHANGED"; case STATUS_BUS_RESET: return "STATUS_BUS_RESET"; case STATUS_END_OF_MEDIA: return "STATUS_END_OF_MEDIA"; case STATUS_BEGINNING_OF_MEDIA: return "STATUS_BEGINNING_OF_MEDIA"; case STATUS_MEDIA_CHECK: return "STATUS_MEDIA_CHECK"; case STATUS_SETMARK_DETECTED: return "STATUS_SETMARK_DETECTED"; case STATUS_NO_DATA_DETECTED: return "STATUS_NO_DATA_DETECTED"; case STATUS_REDIRECTOR_HAS_OPEN_HANDLES: return "STATUS_REDIRECTOR_HAS_OPEN_HANDLES"; case STATUS_SERVER_HAS_OPEN_HANDLES: return "STATUS_SERVER_HAS_OPEN_HANDLES"; case STATUS_ALREADY_DISCONNECTED: return "STATUS_ALREADY_DISCONNECTED"; case STATUS_LONGJUMP: return "STATUS_LONGJUMP"; case STATUS_CLEANER_CARTRIDGE_INSTALLED: return "STATUS_CLEANER_CARTRIDGE_INSTALLED"; case STATUS_PLUGPLAY_QUERY_VETOED: return "STATUS_PLUGPLAY_QUERY_VETOED"; case STATUS_UNWIND_CONSOLIDATE: return "STATUS_UNWIND_CONSOLIDATE"; case STATUS_REGISTRY_HIVE_RECOVERED: return "STATUS_REGISTRY_HIVE_RECOVERED"; case STATUS_DLL_MIGHT_BE_INSECURE: return "STATUS_DLL_MIGHT_BE_INSECURE"; case STATUS_DLL_MIGHT_BE_INCOMPATIBLE: return "STATUS_DLL_MIGHT_BE_INCOMPATIBLE"; case STATUS_STOPPED_ON_SYMLINK: return "STATUS_STOPPED_ON_SYMLINK"; case STATUS_DEVICE_REQUIRES_CLEANING: return "STATUS_DEVICE_REQUIRES_CLEANING"; case STATUS_DEVICE_DOOR_OPEN: return "STATUS_DEVICE_DOOR_OPEN"; case STATUS_DATA_LOST_REPAIR: return "STATUS_DATA_LOST_REPAIR"; case DBG_EXCEPTION_NOT_HANDLED: return "DBG_EXCEPTION_NOT_HANDLED"; case STATUS_CLUSTER_NODE_ALREADY_UP: return "STATUS_CLUSTER_NODE_ALREADY_UP"; case STATUS_CLUSTER_NODE_ALREADY_DOWN: return "STATUS_CLUSTER_NODE_ALREADY_DOWN"; case STATUS_CLUSTER_NETWORK_ALREADY_ONLINE: return "STATUS_CLUSTER_NETWORK_ALREADY_ONLINE"; case STATUS_CLUSTER_NETWORK_ALREADY_OFFLINE: return "STATUS_CLUSTER_NETWORK_ALREADY_OFFLINE"; case STATUS_CLUSTER_NODE_ALREADY_MEMBER: return "STATUS_CLUSTER_NODE_ALREADY_MEMBER"; case STATUS_COULD_NOT_RESIZE_LOG: return "STATUS_COULD_NOT_RESIZE_LOG"; case STATUS_NO_TXF_METADATA: return "STATUS_NO_TXF_METADATA"; case STATUS_CANT_RECOVER_WITH_HANDLE_OPEN: return "STATUS_CANT_RECOVER_WITH_HANDLE_OPEN"; case STATUS_TXF_METADATA_ALREADY_PRESENT: return "STATUS_TXF_METADATA_ALREADY_PRESENT"; case STATUS_TRANSACTION_SCOPE_CALLBACKS_NOT_SET: return "STATUS_TRANSACTION_SCOPE_CALLBACKS_NOT_SET"; case STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD_RECOVERED: return "STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD_RECOVERED"; case STATUS_FLT_BUFFER_TOO_SMALL: return "STATUS_FLT_BUFFER_TOO_SMALL"; case STATUS_FVE_PARTIAL_METADATA: return "STATUS_FVE_PARTIAL_METADATA"; case STATUS_FVE_TRANSIENT_STATE: return "STATUS_FVE_TRANSIENT_STATE"; case STATUS_UNSUCCESSFUL: return "STATUS_UNSUCCESSFUL"; case STATUS_NOT_IMPLEMENTED: return "STATUS_NOT_IMPLEMENTED"; case STATUS_INVALID_INFO_CLASS: return "STATUS_INVALID_INFO_CLASS"; case STATUS_INFO_LENGTH_MISMATCH: return "STATUS_INFO_LENGTH_MISMATCH"; case STATUS_ACCESS_VIOLATION: return "STATUS_ACCESS_VIOLATION"; case STATUS_IN_PAGE_ERROR: return "STATUS_IN_PAGE_ERROR"; case STATUS_PAGEFILE_QUOTA: return "STATUS_PAGEFILE_QUOTA"; case STATUS_INVALID_HANDLE: return "STATUS_INVALID_HANDLE"; case STATUS_BAD_INITIAL_STACK: return "STATUS_BAD_INITIAL_STACK"; case STATUS_BAD_INITIAL_PC: return "STATUS_BAD_INITIAL_PC"; case STATUS_INVALID_CID: return "STATUS_INVALID_CID"; case STATUS_TIMER_NOT_CANCELED: return "STATUS_TIMER_NOT_CANCELED"; case STATUS_INVALID_PARAMETER: return "STATUS_INVALID_PARAMETER"; case STATUS_NO_SUCH_DEVICE: return "STATUS_NO_SUCH_DEVICE"; case STATUS_NO_SUCH_FILE: return "STATUS_NO_SUCH_FILE"; case STATUS_INVALID_DEVICE_REQUEST: return "STATUS_INVALID_DEVICE_REQUEST"; case STATUS_END_OF_FILE: return "STATUS_END_OF_FILE"; case STATUS_WRONG_VOLUME: return "STATUS_WRONG_VOLUME"; case STATUS_NO_MEDIA_IN_DEVICE: return "STATUS_NO_MEDIA_IN_DEVICE"; case STATUS_UNRECOGNIZED_MEDIA: return "STATUS_UNRECOGNIZED_MEDIA"; case STATUS_NONEXISTENT_SECTOR: return "STATUS_NONEXISTENT_SECTOR"; case STATUS_MORE_PROCESSING_REQUIRED: return "STATUS_MORE_PROCESSING_REQUIRED"; case STATUS_NO_MEMORY: return "STATUS_NO_MEMORY"; case STATUS_CONFLICTING_ADDRESSES: return "STATUS_CONFLICTING_ADDRESSES"; case STATUS_NOT_MAPPED_VIEW: return "STATUS_NOT_MAPPED_VIEW"; case STATUS_UNABLE_TO_FREE_VM: return "STATUS_UNABLE_TO_FREE_VM"; case STATUS_UNABLE_TO_DELETE_SECTION: return "STATUS_UNABLE_TO_DELETE_SECTION"; case STATUS_INVALID_SYSTEM_SERVICE: return "STATUS_INVALID_SYSTEM_SERVICE"; case STATUS_ILLEGAL_INSTRUCTION: return "STATUS_ILLEGAL_INSTRUCTION"; case STATUS_INVALID_LOCK_SEQUENCE: return "STATUS_INVALID_LOCK_SEQUENCE"; case STATUS_INVALID_VIEW_SIZE: return "STATUS_INVALID_VIEW_SIZE"; case STATUS_INVALID_FILE_FOR_SECTION: return "STATUS_INVALID_FILE_FOR_SECTION"; case STATUS_ALREADY_COMMITTED: return "STATUS_ALREADY_COMMITTED"; case STATUS_ACCESS_DENIED: return "STATUS_ACCESS_DENIED"; case STATUS_BUFFER_TOO_SMALL: return "STATUS_BUFFER_TOO_SMALL"; case STATUS_OBJECT_TYPE_MISMATCH: return "STATUS_OBJECT_TYPE_MISMATCH"; case STATUS_NONCONTINUABLE_EXCEPTION: return "STATUS_NONCONTINUABLE_EXCEPTION"; case STATUS_INVALID_DISPOSITION: return "STATUS_INVALID_DISPOSITION"; case STATUS_UNWIND: return "STATUS_UNWIND"; case STATUS_BAD_STACK: return "STATUS_BAD_STACK"; case STATUS_INVALID_UNWIND_TARGET: return "STATUS_INVALID_UNWIND_TARGET"; case STATUS_NOT_LOCKED: return "STATUS_NOT_LOCKED"; case STATUS_PARITY_ERROR: return "STATUS_PARITY_ERROR"; case STATUS_UNABLE_TO_DECOMMIT_VM: return "STATUS_UNABLE_TO_DECOMMIT_VM"; case STATUS_NOT_COMMITTED: return "STATUS_NOT_COMMITTED"; case STATUS_INVALID_PORT_ATTRIBUTES: return "STATUS_INVALID_PORT_ATTRIBUTES"; case STATUS_PORT_MESSAGE_TOO_LONG: return "STATUS_PORT_MESSAGE_TOO_LONG"; case STATUS_INVALID_PARAMETER_MIX: return "STATUS_INVALID_PARAMETER_MIX"; case STATUS_INVALID_QUOTA_LOWER: return "STATUS_INVALID_QUOTA_LOWER"; case STATUS_DISK_CORRUPT_ERROR: return "STATUS_DISK_CORRUPT_ERROR"; case STATUS_OBJECT_NAME_INVALID: return "STATUS_OBJECT_NAME_INVALID"; case STATUS_OBJECT_NAME_NOT_FOUND: return "STATUS_OBJECT_NAME_NOT_FOUND"; case STATUS_OBJECT_NAME_COLLISION: return "STATUS_OBJECT_NAME_COLLISION"; case STATUS_PORT_DISCONNECTED: return "STATUS_PORT_DISCONNECTED"; case STATUS_DEVICE_ALREADY_ATTACHED: return "STATUS_DEVICE_ALREADY_ATTACHED"; case STATUS_OBJECT_PATH_INVALID: return "STATUS_OBJECT_PATH_INVALID"; case STATUS_OBJECT_PATH_NOT_FOUND: return "STATUS_OBJECT_PATH_NOT_FOUND"; case STATUS_OBJECT_PATH_SYNTAX_BAD: return "STATUS_OBJECT_PATH_SYNTAX_BAD"; case STATUS_DATA_OVERRUN: return "STATUS_DATA_OVERRUN"; case STATUS_DATA_LATE_ERROR: return "STATUS_DATA_LATE_ERROR"; case STATUS_DATA_ERROR: return "STATUS_DATA_ERROR"; case STATUS_CRC_ERROR: return "STATUS_CRC_ERROR"; case STATUS_SECTION_TOO_BIG: return "STATUS_SECTION_TOO_BIG"; case STATUS_PORT_CONNECTION_REFUSED: return "STATUS_PORT_CONNECTION_REFUSED"; case STATUS_INVALID_PORT_HANDLE: return "STATUS_INVALID_PORT_HANDLE"; case STATUS_SHARING_VIOLATION: return "STATUS_SHARING_VIOLATION"; case STATUS_QUOTA_EXCEEDED: return "STATUS_QUOTA_EXCEEDED"; case STATUS_INVALID_PAGE_PROTECTION: return "STATUS_INVALID_PAGE_PROTECTION"; case STATUS_MUTANT_NOT_OWNED: return "STATUS_MUTANT_NOT_OWNED"; case STATUS_SEMAPHORE_LIMIT_EXCEEDED: return "STATUS_SEMAPHORE_LIMIT_EXCEEDED"; case STATUS_PORT_ALREADY_SET: return "STATUS_PORT_ALREADY_SET"; case STATUS_SECTION_NOT_IMAGE: return "STATUS_SECTION_NOT_IMAGE"; case STATUS_SUSPEND_COUNT_EXCEEDED: return "STATUS_SUSPEND_COUNT_EXCEEDED"; case STATUS_THREAD_IS_TERMINATING: return "STATUS_THREAD_IS_TERMINATING"; case STATUS_BAD_WORKING_SET_LIMIT: return "STATUS_BAD_WORKING_SET_LIMIT"; case STATUS_INCOMPATIBLE_FILE_MAP: return "STATUS_INCOMPATIBLE_FILE_MAP"; case STATUS_SECTION_PROTECTION: return "STATUS_SECTION_PROTECTION"; case STATUS_EAS_NOT_SUPPORTED: return "STATUS_EAS_NOT_SUPPORTED"; case STATUS_EA_TOO_LARGE: return "STATUS_EA_TOO_LARGE"; case STATUS_NONEXISTENT_EA_ENTRY: return "STATUS_NONEXISTENT_EA_ENTRY"; case STATUS_NO_EAS_ON_FILE: return "STATUS_NO_EAS_ON_FILE"; case STATUS_EA_CORRUPT_ERROR: return "STATUS_EA_CORRUPT_ERROR"; case STATUS_FILE_LOCK_CONFLICT: return "STATUS_FILE_LOCK_CONFLICT"; case STATUS_LOCK_NOT_GRANTED: return "STATUS_LOCK_NOT_GRANTED"; case STATUS_DELETE_PENDING: return "STATUS_DELETE_PENDING"; case STATUS_CTL_FILE_NOT_SUPPORTED: return "STATUS_CTL_FILE_NOT_SUPPORTED"; case STATUS_UNKNOWN_REVISION: return "STATUS_UNKNOWN_REVISION"; case STATUS_REVISION_MISMATCH: return "STATUS_REVISION_MISMATCH"; case STATUS_INVALID_OWNER: return "STATUS_INVALID_OWNER"; case STATUS_INVALID_PRIMARY_GROUP: return "STATUS_INVALID_PRIMARY_GROUP"; case STATUS_NO_IMPERSONATION_TOKEN: return "STATUS_NO_IMPERSONATION_TOKEN"; case STATUS_CANT_DISABLE_MANDATORY: return "STATUS_CANT_DISABLE_MANDATORY"; case STATUS_NO_LOGON_SERVERS: return "STATUS_NO_LOGON_SERVERS"; case STATUS_NO_SUCH_LOGON_SESSION: return "STATUS_NO_SUCH_LOGON_SESSION"; case STATUS_NO_SUCH_PRIVILEGE: return "STATUS_NO_SUCH_PRIVILEGE"; case STATUS_PRIVILEGE_NOT_HELD: return "STATUS_PRIVILEGE_NOT_HELD"; case STATUS_INVALID_ACCOUNT_NAME: return "STATUS_INVALID_ACCOUNT_NAME"; case STATUS_USER_EXISTS: return "STATUS_USER_EXISTS"; case STATUS_NO_SUCH_USER: return "STATUS_NO_SUCH_USER"; case STATUS_GROUP_EXISTS: return "STATUS_GROUP_EXISTS"; case STATUS_NO_SUCH_GROUP: return "STATUS_NO_SUCH_GROUP"; case STATUS_MEMBER_IN_GROUP: return "STATUS_MEMBER_IN_GROUP"; case STATUS_MEMBER_NOT_IN_GROUP: return "STATUS_MEMBER_NOT_IN_GROUP"; case STATUS_LAST_ADMIN: return "STATUS_LAST_ADMIN"; case STATUS_WRONG_PASSWORD: return "STATUS_WRONG_PASSWORD"; case STATUS_ILL_FORMED_PASSWORD: return "STATUS_ILL_FORMED_PASSWORD"; case STATUS_PASSWORD_RESTRICTION: return "STATUS_PASSWORD_RESTRICTION"; case STATUS_LOGON_FAILURE: return "STATUS_LOGON_FAILURE"; case STATUS_ACCOUNT_RESTRICTION: return "STATUS_ACCOUNT_RESTRICTION"; case STATUS_INVALID_LOGON_HOURS: return "STATUS_INVALID_LOGON_HOURS"; case STATUS_INVALID_WORKSTATION: return "STATUS_INVALID_WORKSTATION"; case STATUS_PASSWORD_EXPIRED: return "STATUS_PASSWORD_EXPIRED"; case STATUS_ACCOUNT_DISABLED: return "STATUS_ACCOUNT_DISABLED"; case STATUS_NONE_MAPPED: return "STATUS_NONE_MAPPED"; case STATUS_TOO_MANY_LUIDS_REQUESTED: return "STATUS_TOO_MANY_LUIDS_REQUESTED"; case STATUS_LUIDS_EXHAUSTED: return "STATUS_LUIDS_EXHAUSTED"; case STATUS_INVALID_SUB_AUTHORITY: return "STATUS_INVALID_SUB_AUTHORITY"; case STATUS_INVALID_ACL: return "STATUS_INVALID_ACL"; case STATUS_INVALID_SID: return "STATUS_INVALID_SID"; case STATUS_INVALID_SECURITY_DESCR: return "STATUS_INVALID_SECURITY_DESCR"; case STATUS_PROCEDURE_NOT_FOUND: return "STATUS_PROCEDURE_NOT_FOUND"; case STATUS_INVALID_IMAGE_FORMAT: return "STATUS_INVALID_IMAGE_FORMAT"; case STATUS_NO_TOKEN: return "STATUS_NO_TOKEN"; case STATUS_BAD_INHERITANCE_ACL: return "STATUS_BAD_INHERITANCE_ACL"; case STATUS_RANGE_NOT_LOCKED: return "STATUS_RANGE_NOT_LOCKED"; case STATUS_DISK_FULL: return "STATUS_DISK_FULL"; case STATUS_SERVER_DISABLED: return "STATUS_SERVER_DISABLED"; case STATUS_SERVER_NOT_DISABLED: return "STATUS_SERVER_NOT_DISABLED"; case STATUS_TOO_MANY_GUIDS_REQUESTED: return "STATUS_TOO_MANY_GUIDS_REQUESTED"; case STATUS_GUIDS_EXHAUSTED: return "STATUS_GUIDS_EXHAUSTED"; case STATUS_INVALID_ID_AUTHORITY: return "STATUS_INVALID_ID_AUTHORITY"; case STATUS_AGENTS_EXHAUSTED: return "STATUS_AGENTS_EXHAUSTED"; case STATUS_INVALID_VOLUME_LABEL: return "STATUS_INVALID_VOLUME_LABEL"; case STATUS_SECTION_NOT_EXTENDED: return "STATUS_SECTION_NOT_EXTENDED"; case STATUS_NOT_MAPPED_DATA: return "STATUS_NOT_MAPPED_DATA"; case STATUS_RESOURCE_DATA_NOT_FOUND: return "STATUS_RESOURCE_DATA_NOT_FOUND"; case STATUS_RESOURCE_TYPE_NOT_FOUND: return "STATUS_RESOURCE_TYPE_NOT_FOUND"; case STATUS_RESOURCE_NAME_NOT_FOUND: return "STATUS_RESOURCE_NAME_NOT_FOUND"; case STATUS_ARRAY_BOUNDS_EXCEEDED: return "STATUS_ARRAY_BOUNDS_EXCEEDED"; case STATUS_FLOAT_DENORMAL_OPERAND: return "STATUS_FLOAT_DENORMAL_OPERAND"; case STATUS_FLOAT_DIVIDE_BY_ZERO: return "STATUS_FLOAT_DIVIDE_BY_ZERO"; case STATUS_FLOAT_INEXACT_RESULT: return "STATUS_FLOAT_INEXACT_RESULT"; case STATUS_FLOAT_INVALID_OPERATION: return "STATUS_FLOAT_INVALID_OPERATION"; case STATUS_FLOAT_OVERFLOW: return "STATUS_FLOAT_OVERFLOW"; case STATUS_FLOAT_STACK_CHECK: return "STATUS_FLOAT_STACK_CHECK"; case STATUS_FLOAT_UNDERFLOW: return "STATUS_FLOAT_UNDERFLOW"; case STATUS_INTEGER_DIVIDE_BY_ZERO: return "STATUS_INTEGER_DIVIDE_BY_ZERO"; case STATUS_INTEGER_OVERFLOW: return "STATUS_INTEGER_OVERFLOW"; case STATUS_PRIVILEGED_INSTRUCTION: return "STATUS_PRIVILEGED_INSTRUCTION"; case STATUS_TOO_MANY_PAGING_FILES: return "STATUS_TOO_MANY_PAGING_FILES"; case STATUS_FILE_INVALID: return "STATUS_FILE_INVALID"; case STATUS_ALLOTTED_SPACE_EXCEEDED: return "STATUS_ALLOTTED_SPACE_EXCEEDED"; case STATUS_INSUFFICIENT_RESOURCES: return "STATUS_INSUFFICIENT_RESOURCES"; case STATUS_DFS_EXIT_PATH_FOUND: return "STATUS_DFS_EXIT_PATH_FOUND"; case STATUS_DEVICE_DATA_ERROR: return "STATUS_DEVICE_DATA_ERROR"; case STATUS_DEVICE_NOT_CONNECTED: return "STATUS_DEVICE_NOT_CONNECTED"; case STATUS_FREE_VM_NOT_AT_BASE: return "STATUS_FREE_VM_NOT_AT_BASE"; case STATUS_MEMORY_NOT_ALLOCATED: return "STATUS_MEMORY_NOT_ALLOCATED"; case STATUS_WORKING_SET_QUOTA: return "STATUS_WORKING_SET_QUOTA"; case STATUS_MEDIA_WRITE_PROTECTED: return "STATUS_MEDIA_WRITE_PROTECTED"; case STATUS_DEVICE_NOT_READY: return "STATUS_DEVICE_NOT_READY"; case STATUS_INVALID_GROUP_ATTRIBUTES: return "STATUS_INVALID_GROUP_ATTRIBUTES"; case STATUS_BAD_IMPERSONATION_LEVEL: return "STATUS_BAD_IMPERSONATION_LEVEL"; case STATUS_CANT_OPEN_ANONYMOUS: return "STATUS_CANT_OPEN_ANONYMOUS"; case STATUS_BAD_VALIDATION_CLASS: return "STATUS_BAD_VALIDATION_CLASS"; case STATUS_BAD_TOKEN_TYPE: return "STATUS_BAD_TOKEN_TYPE"; case STATUS_BAD_MASTER_BOOT_RECORD: return "STATUS_BAD_MASTER_BOOT_RECORD"; case STATUS_INSTRUCTION_MISALIGNMENT: return "STATUS_INSTRUCTION_MISALIGNMENT"; case STATUS_INSTANCE_NOT_AVAILABLE: return "STATUS_INSTANCE_NOT_AVAILABLE"; case STATUS_PIPE_NOT_AVAILABLE: return "STATUS_PIPE_NOT_AVAILABLE"; case STATUS_INVALID_PIPE_STATE: return "STATUS_INVALID_PIPE_STATE"; case STATUS_PIPE_BUSY: return "STATUS_PIPE_BUSY"; case STATUS_ILLEGAL_FUNCTION: return "STATUS_ILLEGAL_FUNCTION"; case STATUS_PIPE_DISCONNECTED: return "STATUS_PIPE_DISCONNECTED"; case STATUS_PIPE_CLOSING: return "STATUS_PIPE_CLOSING"; case STATUS_PIPE_CONNECTED: return "STATUS_PIPE_CONNECTED"; case STATUS_PIPE_LISTENING: return "STATUS_PIPE_LISTENING"; case STATUS_INVALID_READ_MODE: return "STATUS_INVALID_READ_MODE"; case STATUS_IO_TIMEOUT: return "STATUS_IO_TIMEOUT"; case STATUS_FILE_FORCED_CLOSED: return "STATUS_FILE_FORCED_CLOSED"; case STATUS_PROFILING_NOT_STARTED: return "STATUS_PROFILING_NOT_STARTED"; case STATUS_PROFILING_NOT_STOPPED: return "STATUS_PROFILING_NOT_STOPPED"; case STATUS_COULD_NOT_INTERPRET: return "STATUS_COULD_NOT_INTERPRET"; case STATUS_FILE_IS_A_DIRECTORY: return "STATUS_FILE_IS_A_DIRECTORY"; case STATUS_NOT_SUPPORTED: return "STATUS_NOT_SUPPORTED"; case STATUS_REMOTE_NOT_LISTENING: return "STATUS_REMOTE_NOT_LISTENING"; case STATUS_DUPLICATE_NAME: return "STATUS_DUPLICATE_NAME"; case STATUS_BAD_NETWORK_PATH: return "STATUS_BAD_NETWORK_PATH"; case STATUS_NETWORK_BUSY: return "STATUS_NETWORK_BUSY"; case STATUS_DEVICE_DOES_NOT_EXIST: return "STATUS_DEVICE_DOES_NOT_EXIST"; case STATUS_TOO_MANY_COMMANDS: return "STATUS_TOO_MANY_COMMANDS"; case STATUS_ADAPTER_HARDWARE_ERROR: return "STATUS_ADAPTER_HARDWARE_ERROR"; case STATUS_INVALID_NETWORK_RESPONSE: return "STATUS_INVALID_NETWORK_RESPONSE"; case STATUS_UNEXPECTED_NETWORK_ERROR: return "STATUS_UNEXPECTED_NETWORK_ERROR"; case STATUS_BAD_REMOTE_ADAPTER: return "STATUS_BAD_REMOTE_ADAPTER"; case STATUS_PRINT_QUEUE_FULL: return "STATUS_PRINT_QUEUE_FULL"; case STATUS_NO_SPOOL_SPACE: return "STATUS_NO_SPOOL_SPACE"; case STATUS_PRINT_CANCELLED: return "STATUS_PRINT_CANCELLED"; case STATUS_NETWORK_NAME_DELETED: return "STATUS_NETWORK_NAME_DELETED"; case STATUS_NETWORK_ACCESS_DENIED: return "STATUS_NETWORK_ACCESS_DENIED"; case STATUS_BAD_DEVICE_TYPE: return "STATUS_BAD_DEVICE_TYPE"; case STATUS_BAD_NETWORK_NAME: return "STATUS_BAD_NETWORK_NAME"; case STATUS_TOO_MANY_NAMES: return "STATUS_TOO_MANY_NAMES"; case STATUS_TOO_MANY_SESSIONS: return "STATUS_TOO_MANY_SESSIONS"; case STATUS_SHARING_PAUSED: return "STATUS_SHARING_PAUSED"; case STATUS_REQUEST_NOT_ACCEPTED: return "STATUS_REQUEST_NOT_ACCEPTED"; case STATUS_REDIRECTOR_PAUSED: return "STATUS_REDIRECTOR_PAUSED"; case STATUS_NET_WRITE_FAULT: return "STATUS_NET_WRITE_FAULT"; case STATUS_PROFILING_AT_LIMIT: return "STATUS_PROFILING_AT_LIMIT"; case STATUS_NOT_SAME_DEVICE: return "STATUS_NOT_SAME_DEVICE"; case STATUS_FILE_RENAMED: return "STATUS_FILE_RENAMED"; case STATUS_VIRTUAL_CIRCUIT_CLOSED: return "STATUS_VIRTUAL_CIRCUIT_CLOSED"; case STATUS_NO_SECURITY_ON_OBJECT: return "STATUS_NO_SECURITY_ON_OBJECT"; case STATUS_CANT_WAIT: return "STATUS_CANT_WAIT"; case STATUS_PIPE_EMPTY: return "STATUS_PIPE_EMPTY"; case STATUS_CANT_ACCESS_DOMAIN_INFO: return "STATUS_CANT_ACCESS_DOMAIN_INFO"; case STATUS_CANT_TERMINATE_SELF: return "STATUS_CANT_TERMINATE_SELF"; case STATUS_INVALID_SERVER_STATE: return "STATUS_INVALID_SERVER_STATE"; case STATUS_INVALID_DOMAIN_STATE: return "STATUS_INVALID_DOMAIN_STATE"; case STATUS_INVALID_DOMAIN_ROLE: return "STATUS_INVALID_DOMAIN_ROLE"; case STATUS_NO_SUCH_DOMAIN: return "STATUS_NO_SUCH_DOMAIN"; case STATUS_DOMAIN_EXISTS: return "STATUS_DOMAIN_EXISTS"; case STATUS_DOMAIN_LIMIT_EXCEEDED: return "STATUS_DOMAIN_LIMIT_EXCEEDED"; case STATUS_OPLOCK_NOT_GRANTED: return "STATUS_OPLOCK_NOT_GRANTED"; case STATUS_INVALID_OPLOCK_PROTOCOL: return "STATUS_INVALID_OPLOCK_PROTOCOL"; case STATUS_INTERNAL_DB_CORRUPTION: return "STATUS_INTERNAL_DB_CORRUPTION"; case STATUS_INTERNAL_ERROR: return "STATUS_INTERNAL_ERROR"; case STATUS_GENERIC_NOT_MAPPED: return "STATUS_GENERIC_NOT_MAPPED"; case STATUS_BAD_DESCRIPTOR_FORMAT: return "STATUS_BAD_DESCRIPTOR_FORMAT"; case STATUS_INVALID_USER_BUFFER: return "STATUS_INVALID_USER_BUFFER"; case STATUS_UNEXPECTED_IO_ERROR: return "STATUS_UNEXPECTED_IO_ERROR"; case STATUS_UNEXPECTED_MM_CREATE_ERR: return "STATUS_UNEXPECTED_MM_CREATE_ERR"; case STATUS_UNEXPECTED_MM_MAP_ERROR: return "STATUS_UNEXPECTED_MM_MAP_ERROR"; case STATUS_UNEXPECTED_MM_EXTEND_ERR: return "STATUS_UNEXPECTED_MM_EXTEND_ERR"; case STATUS_NOT_LOGON_PROCESS: return "STATUS_NOT_LOGON_PROCESS"; case STATUS_LOGON_SESSION_EXISTS: return "STATUS_LOGON_SESSION_EXISTS"; case STATUS_INVALID_PARAMETER_1: return "STATUS_INVALID_PARAMETER_1"; case STATUS_INVALID_PARAMETER_2: return "STATUS_INVALID_PARAMETER_2"; case STATUS_INVALID_PARAMETER_3: return "STATUS_INVALID_PARAMETER_3"; case STATUS_INVALID_PARAMETER_4: return "STATUS_INVALID_PARAMETER_4"; case STATUS_INVALID_PARAMETER_5: return "STATUS_INVALID_PARAMETER_5"; case STATUS_INVALID_PARAMETER_6: return "STATUS_INVALID_PARAMETER_6"; case STATUS_INVALID_PARAMETER_7: return "STATUS_INVALID_PARAMETER_7"; case STATUS_INVALID_PARAMETER_8: return "STATUS_INVALID_PARAMETER_8"; case STATUS_INVALID_PARAMETER_9: return "STATUS_INVALID_PARAMETER_9"; case STATUS_INVALID_PARAMETER_10: return "STATUS_INVALID_PARAMETER_10"; case STATUS_INVALID_PARAMETER_11: return "STATUS_INVALID_PARAMETER_11"; case STATUS_INVALID_PARAMETER_12: return "STATUS_INVALID_PARAMETER_12"; case STATUS_REDIRECTOR_NOT_STARTED: return "STATUS_REDIRECTOR_NOT_STARTED"; case STATUS_REDIRECTOR_STARTED: return "STATUS_REDIRECTOR_STARTED"; case STATUS_STACK_OVERFLOW: return "STATUS_STACK_OVERFLOW"; case STATUS_NO_SUCH_PACKAGE: return "STATUS_NO_SUCH_PACKAGE"; case STATUS_BAD_FUNCTION_TABLE: return "STATUS_BAD_FUNCTION_TABLE"; case STATUS_VARIABLE_NOT_FOUND: return "STATUS_VARIABLE_NOT_FOUND"; case STATUS_DIRECTORY_NOT_EMPTY: return "STATUS_DIRECTORY_NOT_EMPTY"; case STATUS_FILE_CORRUPT_ERROR: return "STATUS_FILE_CORRUPT_ERROR"; case STATUS_NOT_A_DIRECTORY: return "STATUS_NOT_A_DIRECTORY"; case STATUS_BAD_LOGON_SESSION_STATE: return "STATUS_BAD_LOGON_SESSION_STATE"; case STATUS_LOGON_SESSION_COLLISION: return "STATUS_LOGON_SESSION_COLLISION"; case STATUS_NAME_TOO_LONG: return "STATUS_NAME_TOO_LONG"; case STATUS_FILES_OPEN: return "STATUS_FILES_OPEN"; case STATUS_CONNECTION_IN_USE: return "STATUS_CONNECTION_IN_USE"; case STATUS_MESSAGE_NOT_FOUND: return "STATUS_MESSAGE_NOT_FOUND"; case STATUS_PROCESS_IS_TERMINATING: return "STATUS_PROCESS_IS_TERMINATING"; case STATUS_INVALID_LOGON_TYPE: return "STATUS_INVALID_LOGON_TYPE"; case STATUS_NO_GUID_TRANSLATION: return "STATUS_NO_GUID_TRANSLATION"; case STATUS_CANNOT_IMPERSONATE: return "STATUS_CANNOT_IMPERSONATE"; case STATUS_IMAGE_ALREADY_LOADED: return "STATUS_IMAGE_ALREADY_LOADED"; case STATUS_NO_LDT: return "STATUS_NO_LDT"; case STATUS_INVALID_LDT_SIZE: return "STATUS_INVALID_LDT_SIZE"; case STATUS_INVALID_LDT_OFFSET: return "STATUS_INVALID_LDT_OFFSET"; case STATUS_INVALID_LDT_DESCRIPTOR: return "STATUS_INVALID_LDT_DESCRIPTOR"; case STATUS_INVALID_IMAGE_NE_FORMAT: return "STATUS_INVALID_IMAGE_NE_FORMAT"; case STATUS_RXACT_INVALID_STATE: return "STATUS_RXACT_INVALID_STATE"; case STATUS_RXACT_COMMIT_FAILURE: return "STATUS_RXACT_COMMIT_FAILURE"; case STATUS_MAPPED_FILE_SIZE_ZERO: return "STATUS_MAPPED_FILE_SIZE_ZERO"; case STATUS_TOO_MANY_OPENED_FILES: return "STATUS_TOO_MANY_OPENED_FILES"; case STATUS_CANCELLED: return "STATUS_CANCELLED"; case STATUS_CANNOT_DELETE: return "STATUS_CANNOT_DELETE"; case STATUS_INVALID_COMPUTER_NAME: return "STATUS_INVALID_COMPUTER_NAME"; case STATUS_FILE_DELETED: return "STATUS_FILE_DELETED"; case STATUS_SPECIAL_ACCOUNT: return "STATUS_SPECIAL_ACCOUNT"; case STATUS_SPECIAL_GROUP: return "STATUS_SPECIAL_GROUP"; case STATUS_SPECIAL_USER: return "STATUS_SPECIAL_USER"; case STATUS_MEMBERS_PRIMARY_GROUP: return "STATUS_MEMBERS_PRIMARY_GROUP"; case STATUS_FILE_CLOSED: return "STATUS_FILE_CLOSED"; case STATUS_TOO_MANY_THREADS: return "STATUS_TOO_MANY_THREADS"; case STATUS_THREAD_NOT_IN_PROCESS: return "STATUS_THREAD_NOT_IN_PROCESS"; case STATUS_TOKEN_ALREADY_IN_USE: return "STATUS_TOKEN_ALREADY_IN_USE"; case STATUS_PAGEFILE_QUOTA_EXCEEDED: return "STATUS_PAGEFILE_QUOTA_EXCEEDED"; case STATUS_COMMITMENT_LIMIT: return "STATUS_COMMITMENT_LIMIT"; case STATUS_INVALID_IMAGE_LE_FORMAT: return "STATUS_INVALID_IMAGE_LE_FORMAT"; case STATUS_INVALID_IMAGE_NOT_MZ: return "STATUS_INVALID_IMAGE_NOT_MZ"; case STATUS_INVALID_IMAGE_PROTECT: return "STATUS_INVALID_IMAGE_PROTECT"; case STATUS_INVALID_IMAGE_WIN_16: return "STATUS_INVALID_IMAGE_WIN_16"; case STATUS_LOGON_SERVER_CONFLICT: return "STATUS_LOGON_SERVER_CONFLICT"; case STATUS_TIME_DIFFERENCE_AT_DC: return "STATUS_TIME_DIFFERENCE_AT_DC"; case STATUS_SYNCHRONIZATION_REQUIRED: return "STATUS_SYNCHRONIZATION_REQUIRED"; case STATUS_DLL_NOT_FOUND: return "STATUS_DLL_NOT_FOUND"; case STATUS_OPEN_FAILED: return "STATUS_OPEN_FAILED"; case STATUS_IO_PRIVILEGE_FAILED: return "STATUS_IO_PRIVILEGE_FAILED"; case STATUS_ORDINAL_NOT_FOUND: return "STATUS_ORDINAL_NOT_FOUND"; case STATUS_ENTRYPOINT_NOT_FOUND: return "STATUS_ENTRYPOINT_NOT_FOUND"; case STATUS_CONTROL_C_EXIT: return "STATUS_CONTROL_C_EXIT"; case STATUS_LOCAL_DISCONNECT: return "STATUS_LOCAL_DISCONNECT"; case STATUS_REMOTE_DISCONNECT: return "STATUS_REMOTE_DISCONNECT"; case STATUS_REMOTE_RESOURCES: return "STATUS_REMOTE_RESOURCES"; case STATUS_LINK_FAILED: return "STATUS_LINK_FAILED"; case STATUS_LINK_TIMEOUT: return "STATUS_LINK_TIMEOUT"; case STATUS_INVALID_CONNECTION: return "STATUS_INVALID_CONNECTION"; case STATUS_INVALID_ADDRESS: return "STATUS_INVALID_ADDRESS"; case STATUS_DLL_INIT_FAILED: return "STATUS_DLL_INIT_FAILED"; case STATUS_MISSING_SYSTEMFILE: return "STATUS_MISSING_SYSTEMFILE"; case STATUS_UNHANDLED_EXCEPTION: return "STATUS_UNHANDLED_EXCEPTION"; case STATUS_APP_INIT_FAILURE: return "STATUS_APP_INIT_FAILURE"; case STATUS_PAGEFILE_CREATE_FAILED: return "STATUS_PAGEFILE_CREATE_FAILED"; case STATUS_NO_PAGEFILE: return "STATUS_NO_PAGEFILE"; case STATUS_INVALID_LEVEL: return "STATUS_INVALID_LEVEL"; case STATUS_WRONG_PASSWORD_CORE: return "STATUS_WRONG_PASSWORD_CORE"; case STATUS_ILLEGAL_FLOAT_CONTEXT: return "STATUS_ILLEGAL_FLOAT_CONTEXT"; case STATUS_PIPE_BROKEN: return "STATUS_PIPE_BROKEN"; case STATUS_REGISTRY_CORRUPT: return "STATUS_REGISTRY_CORRUPT"; case STATUS_REGISTRY_IO_FAILED: return "STATUS_REGISTRY_IO_FAILED"; case STATUS_NO_EVENT_PAIR: return "STATUS_NO_EVENT_PAIR"; case STATUS_UNRECOGNIZED_VOLUME: return "STATUS_UNRECOGNIZED_VOLUME"; case STATUS_SERIAL_NO_DEVICE_INITED: return "STATUS_SERIAL_NO_DEVICE_INITED"; case STATUS_NO_SUCH_ALIAS: return "STATUS_NO_SUCH_ALIAS"; case STATUS_MEMBER_NOT_IN_ALIAS: return "STATUS_MEMBER_NOT_IN_ALIAS"; case STATUS_MEMBER_IN_ALIAS: return "STATUS_MEMBER_IN_ALIAS"; case STATUS_ALIAS_EXISTS: return "STATUS_ALIAS_EXISTS"; case STATUS_LOGON_NOT_GRANTED: return "STATUS_LOGON_NOT_GRANTED"; case STATUS_TOO_MANY_SECRETS: return "STATUS_TOO_MANY_SECRETS"; case STATUS_SECRET_TOO_LONG: return "STATUS_SECRET_TOO_LONG"; case STATUS_INTERNAL_DB_ERROR: return "STATUS_INTERNAL_DB_ERROR"; case STATUS_FULLSCREEN_MODE: return "STATUS_FULLSCREEN_MODE"; case STATUS_TOO_MANY_CONTEXT_IDS: return "STATUS_TOO_MANY_CONTEXT_IDS"; case STATUS_LOGON_TYPE_NOT_GRANTED: return "STATUS_LOGON_TYPE_NOT_GRANTED"; case STATUS_NOT_REGISTRY_FILE: return "STATUS_NOT_REGISTRY_FILE"; case STATUS_NT_CROSS_ENCRYPTION_REQUIRED: return "STATUS_NT_CROSS_ENCRYPTION_REQUIRED"; case STATUS_DOMAIN_CTRLR_CONFIG_ERROR: return "STATUS_DOMAIN_CTRLR_CONFIG_ERROR"; case STATUS_FT_MISSING_MEMBER: return "STATUS_FT_MISSING_MEMBER"; case STATUS_ILL_FORMED_SERVICE_ENTRY: return "STATUS_ILL_FORMED_SERVICE_ENTRY"; case STATUS_ILLEGAL_CHARACTER: return "STATUS_ILLEGAL_CHARACTER"; case STATUS_UNMAPPABLE_CHARACTER: return "STATUS_UNMAPPABLE_CHARACTER"; case STATUS_UNDEFINED_CHARACTER: return "STATUS_UNDEFINED_CHARACTER"; case STATUS_FLOPPY_VOLUME: return "STATUS_FLOPPY_VOLUME"; case STATUS_FLOPPY_ID_MARK_NOT_FOUND: return "STATUS_FLOPPY_ID_MARK_NOT_FOUND"; case STATUS_FLOPPY_WRONG_CYLINDER: return "STATUS_FLOPPY_WRONG_CYLINDER"; case STATUS_FLOPPY_UNKNOWN_ERROR: return "STATUS_FLOPPY_UNKNOWN_ERROR"; case STATUS_FLOPPY_BAD_REGISTERS: return "STATUS_FLOPPY_BAD_REGISTERS"; case STATUS_DISK_RECALIBRATE_FAILED: return "STATUS_DISK_RECALIBRATE_FAILED"; case STATUS_DISK_OPERATION_FAILED: return "STATUS_DISK_OPERATION_FAILED"; case STATUS_DISK_RESET_FAILED: return "STATUS_DISK_RESET_FAILED"; case STATUS_SHARED_IRQ_BUSY: return "STATUS_SHARED_IRQ_BUSY"; case STATUS_FT_ORPHANING: return "STATUS_FT_ORPHANING"; case STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT: return "STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT"; case STATUS_PARTITION_FAILURE: return "STATUS_PARTITION_FAILURE"; case STATUS_INVALID_BLOCK_LENGTH: return "STATUS_INVALID_BLOCK_LENGTH"; case STATUS_DEVICE_NOT_PARTITIONED: return "STATUS_DEVICE_NOT_PARTITIONED"; case STATUS_UNABLE_TO_LOCK_MEDIA: return "STATUS_UNABLE_TO_LOCK_MEDIA"; case STATUS_UNABLE_TO_UNLOAD_MEDIA: return "STATUS_UNABLE_TO_UNLOAD_MEDIA"; case STATUS_EOM_OVERFLOW: return "STATUS_EOM_OVERFLOW"; case STATUS_NO_MEDIA: return "STATUS_NO_MEDIA"; case STATUS_NO_SUCH_MEMBER: return "STATUS_NO_SUCH_MEMBER"; case STATUS_INVALID_MEMBER: return "STATUS_INVALID_MEMBER"; case STATUS_KEY_DELETED: return "STATUS_KEY_DELETED"; case STATUS_NO_LOG_SPACE: return "STATUS_NO_LOG_SPACE"; case STATUS_TOO_MANY_SIDS: return "STATUS_TOO_MANY_SIDS"; case STATUS_LM_CROSS_ENCRYPTION_REQUIRED: return "STATUS_LM_CROSS_ENCRYPTION_REQUIRED"; case STATUS_KEY_HAS_CHILDREN: return "STATUS_KEY_HAS_CHILDREN"; case STATUS_CHILD_MUST_BE_VOLATILE: return "STATUS_CHILD_MUST_BE_VOLATILE"; case STATUS_DEVICE_CONFIGURATION_ERROR: return "STATUS_DEVICE_CONFIGURATION_ERROR"; case STATUS_DRIVER_INTERNAL_ERROR: return "STATUS_DRIVER_INTERNAL_ERROR"; case STATUS_INVALID_DEVICE_STATE: return "STATUS_INVALID_DEVICE_STATE"; case STATUS_IO_DEVICE_ERROR: return "STATUS_IO_DEVICE_ERROR"; case STATUS_DEVICE_PROTOCOL_ERROR: return "STATUS_DEVICE_PROTOCOL_ERROR"; case STATUS_BACKUP_CONTROLLER: return "STATUS_BACKUP_CONTROLLER"; case STATUS_LOG_FILE_FULL: return "STATUS_LOG_FILE_FULL"; case STATUS_TOO_LATE: return "STATUS_TOO_LATE"; case STATUS_NO_TRUST_LSA_SECRET: return "STATUS_NO_TRUST_LSA_SECRET"; case STATUS_NO_TRUST_SAM_ACCOUNT: return "STATUS_NO_TRUST_SAM_ACCOUNT"; case STATUS_TRUSTED_DOMAIN_FAILURE: return "STATUS_TRUSTED_DOMAIN_FAILURE"; case STATUS_TRUSTED_RELATIONSHIP_FAILURE: return "STATUS_TRUSTED_RELATIONSHIP_FAILURE"; case STATUS_EVENTLOG_FILE_CORRUPT: return "STATUS_EVENTLOG_FILE_CORRUPT"; case STATUS_EVENTLOG_CANT_START: return "STATUS_EVENTLOG_CANT_START"; case STATUS_TRUST_FAILURE: return "STATUS_TRUST_FAILURE"; case STATUS_MUTANT_LIMIT_EXCEEDED: return "STATUS_MUTANT_LIMIT_EXCEEDED"; case STATUS_NETLOGON_NOT_STARTED: return "STATUS_NETLOGON_NOT_STARTED"; case STATUS_ACCOUNT_EXPIRED: return "STATUS_ACCOUNT_EXPIRED"; case STATUS_POSSIBLE_DEADLOCK: return "STATUS_POSSIBLE_DEADLOCK"; case STATUS_NETWORK_CREDENTIAL_CONFLICT: return "STATUS_NETWORK_CREDENTIAL_CONFLICT"; case STATUS_REMOTE_SESSION_LIMIT: return "STATUS_REMOTE_SESSION_LIMIT"; case STATUS_EVENTLOG_FILE_CHANGED: return "STATUS_EVENTLOG_FILE_CHANGED"; case STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT: return "STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT"; case STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT: return "STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT"; case STATUS_NOLOGON_SERVER_TRUST_ACCOUNT: return "STATUS_NOLOGON_SERVER_TRUST_ACCOUNT"; case STATUS_DOMAIN_TRUST_INCONSISTENT: return "STATUS_DOMAIN_TRUST_INCONSISTENT"; case STATUS_FS_DRIVER_REQUIRED: return "STATUS_FS_DRIVER_REQUIRED"; case STATUS_IMAGE_ALREADY_LOADED_AS_DLL: return "STATUS_IMAGE_ALREADY_LOADED_AS_DLL"; case STATUS_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING: return "STATUS_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING"; case STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME: return "STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME"; case STATUS_SECURITY_STREAM_IS_INCONSISTENT: return "STATUS_SECURITY_STREAM_IS_INCONSISTENT"; case STATUS_INVALID_LOCK_RANGE: return "STATUS_INVALID_LOCK_RANGE"; case STATUS_INVALID_ACE_CONDITION: return "STATUS_INVALID_ACE_CONDITION"; case STATUS_IMAGE_SUBSYSTEM_NOT_PRESENT: return "STATUS_IMAGE_SUBSYSTEM_NOT_PRESENT"; case STATUS_NOTIFICATION_GUID_ALREADY_DEFINED: return "STATUS_NOTIFICATION_GUID_ALREADY_DEFINED"; case STATUS_NETWORK_OPEN_RESTRICTION: return "STATUS_NETWORK_OPEN_RESTRICTION"; case STATUS_NO_USER_SESSION_KEY: return "STATUS_NO_USER_SESSION_KEY"; case STATUS_USER_SESSION_DELETED: return "STATUS_USER_SESSION_DELETED"; case STATUS_RESOURCE_LANG_NOT_FOUND: return "STATUS_RESOURCE_LANG_NOT_FOUND"; case STATUS_INSUFF_SERVER_RESOURCES: return "STATUS_INSUFF_SERVER_RESOURCES"; case STATUS_INVALID_BUFFER_SIZE: return "STATUS_INVALID_BUFFER_SIZE"; case STATUS_INVALID_ADDRESS_COMPONENT: return "STATUS_INVALID_ADDRESS_COMPONENT"; case STATUS_INVALID_ADDRESS_WILDCARD: return "STATUS_INVALID_ADDRESS_WILDCARD"; case STATUS_TOO_MANY_ADDRESSES: return "STATUS_TOO_MANY_ADDRESSES"; case STATUS_ADDRESS_ALREADY_EXISTS: return "STATUS_ADDRESS_ALREADY_EXISTS"; case STATUS_ADDRESS_CLOSED: return "STATUS_ADDRESS_CLOSED"; case STATUS_CONNECTION_DISCONNECTED: return "STATUS_CONNECTION_DISCONNECTED"; case STATUS_CONNECTION_RESET: return "STATUS_CONNECTION_RESET"; case STATUS_TOO_MANY_NODES: return "STATUS_TOO_MANY_NODES"; case STATUS_TRANSACTION_ABORTED: return "STATUS_TRANSACTION_ABORTED"; case STATUS_TRANSACTION_TIMED_OUT: return "STATUS_TRANSACTION_TIMED_OUT"; case STATUS_TRANSACTION_NO_RELEASE: return "STATUS_TRANSACTION_NO_RELEASE"; case STATUS_TRANSACTION_NO_MATCH: return "STATUS_TRANSACTION_NO_MATCH"; case STATUS_TRANSACTION_RESPONDED: return "STATUS_TRANSACTION_RESPONDED"; case STATUS_TRANSACTION_INVALID_ID: return "STATUS_TRANSACTION_INVALID_ID"; case STATUS_TRANSACTION_INVALID_TYPE: return "STATUS_TRANSACTION_INVALID_TYPE"; case STATUS_NOT_SERVER_SESSION: return "STATUS_NOT_SERVER_SESSION"; case STATUS_NOT_CLIENT_SESSION: return "STATUS_NOT_CLIENT_SESSION"; case STATUS_CANNOT_LOAD_REGISTRY_FILE: return "STATUS_CANNOT_LOAD_REGISTRY_FILE"; case STATUS_DEBUG_ATTACH_FAILED: return "STATUS_DEBUG_ATTACH_FAILED"; case STATUS_SYSTEM_PROCESS_TERMINATED: return "STATUS_SYSTEM_PROCESS_TERMINATED"; case STATUS_DATA_NOT_ACCEPTED: return "STATUS_DATA_NOT_ACCEPTED"; case STATUS_NO_BROWSER_SERVERS_FOUND: return "STATUS_NO_BROWSER_SERVERS_FOUND"; case STATUS_VDM_HARD_ERROR: return "STATUS_VDM_HARD_ERROR"; case STATUS_DRIVER_CANCEL_TIMEOUT: return "STATUS_DRIVER_CANCEL_TIMEOUT"; case STATUS_REPLY_MESSAGE_MISMATCH: return "STATUS_REPLY_MESSAGE_MISMATCH"; case STATUS_MAPPED_ALIGNMENT: return "STATUS_MAPPED_ALIGNMENT"; case STATUS_IMAGE_CHECKSUM_MISMATCH: return "STATUS_IMAGE_CHECKSUM_MISMATCH"; case STATUS_LOST_WRITEBEHIND_DATA: return "STATUS_LOST_WRITEBEHIND_DATA"; case STATUS_CLIENT_SERVER_PARAMETERS_INVALID: return "STATUS_CLIENT_SERVER_PARAMETERS_INVALID"; case STATUS_PASSWORD_MUST_CHANGE: return "STATUS_PASSWORD_MUST_CHANGE"; case STATUS_NOT_FOUND: return "STATUS_NOT_FOUND"; case STATUS_NOT_TINY_STREAM: return "STATUS_NOT_TINY_STREAM"; case STATUS_RECOVERY_FAILURE: return "STATUS_RECOVERY_FAILURE"; case STATUS_STACK_OVERFLOW_READ: return "STATUS_STACK_OVERFLOW_READ"; case STATUS_FAIL_CHECK: return "STATUS_FAIL_CHECK"; case STATUS_DUPLICATE_OBJECTID: return "STATUS_DUPLICATE_OBJECTID"; case STATUS_OBJECTID_EXISTS: return "STATUS_OBJECTID_EXISTS"; case STATUS_CONVERT_TO_LARGE: return "STATUS_CONVERT_TO_LARGE"; case STATUS_RETRY: return "STATUS_RETRY"; case STATUS_FOUND_OUT_OF_SCOPE: return "STATUS_FOUND_OUT_OF_SCOPE"; case STATUS_ALLOCATE_BUCKET: return "STATUS_ALLOCATE_BUCKET"; case STATUS_PROPSET_NOT_FOUND: return "STATUS_PROPSET_NOT_FOUND"; case STATUS_MARSHALL_OVERFLOW: return "STATUS_MARSHALL_OVERFLOW"; case STATUS_INVALID_VARIANT: return "STATUS_INVALID_VARIANT"; case STATUS_DOMAIN_CONTROLLER_NOT_FOUND: return "STATUS_DOMAIN_CONTROLLER_NOT_FOUND"; case STATUS_ACCOUNT_LOCKED_OUT: return "STATUS_ACCOUNT_LOCKED_OUT"; case STATUS_HANDLE_NOT_CLOSABLE: return "STATUS_HANDLE_NOT_CLOSABLE"; case STATUS_CONNECTION_REFUSED: return "STATUS_CONNECTION_REFUSED"; case STATUS_GRACEFUL_DISCONNECT: return "STATUS_GRACEFUL_DISCONNECT"; case STATUS_ADDRESS_ALREADY_ASSOCIATED: return "STATUS_ADDRESS_ALREADY_ASSOCIATED"; case STATUS_ADDRESS_NOT_ASSOCIATED: return "STATUS_ADDRESS_NOT_ASSOCIATED"; case STATUS_CONNECTION_INVALID: return "STATUS_CONNECTION_INVALID"; case STATUS_CONNECTION_ACTIVE: return "STATUS_CONNECTION_ACTIVE"; case STATUS_NETWORK_UNREACHABLE: return "STATUS_NETWORK_UNREACHABLE"; case STATUS_HOST_UNREACHABLE: return "STATUS_HOST_UNREACHABLE"; case STATUS_PROTOCOL_UNREACHABLE: return "STATUS_PROTOCOL_UNREACHABLE"; case STATUS_PORT_UNREACHABLE: return "STATUS_PORT_UNREACHABLE"; case STATUS_REQUEST_ABORTED: return "STATUS_REQUEST_ABORTED"; case STATUS_CONNECTION_ABORTED: return "STATUS_CONNECTION_ABORTED"; case STATUS_BAD_COMPRESSION_BUFFER: return "STATUS_BAD_COMPRESSION_BUFFER"; case STATUS_USER_MAPPED_FILE: return "STATUS_USER_MAPPED_FILE"; case STATUS_AUDIT_FAILED: return "STATUS_AUDIT_FAILED"; case STATUS_TIMER_RESOLUTION_NOT_SET: return "STATUS_TIMER_RESOLUTION_NOT_SET"; case STATUS_CONNECTION_COUNT_LIMIT: return "STATUS_CONNECTION_COUNT_LIMIT"; case STATUS_LOGIN_TIME_RESTRICTION: return "STATUS_LOGIN_TIME_RESTRICTION"; case STATUS_LOGIN_WKSTA_RESTRICTION: return "STATUS_LOGIN_WKSTA_RESTRICTION"; case STATUS_IMAGE_MP_UP_MISMATCH: return "STATUS_IMAGE_MP_UP_MISMATCH"; case STATUS_INSUFFICIENT_LOGON_INFO: return "STATUS_INSUFFICIENT_LOGON_INFO"; case STATUS_BAD_DLL_ENTRYPOINT: return "STATUS_BAD_DLL_ENTRYPOINT"; case STATUS_BAD_SERVICE_ENTRYPOINT: return "STATUS_BAD_SERVICE_ENTRYPOINT"; case STATUS_LPC_REPLY_LOST: return "STATUS_LPC_REPLY_LOST"; case STATUS_IP_ADDRESS_CONFLICT1: return "STATUS_IP_ADDRESS_CONFLICT1"; case STATUS_IP_ADDRESS_CONFLICT2: return "STATUS_IP_ADDRESS_CONFLICT2"; case STATUS_REGISTRY_QUOTA_LIMIT: return "STATUS_REGISTRY_QUOTA_LIMIT"; case STATUS_PATH_NOT_COVERED: return "STATUS_PATH_NOT_COVERED"; case STATUS_NO_CALLBACK_ACTIVE: return "STATUS_NO_CALLBACK_ACTIVE"; case STATUS_LICENSE_QUOTA_EXCEEDED: return "STATUS_LICENSE_QUOTA_EXCEEDED"; case STATUS_PWD_TOO_SHORT: return "STATUS_PWD_TOO_SHORT"; case STATUS_PWD_TOO_RECENT: return "STATUS_PWD_TOO_RECENT"; case STATUS_PWD_HISTORY_CONFLICT: return "STATUS_PWD_HISTORY_CONFLICT"; case STATUS_PLUGPLAY_NO_DEVICE: return "STATUS_PLUGPLAY_NO_DEVICE"; case STATUS_UNSUPPORTED_COMPRESSION: return "STATUS_UNSUPPORTED_COMPRESSION"; case STATUS_INVALID_HW_PROFILE: return "STATUS_INVALID_HW_PROFILE"; case STATUS_INVALID_PLUGPLAY_DEVICE_PATH: return "STATUS_INVALID_PLUGPLAY_DEVICE_PATH"; case STATUS_DRIVER_ORDINAL_NOT_FOUND: return "STATUS_DRIVER_ORDINAL_NOT_FOUND"; case STATUS_DRIVER_ENTRYPOINT_NOT_FOUND: return "STATUS_DRIVER_ENTRYPOINT_NOT_FOUND"; case STATUS_RESOURCE_NOT_OWNED: return "STATUS_RESOURCE_NOT_OWNED"; case STATUS_TOO_MANY_LINKS: return "STATUS_TOO_MANY_LINKS"; case STATUS_QUOTA_LIST_INCONSISTENT: return "STATUS_QUOTA_LIST_INCONSISTENT"; case STATUS_FILE_IS_OFFLINE: return "STATUS_FILE_IS_OFFLINE"; case STATUS_EVALUATION_EXPIRATION: return "STATUS_EVALUATION_EXPIRATION"; case STATUS_ILLEGAL_DLL_RELOCATION: return "STATUS_ILLEGAL_DLL_RELOCATION"; case STATUS_LICENSE_VIOLATION: return "STATUS_LICENSE_VIOLATION"; case STATUS_DLL_INIT_FAILED_LOGOFF: return "STATUS_DLL_INIT_FAILED_LOGOFF"; case STATUS_DRIVER_UNABLE_TO_LOAD: return "STATUS_DRIVER_UNABLE_TO_LOAD"; case STATUS_DFS_UNAVAILABLE: return "STATUS_DFS_UNAVAILABLE"; case STATUS_VOLUME_DISMOUNTED: return "STATUS_VOLUME_DISMOUNTED"; case STATUS_WX86_INTERNAL_ERROR: return "STATUS_WX86_INTERNAL_ERROR"; case STATUS_WX86_FLOAT_STACK_CHECK: return "STATUS_WX86_FLOAT_STACK_CHECK"; case STATUS_VALIDATE_CONTINUE: return "STATUS_VALIDATE_CONTINUE"; case STATUS_NO_MATCH: return "STATUS_NO_MATCH"; case STATUS_NO_MORE_MATCHES: return "STATUS_NO_MORE_MATCHES"; case STATUS_NOT_A_REPARSE_POINT: return "STATUS_NOT_A_REPARSE_POINT"; case STATUS_IO_REPARSE_TAG_INVALID: return "STATUS_IO_REPARSE_TAG_INVALID"; case STATUS_IO_REPARSE_TAG_MISMATCH: return "STATUS_IO_REPARSE_TAG_MISMATCH"; case STATUS_IO_REPARSE_DATA_INVALID: return "STATUS_IO_REPARSE_DATA_INVALID"; case STATUS_IO_REPARSE_TAG_NOT_HANDLED: return "STATUS_IO_REPARSE_TAG_NOT_HANDLED"; case STATUS_REPARSE_POINT_NOT_RESOLVED: return "STATUS_REPARSE_POINT_NOT_RESOLVED"; case STATUS_DIRECTORY_IS_A_REPARSE_POINT: return "STATUS_DIRECTORY_IS_A_REPARSE_POINT"; case STATUS_RANGE_LIST_CONFLICT: return "STATUS_RANGE_LIST_CONFLICT"; case STATUS_SOURCE_ELEMENT_EMPTY: return "STATUS_SOURCE_ELEMENT_EMPTY"; case STATUS_DESTINATION_ELEMENT_FULL: return "STATUS_DESTINATION_ELEMENT_FULL"; case STATUS_ILLEGAL_ELEMENT_ADDRESS: return "STATUS_ILLEGAL_ELEMENT_ADDRESS"; case STATUS_MAGAZINE_NOT_PRESENT: return "STATUS_MAGAZINE_NOT_PRESENT"; case STATUS_REINITIALIZATION_NEEDED: return "STATUS_REINITIALIZATION_NEEDED"; case STATUS_ENCRYPTION_FAILED: return "STATUS_ENCRYPTION_FAILED"; case STATUS_DECRYPTION_FAILED: return "STATUS_DECRYPTION_FAILED"; case STATUS_RANGE_NOT_FOUND: return "STATUS_RANGE_NOT_FOUND"; case STATUS_NO_RECOVERY_POLICY: return "STATUS_NO_RECOVERY_POLICY"; case STATUS_NO_EFS: return "STATUS_NO_EFS"; case STATUS_WRONG_EFS: return "STATUS_WRONG_EFS"; case STATUS_NO_USER_KEYS: return "STATUS_NO_USER_KEYS"; case STATUS_FILE_NOT_ENCRYPTED: return "STATUS_FILE_NOT_ENCRYPTED"; case STATUS_NOT_EXPORT_FORMAT: return "STATUS_NOT_EXPORT_FORMAT"; case STATUS_FILE_ENCRYPTED: return "STATUS_FILE_ENCRYPTED"; case STATUS_WMI_GUID_NOT_FOUND: return "STATUS_WMI_GUID_NOT_FOUND"; case STATUS_WMI_INSTANCE_NOT_FOUND: return "STATUS_WMI_INSTANCE_NOT_FOUND"; case STATUS_WMI_ITEMID_NOT_FOUND: return "STATUS_WMI_ITEMID_NOT_FOUND"; case STATUS_WMI_TRY_AGAIN: return "STATUS_WMI_TRY_AGAIN"; case STATUS_SHARED_POLICY: return "STATUS_SHARED_POLICY"; case STATUS_POLICY_OBJECT_NOT_FOUND: return "STATUS_POLICY_OBJECT_NOT_FOUND"; case STATUS_POLICY_ONLY_IN_DS: return "STATUS_POLICY_ONLY_IN_DS"; case STATUS_VOLUME_NOT_UPGRADED: return "STATUS_VOLUME_NOT_UPGRADED"; case STATUS_REMOTE_STORAGE_NOT_ACTIVE: return "STATUS_REMOTE_STORAGE_NOT_ACTIVE"; case STATUS_REMOTE_STORAGE_MEDIA_ERROR: return "STATUS_REMOTE_STORAGE_MEDIA_ERROR"; case STATUS_NO_TRACKING_SERVICE: return "STATUS_NO_TRACKING_SERVICE"; case STATUS_SERVER_SID_MISMATCH: return "STATUS_SERVER_SID_MISMATCH"; case STATUS_DS_NO_ATTRIBUTE_OR_VALUE: return "STATUS_DS_NO_ATTRIBUTE_OR_VALUE"; case STATUS_DS_INVALID_ATTRIBUTE_SYNTAX: return "STATUS_DS_INVALID_ATTRIBUTE_SYNTAX"; case STATUS_DS_ATTRIBUTE_TYPE_UNDEFINED: return "STATUS_DS_ATTRIBUTE_TYPE_UNDEFINED"; case STATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS: return "STATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS"; case STATUS_DS_BUSY: return "STATUS_DS_BUSY"; case STATUS_DS_UNAVAILABLE: return "STATUS_DS_UNAVAILABLE"; case STATUS_DS_NO_RIDS_ALLOCATED: return "STATUS_DS_NO_RIDS_ALLOCATED"; case STATUS_DS_NO_MORE_RIDS: return "STATUS_DS_NO_MORE_RIDS"; case STATUS_DS_INCORRECT_ROLE_OWNER: return "STATUS_DS_INCORRECT_ROLE_OWNER"; case STATUS_DS_RIDMGR_INIT_ERROR: return "STATUS_DS_RIDMGR_INIT_ERROR"; case STATUS_DS_OBJ_CLASS_VIOLATION: return "STATUS_DS_OBJ_CLASS_VIOLATION"; case STATUS_DS_CANT_ON_NON_LEAF: return "STATUS_DS_CANT_ON_NON_LEAF"; case STATUS_DS_CANT_ON_RDN: return "STATUS_DS_CANT_ON_RDN"; case STATUS_DS_CANT_MOD_OBJ_CLASS: return "STATUS_DS_CANT_MOD_OBJ_CLASS"; case STATUS_DS_CROSS_DOM_MOVE_FAILED: return "STATUS_DS_CROSS_DOM_MOVE_FAILED"; case STATUS_DS_GC_NOT_AVAILABLE: return "STATUS_DS_GC_NOT_AVAILABLE"; case STATUS_DIRECTORY_SERVICE_REQUIRED: return "STATUS_DIRECTORY_SERVICE_REQUIRED"; case STATUS_REPARSE_ATTRIBUTE_CONFLICT: return "STATUS_REPARSE_ATTRIBUTE_CONFLICT"; case STATUS_CANT_ENABLE_DENY_ONLY: return "STATUS_CANT_ENABLE_DENY_ONLY"; case STATUS_FLOAT_MULTIPLE_FAULTS: return "STATUS_FLOAT_MULTIPLE_FAULTS"; case STATUS_FLOAT_MULTIPLE_TRAPS: return "STATUS_FLOAT_MULTIPLE_TRAPS"; case STATUS_DEVICE_REMOVED: return "STATUS_DEVICE_REMOVED"; case STATUS_JOURNAL_DELETE_IN_PROGRESS: return "STATUS_JOURNAL_DELETE_IN_PROGRESS"; case STATUS_JOURNAL_NOT_ACTIVE: return "STATUS_JOURNAL_NOT_ACTIVE"; case STATUS_NOINTERFACE: return "STATUS_NOINTERFACE"; case STATUS_DS_ADMIN_LIMIT_EXCEEDED: return "STATUS_DS_ADMIN_LIMIT_EXCEEDED"; case STATUS_DRIVER_FAILED_SLEEP: return "STATUS_DRIVER_FAILED_SLEEP"; case STATUS_MUTUAL_AUTHENTICATION_FAILED: return "STATUS_MUTUAL_AUTHENTICATION_FAILED"; case STATUS_CORRUPT_SYSTEM_FILE: return "STATUS_CORRUPT_SYSTEM_FILE"; case STATUS_DATATYPE_MISALIGNMENT_ERROR: return "STATUS_DATATYPE_MISALIGNMENT_ERROR"; case STATUS_WMI_READ_ONLY: return "STATUS_WMI_READ_ONLY"; case STATUS_WMI_SET_FAILURE: return "STATUS_WMI_SET_FAILURE"; case STATUS_COMMITMENT_MINIMUM: return "STATUS_COMMITMENT_MINIMUM"; case STATUS_REG_NAT_CONSUMPTION: return "STATUS_REG_NAT_CONSUMPTION"; case STATUS_TRANSPORT_FULL: return "STATUS_TRANSPORT_FULL"; case STATUS_DS_SAM_INIT_FAILURE: return "STATUS_DS_SAM_INIT_FAILURE"; case STATUS_ONLY_IF_CONNECTED: return "STATUS_ONLY_IF_CONNECTED"; case STATUS_DS_SENSITIVE_GROUP_VIOLATION: return "STATUS_DS_SENSITIVE_GROUP_VIOLATION"; case STATUS_PNP_RESTART_ENUMERATION: return "STATUS_PNP_RESTART_ENUMERATION"; case STATUS_JOURNAL_ENTRY_DELETED: return "STATUS_JOURNAL_ENTRY_DELETED"; case STATUS_DS_CANT_MOD_PRIMARYGROUPID: return "STATUS_DS_CANT_MOD_PRIMARYGROUPID"; case STATUS_SYSTEM_IMAGE_BAD_SIGNATURE: return "STATUS_SYSTEM_IMAGE_BAD_SIGNATURE"; case STATUS_PNP_REBOOT_REQUIRED: return "STATUS_PNP_REBOOT_REQUIRED"; case STATUS_POWER_STATE_INVALID: return "STATUS_POWER_STATE_INVALID"; case STATUS_DS_INVALID_GROUP_TYPE: return "STATUS_DS_INVALID_GROUP_TYPE"; case STATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN: return "STATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN"; case STATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN: return "STATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN"; case STATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER: return "STATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER"; case STATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER: return "STATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER"; case STATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER: return "STATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER"; case STATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER: return "STATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER"; case STATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER: return "STATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER"; case STATUS_DS_HAVE_PRIMARY_MEMBERS: return "STATUS_DS_HAVE_PRIMARY_MEMBERS"; case STATUS_WMI_NOT_SUPPORTED: return "STATUS_WMI_NOT_SUPPORTED"; case STATUS_INSUFFICIENT_POWER: return "STATUS_INSUFFICIENT_POWER"; case STATUS_SAM_NEED_BOOTKEY_PASSWORD: return "STATUS_SAM_NEED_BOOTKEY_PASSWORD"; case STATUS_SAM_NEED_BOOTKEY_FLOPPY: return "STATUS_SAM_NEED_BOOTKEY_FLOPPY"; case STATUS_DS_CANT_START: return "STATUS_DS_CANT_START"; case STATUS_DS_INIT_FAILURE: return "STATUS_DS_INIT_FAILURE"; case STATUS_SAM_INIT_FAILURE: return "STATUS_SAM_INIT_FAILURE"; case STATUS_DS_GC_REQUIRED: return "STATUS_DS_GC_REQUIRED"; case STATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY: return "STATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY"; case STATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS: return "STATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS"; case STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED: return "STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED"; case STATUS_CURRENT_DOMAIN_NOT_ALLOWED: return "STATUS_CURRENT_DOMAIN_NOT_ALLOWED"; case STATUS_CANNOT_MAKE: return "STATUS_CANNOT_MAKE"; case STATUS_SYSTEM_SHUTDOWN: return "STATUS_SYSTEM_SHUTDOWN"; case STATUS_DS_INIT_FAILURE_CONSOLE: return "STATUS_DS_INIT_FAILURE_CONSOLE"; case STATUS_DS_SAM_INIT_FAILURE_CONSOLE: return "STATUS_DS_SAM_INIT_FAILURE_CONSOLE"; case STATUS_UNFINISHED_CONTEXT_DELETED: return "STATUS_UNFINISHED_CONTEXT_DELETED"; case STATUS_NO_TGT_REPLY: return "STATUS_NO_TGT_REPLY"; case STATUS_OBJECTID_NOT_FOUND: return "STATUS_OBJECTID_NOT_FOUND"; case STATUS_NO_IP_ADDRESSES: return "STATUS_NO_IP_ADDRESSES"; case STATUS_WRONG_CREDENTIAL_HANDLE: return "STATUS_WRONG_CREDENTIAL_HANDLE"; case STATUS_CRYPTO_SYSTEM_INVALID: return "STATUS_CRYPTO_SYSTEM_INVALID"; case STATUS_MAX_REFERRALS_EXCEEDED: return "STATUS_MAX_REFERRALS_EXCEEDED"; case STATUS_MUST_BE_KDC: return "STATUS_MUST_BE_KDC"; case STATUS_STRONG_CRYPTO_NOT_SUPPORTED: return "STATUS_STRONG_CRYPTO_NOT_SUPPORTED"; case STATUS_TOO_MANY_PRINCIPALS: return "STATUS_TOO_MANY_PRINCIPALS"; case STATUS_NO_PA_DATA: return "STATUS_NO_PA_DATA"; case STATUS_PKINIT_NAME_MISMATCH: return "STATUS_PKINIT_NAME_MISMATCH"; case STATUS_SMARTCARD_LOGON_REQUIRED: return "STATUS_SMARTCARD_LOGON_REQUIRED"; case STATUS_KDC_INVALID_REQUEST: return "STATUS_KDC_INVALID_REQUEST"; case STATUS_KDC_UNABLE_TO_REFER: return "STATUS_KDC_UNABLE_TO_REFER"; case STATUS_KDC_UNKNOWN_ETYPE: return "STATUS_KDC_UNKNOWN_ETYPE"; case STATUS_SHUTDOWN_IN_PROGRESS: return "STATUS_SHUTDOWN_IN_PROGRESS"; case STATUS_SERVER_SHUTDOWN_IN_PROGRESS: return "STATUS_SERVER_SHUTDOWN_IN_PROGRESS"; case STATUS_NOT_SUPPORTED_ON_SBS: return "STATUS_NOT_SUPPORTED_ON_SBS"; case STATUS_WMI_GUID_DISCONNECTED: return "STATUS_WMI_GUID_DISCONNECTED"; case STATUS_WMI_ALREADY_DISABLED: return "STATUS_WMI_ALREADY_DISABLED"; case STATUS_WMI_ALREADY_ENABLED: return "STATUS_WMI_ALREADY_ENABLED"; case STATUS_MFT_TOO_FRAGMENTED: return "STATUS_MFT_TOO_FRAGMENTED"; case STATUS_COPY_PROTECTION_FAILURE: return "STATUS_COPY_PROTECTION_FAILURE"; case STATUS_CSS_AUTHENTICATION_FAILURE: return "STATUS_CSS_AUTHENTICATION_FAILURE"; case STATUS_CSS_KEY_NOT_PRESENT: return "STATUS_CSS_KEY_NOT_PRESENT"; case STATUS_CSS_KEY_NOT_ESTABLISHED: return "STATUS_CSS_KEY_NOT_ESTABLISHED"; case STATUS_CSS_SCRAMBLED_SECTOR: return "STATUS_CSS_SCRAMBLED_SECTOR"; case STATUS_CSS_REGION_MISMATCH: return "STATUS_CSS_REGION_MISMATCH"; case STATUS_CSS_RESETS_EXHAUSTED: return "STATUS_CSS_RESETS_EXHAUSTED"; case STATUS_PKINIT_FAILURE: return "STATUS_PKINIT_FAILURE"; case STATUS_SMARTCARD_SUBSYSTEM_FAILURE: return "STATUS_SMARTCARD_SUBSYSTEM_FAILURE"; case STATUS_NO_KERB_KEY: return "STATUS_NO_KERB_KEY"; case STATUS_HOST_DOWN: return "STATUS_HOST_DOWN"; case STATUS_UNSUPPORTED_PREAUTH: return "STATUS_UNSUPPORTED_PREAUTH"; case STATUS_EFS_ALG_BLOB_TOO_BIG: return "STATUS_EFS_ALG_BLOB_TOO_BIG"; case STATUS_PORT_NOT_SET: return "STATUS_PORT_NOT_SET"; case STATUS_DEBUGGER_INACTIVE: return "STATUS_DEBUGGER_INACTIVE"; case STATUS_DS_VERSION_CHECK_FAILURE: return "STATUS_DS_VERSION_CHECK_FAILURE"; case STATUS_AUDITING_DISABLED: return "STATUS_AUDITING_DISABLED"; case STATUS_PRENT4_MACHINE_ACCOUNT: return "STATUS_PRENT4_MACHINE_ACCOUNT"; case STATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER: return "STATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER"; case STATUS_INVALID_IMAGE_WIN_32: return "STATUS_INVALID_IMAGE_WIN_32"; case STATUS_INVALID_IMAGE_WIN_64: return "STATUS_INVALID_IMAGE_WIN_64"; case STATUS_BAD_BINDINGS: return "STATUS_BAD_BINDINGS"; case STATUS_NETWORK_SESSION_EXPIRED: return "STATUS_NETWORK_SESSION_EXPIRED"; case STATUS_APPHELP_BLOCK: return "STATUS_APPHELP_BLOCK"; case STATUS_ALL_SIDS_FILTERED: return "STATUS_ALL_SIDS_FILTERED"; case STATUS_NOT_SAFE_MODE_DRIVER: return "STATUS_NOT_SAFE_MODE_DRIVER"; case STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT: return "STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT"; case STATUS_ACCESS_DISABLED_BY_POLICY_PATH: return "STATUS_ACCESS_DISABLED_BY_POLICY_PATH"; case STATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER: return "STATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER"; case STATUS_ACCESS_DISABLED_BY_POLICY_OTHER: return "STATUS_ACCESS_DISABLED_BY_POLICY_OTHER"; case STATUS_FAILED_DRIVER_ENTRY: return "STATUS_FAILED_DRIVER_ENTRY"; case STATUS_DEVICE_ENUMERATION_ERROR: return "STATUS_DEVICE_ENUMERATION_ERROR"; case STATUS_MOUNT_POINT_NOT_RESOLVED: return "STATUS_MOUNT_POINT_NOT_RESOLVED"; case STATUS_INVALID_DEVICE_OBJECT_PARAMETER: return "STATUS_INVALID_DEVICE_OBJECT_PARAMETER"; case STATUS_MCA_OCCURED: return "STATUS_MCA_OCCURED"; case STATUS_DRIVER_BLOCKED_CRITICAL: return "STATUS_DRIVER_BLOCKED_CRITICAL"; case STATUS_DRIVER_BLOCKED: return "STATUS_DRIVER_BLOCKED"; case STATUS_DRIVER_DATABASE_ERROR: return "STATUS_DRIVER_DATABASE_ERROR"; case STATUS_SYSTEM_HIVE_TOO_LARGE: return "STATUS_SYSTEM_HIVE_TOO_LARGE"; case STATUS_INVALID_IMPORT_OF_NON_DLL: return "STATUS_INVALID_IMPORT_OF_NON_DLL"; case STATUS_NO_SECRETS: return "STATUS_NO_SECRETS"; case STATUS_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY: return "STATUS_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY"; case STATUS_FAILED_STACK_SWITCH: return "STATUS_FAILED_STACK_SWITCH"; case STATUS_HEAP_CORRUPTION: return "STATUS_HEAP_CORRUPTION"; case STATUS_SMARTCARD_WRONG_PIN: return "STATUS_SMARTCARD_WRONG_PIN"; case STATUS_SMARTCARD_CARD_BLOCKED: return "STATUS_SMARTCARD_CARD_BLOCKED"; case STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED: return "STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED"; case STATUS_SMARTCARD_NO_CARD: return "STATUS_SMARTCARD_NO_CARD"; case STATUS_SMARTCARD_NO_KEY_CONTAINER: return "STATUS_SMARTCARD_NO_KEY_CONTAINER"; case STATUS_SMARTCARD_NO_CERTIFICATE: return "STATUS_SMARTCARD_NO_CERTIFICATE"; case STATUS_SMARTCARD_NO_KEYSET: return "STATUS_SMARTCARD_NO_KEYSET"; case STATUS_SMARTCARD_IO_ERROR: return "STATUS_SMARTCARD_IO_ERROR"; case STATUS_DOWNGRADE_DETECTED: return "STATUS_DOWNGRADE_DETECTED"; case STATUS_SMARTCARD_CERT_REVOKED: return "STATUS_SMARTCARD_CERT_REVOKED"; case STATUS_ISSUING_CA_UNTRUSTED: return "STATUS_ISSUING_CA_UNTRUSTED"; case STATUS_REVOCATION_OFFLINE_C: return "STATUS_REVOCATION_OFFLINE_C"; case STATUS_PKINIT_CLIENT_FAILURE: return "STATUS_PKINIT_CLIENT_FAILURE"; case STATUS_SMARTCARD_CERT_EXPIRED: return "STATUS_SMARTCARD_CERT_EXPIRED"; case STATUS_DRIVER_FAILED_PRIOR_UNLOAD: return "STATUS_DRIVER_FAILED_PRIOR_UNLOAD"; case STATUS_SMARTCARD_SILENT_CONTEXT: return "STATUS_SMARTCARD_SILENT_CONTEXT"; case STATUS_PER_USER_TRUST_QUOTA_EXCEEDED: return "STATUS_PER_USER_TRUST_QUOTA_EXCEEDED"; case STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED: return "STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED"; case STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED: return "STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED"; case STATUS_DS_NAME_NOT_UNIQUE: return "STATUS_DS_NAME_NOT_UNIQUE"; case STATUS_DS_DUPLICATE_ID_FOUND: return "STATUS_DS_DUPLICATE_ID_FOUND"; case STATUS_DS_GROUP_CONVERSION_ERROR: return "STATUS_DS_GROUP_CONVERSION_ERROR"; case STATUS_VOLSNAP_PREPARE_HIBERNATE: return "STATUS_VOLSNAP_PREPARE_HIBERNATE"; case STATUS_USER2USER_REQUIRED: return "STATUS_USER2USER_REQUIRED"; case STATUS_STACK_BUFFER_OVERRUN: return "STATUS_STACK_BUFFER_OVERRUN"; case STATUS_NO_S4U_PROT_SUPPORT: return "STATUS_NO_S4U_PROT_SUPPORT"; case STATUS_CROSSREALM_DELEGATION_FAILURE: return "STATUS_CROSSREALM_DELEGATION_FAILURE"; case STATUS_REVOCATION_OFFLINE_KDC: return "STATUS_REVOCATION_OFFLINE_KDC"; case STATUS_ISSUING_CA_UNTRUSTED_KDC: return "STATUS_ISSUING_CA_UNTRUSTED_KDC"; case STATUS_KDC_CERT_EXPIRED: return "STATUS_KDC_CERT_EXPIRED"; case STATUS_KDC_CERT_REVOKED: return "STATUS_KDC_CERT_REVOKED"; case STATUS_PARAMETER_QUOTA_EXCEEDED: return "STATUS_PARAMETER_QUOTA_EXCEEDED"; case STATUS_HIBERNATION_FAILURE: return "STATUS_HIBERNATION_FAILURE"; case STATUS_DELAY_LOAD_FAILED: return "STATUS_DELAY_LOAD_FAILED"; case STATUS_AUTHENTICATION_FIREWALL_FAILED: return "STATUS_AUTHENTICATION_FIREWALL_FAILED"; case STATUS_VDM_DISALLOWED: return "STATUS_VDM_DISALLOWED"; case STATUS_HUNG_DISPLAY_DRIVER_THREAD: return "STATUS_HUNG_DISPLAY_DRIVER_THREAD"; case STATUS_INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE: return "STATUS_INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE"; case STATUS_INVALID_CRUNTIME_PARAMETER: return "STATUS_INVALID_CRUNTIME_PARAMETER"; case STATUS_NTLM_BLOCKED: return "STATUS_NTLM_BLOCKED"; case STATUS_DS_SRC_SID_EXISTS_IN_FOREST: return "STATUS_DS_SRC_SID_EXISTS_IN_FOREST"; case STATUS_DS_DOMAIN_NAME_EXISTS_IN_FOREST: return "STATUS_DS_DOMAIN_NAME_EXISTS_IN_FOREST"; case STATUS_DS_FLAT_NAME_EXISTS_IN_FOREST: return "STATUS_DS_FLAT_NAME_EXISTS_IN_FOREST"; case STATUS_INVALID_USER_PRINCIPAL_NAME: return "STATUS_INVALID_USER_PRINCIPAL_NAME"; case STATUS_ASSERTION_FAILURE: return "STATUS_ASSERTION_FAILURE"; case STATUS_VERIFIER_STOP: return "STATUS_VERIFIER_STOP"; case STATUS_CALLBACK_POP_STACK: return "STATUS_CALLBACK_POP_STACK"; case STATUS_INCOMPATIBLE_DRIVER_BLOCKED: return "STATUS_INCOMPATIBLE_DRIVER_BLOCKED"; case STATUS_HIVE_UNLOADED: return "STATUS_HIVE_UNLOADED"; case STATUS_COMPRESSION_DISABLED: return "STATUS_COMPRESSION_DISABLED"; case STATUS_FILE_SYSTEM_LIMITATION: return "STATUS_FILE_SYSTEM_LIMITATION"; case STATUS_INVALID_IMAGE_HASH: return "STATUS_INVALID_IMAGE_HASH"; case STATUS_NOT_CAPABLE: return "STATUS_NOT_CAPABLE"; case STATUS_REQUEST_OUT_OF_SEQUENCE: return "STATUS_REQUEST_OUT_OF_SEQUENCE"; case STATUS_IMPLEMENTATION_LIMIT: return "STATUS_IMPLEMENTATION_LIMIT"; case STATUS_ELEVATION_REQUIRED: return "STATUS_ELEVATION_REQUIRED"; case STATUS_NO_SECURITY_CONTEXT: return "STATUS_NO_SECURITY_CONTEXT"; case STATUS_PKU2U_CERT_FAILURE: return "STATUS_PKU2U_CERT_FAILURE"; case STATUS_BEYOND_VDL: return "STATUS_BEYOND_VDL"; case STATUS_ENCOUNTERED_WRITE_IN_PROGRESS: return "STATUS_ENCOUNTERED_WRITE_IN_PROGRESS"; case STATUS_PTE_CHANGED: return "STATUS_PTE_CHANGED"; case STATUS_PURGE_FAILED: return "STATUS_PURGE_FAILED"; case STATUS_CRED_REQUIRES_CONFIRMATION: return "STATUS_CRED_REQUIRES_CONFIRMATION"; case STATUS_CS_ENCRYPTION_INVALID_SERVER_RESPONSE: return "STATUS_CS_ENCRYPTION_INVALID_SERVER_RESPONSE"; case STATUS_CS_ENCRYPTION_UNSUPPORTED_SERVER: return "STATUS_CS_ENCRYPTION_UNSUPPORTED_SERVER"; case STATUS_CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE: return "STATUS_CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE"; case STATUS_CS_ENCRYPTION_NEW_ENCRYPTED_FILE: return "STATUS_CS_ENCRYPTION_NEW_ENCRYPTED_FILE"; case STATUS_CS_ENCRYPTION_FILE_NOT_CSE: return "STATUS_CS_ENCRYPTION_FILE_NOT_CSE"; case STATUS_INVALID_LABEL: return "STATUS_INVALID_LABEL"; case STATUS_DRIVER_PROCESS_TERMINATED: return "STATUS_DRIVER_PROCESS_TERMINATED"; case STATUS_AMBIGUOUS_SYSTEM_DEVICE: return "STATUS_AMBIGUOUS_SYSTEM_DEVICE"; case STATUS_SYSTEM_DEVICE_NOT_FOUND: return "STATUS_SYSTEM_DEVICE_NOT_FOUND"; case STATUS_RESTART_BOOT_APPLICATION: return "STATUS_RESTART_BOOT_APPLICATION"; case STATUS_INSUFFICIENT_NVRAM_RESOURCES: return "STATUS_INSUFFICIENT_NVRAM_RESOURCES"; case STATUS_INVALID_TASK_NAME: return "STATUS_INVALID_TASK_NAME"; case STATUS_INVALID_TASK_INDEX: return "STATUS_INVALID_TASK_INDEX"; case STATUS_THREAD_ALREADY_IN_TASK: return "STATUS_THREAD_ALREADY_IN_TASK"; case STATUS_CALLBACK_BYPASS: return "STATUS_CALLBACK_BYPASS"; case STATUS_FAIL_FAST_EXCEPTION: return "STATUS_FAIL_FAST_EXCEPTION"; case STATUS_IMAGE_CERT_REVOKED: return "STATUS_IMAGE_CERT_REVOKED"; case STATUS_PORT_CLOSED: return "STATUS_PORT_CLOSED"; case STATUS_MESSAGE_LOST: return "STATUS_MESSAGE_LOST"; case STATUS_INVALID_MESSAGE: return "STATUS_INVALID_MESSAGE"; case STATUS_REQUEST_CANCELED: return "STATUS_REQUEST_CANCELED"; case STATUS_RECURSIVE_DISPATCH: return "STATUS_RECURSIVE_DISPATCH"; case STATUS_LPC_RECEIVE_BUFFER_EXPECTED: return "STATUS_LPC_RECEIVE_BUFFER_EXPECTED"; case STATUS_LPC_INVALID_CONNECTION_USAGE: return "STATUS_LPC_INVALID_CONNECTION_USAGE"; case STATUS_LPC_REQUESTS_NOT_ALLOWED: return "STATUS_LPC_REQUESTS_NOT_ALLOWED"; case STATUS_RESOURCE_IN_USE: return "STATUS_RESOURCE_IN_USE"; case STATUS_HARDWARE_MEMORY_ERROR: return "STATUS_HARDWARE_MEMORY_ERROR"; case STATUS_THREADPOOL_HANDLE_EXCEPTION: return "STATUS_THREADPOOL_HANDLE_EXCEPTION"; case STATUS_THREADPOOL_SET_EVENT_ON_COMPLETION_FAILED: return "STATUS_THREADPOOL_SET_EVENT_ON_COMPLETION_FAILED"; case STATUS_THREADPOOL_RELEASE_SEMAPHORE_ON_COMPLETION_FAILED: return "STATUS_THREADPOOL_RELEASE_SEMAPHORE_ON_COMPLETION_FAILED"; case STATUS_THREADPOOL_RELEASE_MUTEX_ON_COMPLETION_FAILED: return "STATUS_THREADPOOL_RELEASE_MUTEX_ON_COMPLETION_FAILED"; case STATUS_THREADPOOL_FREE_LIBRARY_ON_COMPLETION_FAILED: return "STATUS_THREADPOOL_FREE_LIBRARY_ON_COMPLETION_FAILED"; case STATUS_THREADPOOL_RELEASED_DURING_OPERATION: return "STATUS_THREADPOOL_RELEASED_DURING_OPERATION"; case STATUS_CALLBACK_RETURNED_WHILE_IMPERSONATING: return "STATUS_CALLBACK_RETURNED_WHILE_IMPERSONATING"; case STATUS_APC_RETURNED_WHILE_IMPERSONATING: return "STATUS_APC_RETURNED_WHILE_IMPERSONATING"; case STATUS_PROCESS_IS_PROTECTED: return "STATUS_PROCESS_IS_PROTECTED"; case STATUS_MCA_EXCEPTION: return "STATUS_MCA_EXCEPTION"; case STATUS_CERTIFICATE_MAPPING_NOT_UNIQUE: return "STATUS_CERTIFICATE_MAPPING_NOT_UNIQUE"; case STATUS_SYMLINK_CLASS_DISABLED: return "STATUS_SYMLINK_CLASS_DISABLED"; case STATUS_INVALID_IDN_NORMALIZATION: return "STATUS_INVALID_IDN_NORMALIZATION"; case STATUS_NO_UNICODE_TRANSLATION: return "STATUS_NO_UNICODE_TRANSLATION"; case STATUS_ALREADY_REGISTERED: return "STATUS_ALREADY_REGISTERED"; case STATUS_CONTEXT_MISMATCH: return "STATUS_CONTEXT_MISMATCH"; case STATUS_PORT_ALREADY_HAS_COMPLETION_LIST: return "STATUS_PORT_ALREADY_HAS_COMPLETION_LIST"; case STATUS_CALLBACK_RETURNED_THREAD_PRIORITY: return "STATUS_CALLBACK_RETURNED_THREAD_PRIORITY"; case STATUS_INVALID_THREAD: return "STATUS_INVALID_THREAD"; case STATUS_CALLBACK_RETURNED_TRANSACTION: return "STATUS_CALLBACK_RETURNED_TRANSACTION"; case STATUS_CALLBACK_RETURNED_LDR_LOCK: return "STATUS_CALLBACK_RETURNED_LDR_LOCK"; case STATUS_CALLBACK_RETURNED_LANG: return "STATUS_CALLBACK_RETURNED_LANG"; case STATUS_CALLBACK_RETURNED_PRI_BACK: return "STATUS_CALLBACK_RETURNED_PRI_BACK"; case STATUS_DISK_REPAIR_DISABLED: return "STATUS_DISK_REPAIR_DISABLED"; case STATUS_DS_DOMAIN_RENAME_IN_PROGRESS: return "STATUS_DS_DOMAIN_RENAME_IN_PROGRESS"; case STATUS_DISK_QUOTA_EXCEEDED: return "STATUS_DISK_QUOTA_EXCEEDED"; case STATUS_CONTENT_BLOCKED: return "STATUS_CONTENT_BLOCKED"; case STATUS_BAD_CLUSTERS: return "STATUS_BAD_CLUSTERS"; case STATUS_VOLUME_DIRTY: return "STATUS_VOLUME_DIRTY"; case STATUS_FILE_CHECKED_OUT: return "STATUS_FILE_CHECKED_OUT"; case STATUS_CHECKOUT_REQUIRED: return "STATUS_CHECKOUT_REQUIRED"; case STATUS_BAD_FILE_TYPE: return "STATUS_BAD_FILE_TYPE"; case STATUS_FILE_TOO_LARGE: return "STATUS_FILE_TOO_LARGE"; case STATUS_FORMS_AUTH_REQUIRED: return "STATUS_FORMS_AUTH_REQUIRED"; case STATUS_VIRUS_INFECTED: return "STATUS_VIRUS_INFECTED"; case STATUS_VIRUS_DELETED: return "STATUS_VIRUS_DELETED"; case STATUS_BAD_MCFG_TABLE: return "STATUS_BAD_MCFG_TABLE"; case STATUS_CANNOT_BREAK_OPLOCK: return "STATUS_CANNOT_BREAK_OPLOCK"; case STATUS_WOW_ASSERTION: return "STATUS_WOW_ASSERTION"; case STATUS_INVALID_SIGNATURE: return "STATUS_INVALID_SIGNATURE"; case STATUS_HMAC_NOT_SUPPORTED: return "STATUS_HMAC_NOT_SUPPORTED"; case STATUS_IPSEC_QUEUE_OVERFLOW: return "STATUS_IPSEC_QUEUE_OVERFLOW"; case STATUS_ND_QUEUE_OVERFLOW: return "STATUS_ND_QUEUE_OVERFLOW"; case STATUS_HOPLIMIT_EXCEEDED: return "STATUS_HOPLIMIT_EXCEEDED"; case STATUS_PROTOCOL_NOT_SUPPORTED: return "STATUS_PROTOCOL_NOT_SUPPORTED"; case STATUS_LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED: return "STATUS_LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED"; case STATUS_LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR: return "STATUS_LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR"; case STATUS_LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR: return "STATUS_LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR"; case STATUS_XML_PARSE_ERROR: return "STATUS_XML_PARSE_ERROR"; case STATUS_XMLDSIG_ERROR: return "STATUS_XMLDSIG_ERROR"; case STATUS_WRONG_COMPARTMENT: return "STATUS_WRONG_COMPARTMENT"; case STATUS_AUTHIP_FAILURE: return "STATUS_AUTHIP_FAILURE"; case STATUS_DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS: return "STATUS_DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS"; case STATUS_DS_OID_NOT_FOUND: return "STATUS_DS_OID_NOT_FOUND"; case STATUS_HASH_NOT_SUPPORTED: return "STATUS_HASH_NOT_SUPPORTED"; case STATUS_HASH_NOT_PRESENT: return "STATUS_HASH_NOT_PRESENT"; case DBG_NO_STATE_CHANGE: return "DBG_NO_STATE_CHANGE"; case DBG_APP_NOT_IDLE: return "DBG_APP_NOT_IDLE"; case RPC_NT_INVALID_STRING_BINDING: return "RPC_NT_INVALID_STRING_BINDING"; case RPC_NT_WRONG_KIND_OF_BINDING: return "RPC_NT_WRONG_KIND_OF_BINDING"; case RPC_NT_INVALID_BINDING: return "RPC_NT_INVALID_BINDING"; case RPC_NT_PROTSEQ_NOT_SUPPORTED: return "RPC_NT_PROTSEQ_NOT_SUPPORTED"; case RPC_NT_INVALID_RPC_PROTSEQ: return "RPC_NT_INVALID_RPC_PROTSEQ"; case RPC_NT_INVALID_STRING_UUID: return "RPC_NT_INVALID_STRING_UUID"; case RPC_NT_INVALID_ENDPOINT_FORMAT: return "RPC_NT_INVALID_ENDPOINT_FORMAT"; case RPC_NT_INVALID_NET_ADDR: return "RPC_NT_INVALID_NET_ADDR"; case RPC_NT_NO_ENDPOINT_FOUND: return "RPC_NT_NO_ENDPOINT_FOUND"; case RPC_NT_INVALID_TIMEOUT: return "RPC_NT_INVALID_TIMEOUT"; case RPC_NT_OBJECT_NOT_FOUND: return "RPC_NT_OBJECT_NOT_FOUND"; case RPC_NT_ALREADY_REGISTERED: return "RPC_NT_ALREADY_REGISTERED"; case RPC_NT_TYPE_ALREADY_REGISTERED: return "RPC_NT_TYPE_ALREADY_REGISTERED"; case RPC_NT_ALREADY_LISTENING: return "RPC_NT_ALREADY_LISTENING"; case RPC_NT_NO_PROTSEQS_REGISTERED: return "RPC_NT_NO_PROTSEQS_REGISTERED"; case RPC_NT_NOT_LISTENING: return "RPC_NT_NOT_LISTENING"; case RPC_NT_UNKNOWN_MGR_TYPE: return "RPC_NT_UNKNOWN_MGR_TYPE"; case RPC_NT_UNKNOWN_IF: return "RPC_NT_UNKNOWN_IF"; case RPC_NT_NO_BINDINGS: return "RPC_NT_NO_BINDINGS"; case RPC_NT_NO_PROTSEQS: return "RPC_NT_NO_PROTSEQS"; case RPC_NT_CANT_CREATE_ENDPOINT: return "RPC_NT_CANT_CREATE_ENDPOINT"; case RPC_NT_OUT_OF_RESOURCES: return "RPC_NT_OUT_OF_RESOURCES"; case RPC_NT_SERVER_UNAVAILABLE: return "RPC_NT_SERVER_UNAVAILABLE"; case RPC_NT_SERVER_TOO_BUSY: return "RPC_NT_SERVER_TOO_BUSY"; case RPC_NT_INVALID_NETWORK_OPTIONS: return "RPC_NT_INVALID_NETWORK_OPTIONS"; case RPC_NT_NO_CALL_ACTIVE: return "RPC_NT_NO_CALL_ACTIVE"; case RPC_NT_CALL_FAILED: return "RPC_NT_CALL_FAILED"; case RPC_NT_CALL_FAILED_DNE: return "RPC_NT_CALL_FAILED_DNE"; case RPC_NT_PROTOCOL_ERROR: return "RPC_NT_PROTOCOL_ERROR"; case RPC_NT_UNSUPPORTED_TRANS_SYN: return "RPC_NT_UNSUPPORTED_TRANS_SYN"; case RPC_NT_UNSUPPORTED_TYPE: return "RPC_NT_UNSUPPORTED_TYPE"; case RPC_NT_INVALID_TAG: return "RPC_NT_INVALID_TAG"; case RPC_NT_INVALID_BOUND: return "RPC_NT_INVALID_BOUND"; case RPC_NT_NO_ENTRY_NAME: return "RPC_NT_NO_ENTRY_NAME"; case RPC_NT_INVALID_NAME_SYNTAX: return "RPC_NT_INVALID_NAME_SYNTAX"; case RPC_NT_UNSUPPORTED_NAME_SYNTAX: return "RPC_NT_UNSUPPORTED_NAME_SYNTAX"; case RPC_NT_UUID_NO_ADDRESS: return "RPC_NT_UUID_NO_ADDRESS"; case RPC_NT_DUPLICATE_ENDPOINT: return "RPC_NT_DUPLICATE_ENDPOINT"; case RPC_NT_UNKNOWN_AUTHN_TYPE: return "RPC_NT_UNKNOWN_AUTHN_TYPE"; case RPC_NT_MAX_CALLS_TOO_SMALL: return "RPC_NT_MAX_CALLS_TOO_SMALL"; case RPC_NT_STRING_TOO_LONG: return "RPC_NT_STRING_TOO_LONG"; case RPC_NT_PROTSEQ_NOT_FOUND: return "RPC_NT_PROTSEQ_NOT_FOUND"; case RPC_NT_PROCNUM_OUT_OF_RANGE: return "RPC_NT_PROCNUM_OUT_OF_RANGE"; case RPC_NT_BINDING_HAS_NO_AUTH: return "RPC_NT_BINDING_HAS_NO_AUTH"; case RPC_NT_UNKNOWN_AUTHN_SERVICE: return "RPC_NT_UNKNOWN_AUTHN_SERVICE"; case RPC_NT_UNKNOWN_AUTHN_LEVEL: return "RPC_NT_UNKNOWN_AUTHN_LEVEL"; case RPC_NT_INVALID_AUTH_IDENTITY: return "RPC_NT_INVALID_AUTH_IDENTITY"; case RPC_NT_UNKNOWN_AUTHZ_SERVICE: return "RPC_NT_UNKNOWN_AUTHZ_SERVICE"; case EPT_NT_INVALID_ENTRY: return "EPT_NT_INVALID_ENTRY"; case EPT_NT_CANT_PERFORM_OP: return "EPT_NT_CANT_PERFORM_OP"; case EPT_NT_NOT_REGISTERED: return "EPT_NT_NOT_REGISTERED"; case RPC_NT_NOTHING_TO_EXPORT: return "RPC_NT_NOTHING_TO_EXPORT"; case RPC_NT_INCOMPLETE_NAME: return "RPC_NT_INCOMPLETE_NAME"; case RPC_NT_INVALID_VERS_OPTION: return "RPC_NT_INVALID_VERS_OPTION"; case RPC_NT_NO_MORE_MEMBERS: return "RPC_NT_NO_MORE_MEMBERS"; case RPC_NT_NOT_ALL_OBJS_UNEXPORTED: return "RPC_NT_NOT_ALL_OBJS_UNEXPORTED"; case RPC_NT_INTERFACE_NOT_FOUND: return "RPC_NT_INTERFACE_NOT_FOUND"; case RPC_NT_ENTRY_ALREADY_EXISTS: return "RPC_NT_ENTRY_ALREADY_EXISTS"; case RPC_NT_ENTRY_NOT_FOUND: return "RPC_NT_ENTRY_NOT_FOUND"; case RPC_NT_NAME_SERVICE_UNAVAILABLE: return "RPC_NT_NAME_SERVICE_UNAVAILABLE"; case RPC_NT_INVALID_NAF_ID: return "RPC_NT_INVALID_NAF_ID"; case RPC_NT_CANNOT_SUPPORT: return "RPC_NT_CANNOT_SUPPORT"; case RPC_NT_NO_CONTEXT_AVAILABLE: return "RPC_NT_NO_CONTEXT_AVAILABLE"; case RPC_NT_INTERNAL_ERROR: return "RPC_NT_INTERNAL_ERROR"; case RPC_NT_ZERO_DIVIDE: return "RPC_NT_ZERO_DIVIDE"; case RPC_NT_ADDRESS_ERROR: return "RPC_NT_ADDRESS_ERROR"; case RPC_NT_FP_DIV_ZERO: return "RPC_NT_FP_DIV_ZERO"; case RPC_NT_FP_UNDERFLOW: return "RPC_NT_FP_UNDERFLOW"; case RPC_NT_FP_OVERFLOW: return "RPC_NT_FP_OVERFLOW"; case RPC_NT_CALL_IN_PROGRESS: return "RPC_NT_CALL_IN_PROGRESS"; case RPC_NT_NO_MORE_BINDINGS: return "RPC_NT_NO_MORE_BINDINGS"; case RPC_NT_GROUP_MEMBER_NOT_FOUND: return "RPC_NT_GROUP_MEMBER_NOT_FOUND"; case EPT_NT_CANT_CREATE: return "EPT_NT_CANT_CREATE"; case RPC_NT_INVALID_OBJECT: return "RPC_NT_INVALID_OBJECT"; case RPC_NT_NO_INTERFACES: return "RPC_NT_NO_INTERFACES"; case RPC_NT_CALL_CANCELLED: return "RPC_NT_CALL_CANCELLED"; case RPC_NT_BINDING_INCOMPLETE: return "RPC_NT_BINDING_INCOMPLETE"; case RPC_NT_COMM_FAILURE: return "RPC_NT_COMM_FAILURE"; case RPC_NT_UNSUPPORTED_AUTHN_LEVEL: return "RPC_NT_UNSUPPORTED_AUTHN_LEVEL"; case RPC_NT_NO_PRINC_NAME: return "RPC_NT_NO_PRINC_NAME"; case RPC_NT_NOT_RPC_ERROR: return "RPC_NT_NOT_RPC_ERROR"; case RPC_NT_SEC_PKG_ERROR: return "RPC_NT_SEC_PKG_ERROR"; case RPC_NT_NOT_CANCELLED: return "RPC_NT_NOT_CANCELLED"; case RPC_NT_INVALID_ASYNC_HANDLE: return "RPC_NT_INVALID_ASYNC_HANDLE"; case RPC_NT_INVALID_ASYNC_CALL: return "RPC_NT_INVALID_ASYNC_CALL"; case RPC_NT_PROXY_ACCESS_DENIED: return "RPC_NT_PROXY_ACCESS_DENIED"; case RPC_NT_NO_MORE_ENTRIES: return "RPC_NT_NO_MORE_ENTRIES"; case RPC_NT_SS_CHAR_TRANS_OPEN_FAIL: return "RPC_NT_SS_CHAR_TRANS_OPEN_FAIL"; case RPC_NT_SS_CHAR_TRANS_SHORT_FILE: return "RPC_NT_SS_CHAR_TRANS_SHORT_FILE"; case RPC_NT_SS_IN_NULL_CONTEXT: return "RPC_NT_SS_IN_NULL_CONTEXT"; case RPC_NT_SS_CONTEXT_MISMATCH: return "RPC_NT_SS_CONTEXT_MISMATCH"; case RPC_NT_SS_CONTEXT_DAMAGED: return "RPC_NT_SS_CONTEXT_DAMAGED"; case RPC_NT_SS_HANDLES_MISMATCH: return "RPC_NT_SS_HANDLES_MISMATCH"; case RPC_NT_SS_CANNOT_GET_CALL_HANDLE: return "RPC_NT_SS_CANNOT_GET_CALL_HANDLE"; case RPC_NT_NULL_REF_POINTER: return "RPC_NT_NULL_REF_POINTER"; case RPC_NT_ENUM_VALUE_OUT_OF_RANGE: return "RPC_NT_ENUM_VALUE_OUT_OF_RANGE"; case RPC_NT_BYTE_COUNT_TOO_SMALL: return "RPC_NT_BYTE_COUNT_TOO_SMALL"; case RPC_NT_BAD_STUB_DATA: return "RPC_NT_BAD_STUB_DATA"; case RPC_NT_INVALID_ES_ACTION: return "RPC_NT_INVALID_ES_ACTION"; case RPC_NT_WRONG_ES_VERSION: return "RPC_NT_WRONG_ES_VERSION"; case RPC_NT_WRONG_STUB_VERSION: return "RPC_NT_WRONG_STUB_VERSION"; case RPC_NT_INVALID_PIPE_OBJECT: return "RPC_NT_INVALID_PIPE_OBJECT"; case RPC_NT_INVALID_PIPE_OPERATION: return "RPC_NT_INVALID_PIPE_OPERATION"; case RPC_NT_WRONG_PIPE_VERSION: return "RPC_NT_WRONG_PIPE_VERSION"; case RPC_NT_PIPE_CLOSED: return "RPC_NT_PIPE_CLOSED"; case RPC_NT_PIPE_DISCIPLINE_ERROR: return "RPC_NT_PIPE_DISCIPLINE_ERROR"; case RPC_NT_PIPE_EMPTY: return "RPC_NT_PIPE_EMPTY"; case STATUS_PNP_BAD_MPS_TABLE: return "STATUS_PNP_BAD_MPS_TABLE"; case STATUS_PNP_TRANSLATION_FAILED: return "STATUS_PNP_TRANSLATION_FAILED"; case STATUS_PNP_IRQ_TRANSLATION_FAILED: return "STATUS_PNP_IRQ_TRANSLATION_FAILED"; case STATUS_PNP_INVALID_ID: return "STATUS_PNP_INVALID_ID"; case STATUS_IO_REISSUE_AS_CACHED: return "STATUS_IO_REISSUE_AS_CACHED"; case STATUS_CTX_WINSTATION_NAME_INVALID: return "STATUS_CTX_WINSTATION_NAME_INVALID"; case STATUS_CTX_INVALID_PD: return "STATUS_CTX_INVALID_PD"; case STATUS_CTX_PD_NOT_FOUND: return "STATUS_CTX_PD_NOT_FOUND"; case STATUS_CTX_CLOSE_PENDING: return "STATUS_CTX_CLOSE_PENDING"; case STATUS_CTX_NO_OUTBUF: return "STATUS_CTX_NO_OUTBUF"; case STATUS_CTX_MODEM_INF_NOT_FOUND: return "STATUS_CTX_MODEM_INF_NOT_FOUND"; case STATUS_CTX_INVALID_MODEMNAME: return "STATUS_CTX_INVALID_MODEMNAME"; case STATUS_CTX_RESPONSE_ERROR: return "STATUS_CTX_RESPONSE_ERROR"; case STATUS_CTX_MODEM_RESPONSE_TIMEOUT: return "STATUS_CTX_MODEM_RESPONSE_TIMEOUT"; case STATUS_CTX_MODEM_RESPONSE_NO_CARRIER: return "STATUS_CTX_MODEM_RESPONSE_NO_CARRIER"; case STATUS_CTX_MODEM_RESPONSE_NO_DIALTONE: return "STATUS_CTX_MODEM_RESPONSE_NO_DIALTONE"; case STATUS_CTX_MODEM_RESPONSE_BUSY: return "STATUS_CTX_MODEM_RESPONSE_BUSY"; case STATUS_CTX_MODEM_RESPONSE_VOICE: return "STATUS_CTX_MODEM_RESPONSE_VOICE"; case STATUS_CTX_TD_ERROR: return "STATUS_CTX_TD_ERROR"; case STATUS_CTX_LICENSE_CLIENT_INVALID: return "STATUS_CTX_LICENSE_CLIENT_INVALID"; case STATUS_CTX_LICENSE_NOT_AVAILABLE: return "STATUS_CTX_LICENSE_NOT_AVAILABLE"; case STATUS_CTX_LICENSE_EXPIRED: return "STATUS_CTX_LICENSE_EXPIRED"; case STATUS_CTX_WINSTATION_NOT_FOUND: return "STATUS_CTX_WINSTATION_NOT_FOUND"; case STATUS_CTX_WINSTATION_NAME_COLLISION: return "STATUS_CTX_WINSTATION_NAME_COLLISION"; case STATUS_CTX_WINSTATION_BUSY: return "STATUS_CTX_WINSTATION_BUSY"; case STATUS_CTX_BAD_VIDEO_MODE: return "STATUS_CTX_BAD_VIDEO_MODE"; case STATUS_CTX_GRAPHICS_INVALID: return "STATUS_CTX_GRAPHICS_INVALID"; case STATUS_CTX_NOT_CONSOLE: return "STATUS_CTX_NOT_CONSOLE"; case STATUS_CTX_CLIENT_QUERY_TIMEOUT: return "STATUS_CTX_CLIENT_QUERY_TIMEOUT"; case STATUS_CTX_CONSOLE_DISCONNECT: return "STATUS_CTX_CONSOLE_DISCONNECT"; case STATUS_CTX_CONSOLE_CONNECT: return "STATUS_CTX_CONSOLE_CONNECT"; case STATUS_CTX_SHADOW_DENIED: return "STATUS_CTX_SHADOW_DENIED"; case STATUS_CTX_WINSTATION_ACCESS_DENIED: return "STATUS_CTX_WINSTATION_ACCESS_DENIED"; case STATUS_CTX_INVALID_WD: return "STATUS_CTX_INVALID_WD"; case STATUS_CTX_WD_NOT_FOUND: return "STATUS_CTX_WD_NOT_FOUND"; case STATUS_CTX_SHADOW_INVALID: return "STATUS_CTX_SHADOW_INVALID"; case STATUS_CTX_SHADOW_DISABLED: return "STATUS_CTX_SHADOW_DISABLED"; case STATUS_RDP_PROTOCOL_ERROR: return "STATUS_RDP_PROTOCOL_ERROR"; case STATUS_CTX_CLIENT_LICENSE_NOT_SET: return "STATUS_CTX_CLIENT_LICENSE_NOT_SET"; case STATUS_CTX_CLIENT_LICENSE_IN_USE: return "STATUS_CTX_CLIENT_LICENSE_IN_USE"; case STATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE: return "STATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE"; case STATUS_CTX_SHADOW_NOT_RUNNING: return "STATUS_CTX_SHADOW_NOT_RUNNING"; case STATUS_CTX_LOGON_DISABLED: return "STATUS_CTX_LOGON_DISABLED"; case STATUS_CTX_SECURITY_LAYER_ERROR: return "STATUS_CTX_SECURITY_LAYER_ERROR"; case STATUS_TS_INCOMPATIBLE_SESSIONS: return "STATUS_TS_INCOMPATIBLE_SESSIONS"; case STATUS_MUI_FILE_NOT_FOUND: return "STATUS_MUI_FILE_NOT_FOUND"; case STATUS_MUI_INVALID_FILE: return "STATUS_MUI_INVALID_FILE"; case STATUS_MUI_INVALID_RC_CONFIG: return "STATUS_MUI_INVALID_RC_CONFIG"; case STATUS_MUI_INVALID_LOCALE_NAME: return "STATUS_MUI_INVALID_LOCALE_NAME"; case STATUS_MUI_INVALID_ULTIMATEFALLBACK_NAME: return "STATUS_MUI_INVALID_ULTIMATEFALLBACK_NAME"; case STATUS_MUI_FILE_NOT_LOADED: return "STATUS_MUI_FILE_NOT_LOADED"; case STATUS_RESOURCE_ENUM_USER_STOP: return "STATUS_RESOURCE_ENUM_USER_STOP"; case STATUS_CLUSTER_INVALID_NODE: return "STATUS_CLUSTER_INVALID_NODE"; case STATUS_CLUSTER_NODE_EXISTS: return "STATUS_CLUSTER_NODE_EXISTS"; case STATUS_CLUSTER_JOIN_IN_PROGRESS: return "STATUS_CLUSTER_JOIN_IN_PROGRESS"; case STATUS_CLUSTER_NODE_NOT_FOUND: return "STATUS_CLUSTER_NODE_NOT_FOUND"; case STATUS_CLUSTER_LOCAL_NODE_NOT_FOUND: return "STATUS_CLUSTER_LOCAL_NODE_NOT_FOUND"; case STATUS_CLUSTER_NETWORK_EXISTS: return "STATUS_CLUSTER_NETWORK_EXISTS"; case STATUS_CLUSTER_NETWORK_NOT_FOUND: return "STATUS_CLUSTER_NETWORK_NOT_FOUND"; case STATUS_CLUSTER_NETINTERFACE_EXISTS: return "STATUS_CLUSTER_NETINTERFACE_EXISTS"; case STATUS_CLUSTER_NETINTERFACE_NOT_FOUND: return "STATUS_CLUSTER_NETINTERFACE_NOT_FOUND"; case STATUS_CLUSTER_INVALID_REQUEST: return "STATUS_CLUSTER_INVALID_REQUEST"; case STATUS_CLUSTER_INVALID_NETWORK_PROVIDER: return "STATUS_CLUSTER_INVALID_NETWORK_PROVIDER"; case STATUS_CLUSTER_NODE_DOWN: return "STATUS_CLUSTER_NODE_DOWN"; case STATUS_CLUSTER_NODE_UNREACHABLE: return "STATUS_CLUSTER_NODE_UNREACHABLE"; case STATUS_CLUSTER_NODE_NOT_MEMBER: return "STATUS_CLUSTER_NODE_NOT_MEMBER"; case STATUS_CLUSTER_JOIN_NOT_IN_PROGRESS: return "STATUS_CLUSTER_JOIN_NOT_IN_PROGRESS"; case STATUS_CLUSTER_INVALID_NETWORK: return "STATUS_CLUSTER_INVALID_NETWORK"; case STATUS_CLUSTER_NO_NET_ADAPTERS: return "STATUS_CLUSTER_NO_NET_ADAPTERS"; case STATUS_CLUSTER_NODE_UP: return "STATUS_CLUSTER_NODE_UP"; case STATUS_CLUSTER_NODE_PAUSED: return "STATUS_CLUSTER_NODE_PAUSED"; case STATUS_CLUSTER_NODE_NOT_PAUSED: return "STATUS_CLUSTER_NODE_NOT_PAUSED"; case STATUS_CLUSTER_NO_SECURITY_CONTEXT: return "STATUS_CLUSTER_NO_SECURITY_CONTEXT"; case STATUS_CLUSTER_NETWORK_NOT_INTERNAL: return "STATUS_CLUSTER_NETWORK_NOT_INTERNAL"; case STATUS_CLUSTER_POISONED: return "STATUS_CLUSTER_POISONED"; case STATUS_ACPI_INVALID_OPCODE: return "STATUS_ACPI_INVALID_OPCODE"; case STATUS_ACPI_STACK_OVERFLOW: return "STATUS_ACPI_STACK_OVERFLOW"; case STATUS_ACPI_ASSERT_FAILED: return "STATUS_ACPI_ASSERT_FAILED"; case STATUS_ACPI_INVALID_INDEX: return "STATUS_ACPI_INVALID_INDEX"; case STATUS_ACPI_INVALID_ARGUMENT: return "STATUS_ACPI_INVALID_ARGUMENT"; case STATUS_ACPI_FATAL: return "STATUS_ACPI_FATAL"; case STATUS_ACPI_INVALID_SUPERNAME: return "STATUS_ACPI_INVALID_SUPERNAME"; case STATUS_ACPI_INVALID_ARGTYPE: return "STATUS_ACPI_INVALID_ARGTYPE"; case STATUS_ACPI_INVALID_OBJTYPE: return "STATUS_ACPI_INVALID_OBJTYPE"; case STATUS_ACPI_INVALID_TARGETTYPE: return "STATUS_ACPI_INVALID_TARGETTYPE"; case STATUS_ACPI_INCORRECT_ARGUMENT_COUNT: return "STATUS_ACPI_INCORRECT_ARGUMENT_COUNT"; case STATUS_ACPI_ADDRESS_NOT_MAPPED: return "STATUS_ACPI_ADDRESS_NOT_MAPPED"; case STATUS_ACPI_INVALID_EVENTTYPE: return "STATUS_ACPI_INVALID_EVENTTYPE"; case STATUS_ACPI_HANDLER_COLLISION: return "STATUS_ACPI_HANDLER_COLLISION"; case STATUS_ACPI_INVALID_DATA: return "STATUS_ACPI_INVALID_DATA"; case STATUS_ACPI_INVALID_REGION: return "STATUS_ACPI_INVALID_REGION"; case STATUS_ACPI_INVALID_ACCESS_SIZE: return "STATUS_ACPI_INVALID_ACCESS_SIZE"; case STATUS_ACPI_ACQUIRE_GLOBAL_LOCK: return "STATUS_ACPI_ACQUIRE_GLOBAL_LOCK"; case STATUS_ACPI_ALREADY_INITIALIZED: return "STATUS_ACPI_ALREADY_INITIALIZED"; case STATUS_ACPI_NOT_INITIALIZED: return "STATUS_ACPI_NOT_INITIALIZED"; case STATUS_ACPI_INVALID_MUTEX_LEVEL: return "STATUS_ACPI_INVALID_MUTEX_LEVEL"; case STATUS_ACPI_MUTEX_NOT_OWNED: return "STATUS_ACPI_MUTEX_NOT_OWNED"; case STATUS_ACPI_MUTEX_NOT_OWNER: return "STATUS_ACPI_MUTEX_NOT_OWNER"; case STATUS_ACPI_RS_ACCESS: return "STATUS_ACPI_RS_ACCESS"; case STATUS_ACPI_INVALID_TABLE: return "STATUS_ACPI_INVALID_TABLE"; case STATUS_ACPI_REG_HANDLER_FAILED: return "STATUS_ACPI_REG_HANDLER_FAILED"; case STATUS_ACPI_POWER_REQUEST_FAILED: return "STATUS_ACPI_POWER_REQUEST_FAILED"; case STATUS_SXS_SECTION_NOT_FOUND: return "STATUS_SXS_SECTION_NOT_FOUND"; case STATUS_SXS_CANT_GEN_ACTCTX: return "STATUS_SXS_CANT_GEN_ACTCTX"; case STATUS_SXS_INVALID_ACTCTXDATA_FORMAT: return "STATUS_SXS_INVALID_ACTCTXDATA_FORMAT"; case STATUS_SXS_ASSEMBLY_NOT_FOUND: return "STATUS_SXS_ASSEMBLY_NOT_FOUND"; case STATUS_SXS_MANIFEST_FORMAT_ERROR: return "STATUS_SXS_MANIFEST_FORMAT_ERROR"; case STATUS_SXS_MANIFEST_PARSE_ERROR: return "STATUS_SXS_MANIFEST_PARSE_ERROR"; case STATUS_SXS_ACTIVATION_CONTEXT_DISABLED: return "STATUS_SXS_ACTIVATION_CONTEXT_DISABLED"; case STATUS_SXS_KEY_NOT_FOUND: return "STATUS_SXS_KEY_NOT_FOUND"; case STATUS_SXS_VERSION_CONFLICT: return "STATUS_SXS_VERSION_CONFLICT"; case STATUS_SXS_WRONG_SECTION_TYPE: return "STATUS_SXS_WRONG_SECTION_TYPE"; case STATUS_SXS_THREAD_QUERIES_DISABLED: return "STATUS_SXS_THREAD_QUERIES_DISABLED"; case STATUS_SXS_ASSEMBLY_MISSING: return "STATUS_SXS_ASSEMBLY_MISSING"; case STATUS_SXS_PROCESS_DEFAULT_ALREADY_SET: return "STATUS_SXS_PROCESS_DEFAULT_ALREADY_SET"; case STATUS_SXS_EARLY_DEACTIVATION: return "STATUS_SXS_EARLY_DEACTIVATION"; case STATUS_SXS_INVALID_DEACTIVATION: return "STATUS_SXS_INVALID_DEACTIVATION"; case STATUS_SXS_MULTIPLE_DEACTIVATION: return "STATUS_SXS_MULTIPLE_DEACTIVATION"; case STATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY: return "STATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY"; case STATUS_SXS_PROCESS_TERMINATION_REQUESTED: return "STATUS_SXS_PROCESS_TERMINATION_REQUESTED"; case STATUS_SXS_CORRUPT_ACTIVATION_STACK: return "STATUS_SXS_CORRUPT_ACTIVATION_STACK"; case STATUS_SXS_CORRUPTION: return "STATUS_SXS_CORRUPTION"; case STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE: return "STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE"; case STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_NAME: return "STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_NAME"; case STATUS_SXS_IDENTITY_DUPLICATE_ATTRIBUTE: return "STATUS_SXS_IDENTITY_DUPLICATE_ATTRIBUTE"; case STATUS_SXS_IDENTITY_PARSE_ERROR: return "STATUS_SXS_IDENTITY_PARSE_ERROR"; case STATUS_SXS_COMPONENT_STORE_CORRUPT: return "STATUS_SXS_COMPONENT_STORE_CORRUPT"; case STATUS_SXS_FILE_HASH_MISMATCH: return "STATUS_SXS_FILE_HASH_MISMATCH"; case STATUS_SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT: return "STATUS_SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT"; case STATUS_SXS_IDENTITIES_DIFFERENT: return "STATUS_SXS_IDENTITIES_DIFFERENT"; case STATUS_SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT: return "STATUS_SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT"; case STATUS_SXS_FILE_NOT_PART_OF_ASSEMBLY: return "STATUS_SXS_FILE_NOT_PART_OF_ASSEMBLY"; case STATUS_ADVANCED_INSTALLER_FAILED: return "STATUS_ADVANCED_INSTALLER_FAILED"; case STATUS_XML_ENCODING_MISMATCH: return "STATUS_XML_ENCODING_MISMATCH"; case STATUS_SXS_MANIFEST_TOO_BIG: return "STATUS_SXS_MANIFEST_TOO_BIG"; case STATUS_SXS_SETTING_NOT_REGISTERED: return "STATUS_SXS_SETTING_NOT_REGISTERED"; case STATUS_SXS_TRANSACTION_CLOSURE_INCOMPLETE: return "STATUS_SXS_TRANSACTION_CLOSURE_INCOMPLETE"; case STATUS_SMI_PRIMITIVE_INSTALLER_FAILED: return "STATUS_SMI_PRIMITIVE_INSTALLER_FAILED"; case STATUS_GENERIC_COMMAND_FAILED: return "STATUS_GENERIC_COMMAND_FAILED"; case STATUS_SXS_FILE_HASH_MISSING: return "STATUS_SXS_FILE_HASH_MISSING"; case STATUS_TRANSACTIONAL_CONFLICT: return "STATUS_TRANSACTIONAL_CONFLICT"; case STATUS_INVALID_TRANSACTION: return "STATUS_INVALID_TRANSACTION"; case STATUS_TRANSACTION_NOT_ACTIVE: return "STATUS_TRANSACTION_NOT_ACTIVE"; case STATUS_TM_INITIALIZATION_FAILED: return "STATUS_TM_INITIALIZATION_FAILED"; case STATUS_RM_NOT_ACTIVE: return "STATUS_RM_NOT_ACTIVE"; case STATUS_RM_METADATA_CORRUPT: return "STATUS_RM_METADATA_CORRUPT"; case STATUS_TRANSACTION_NOT_JOINED: return "STATUS_TRANSACTION_NOT_JOINED"; case STATUS_DIRECTORY_NOT_RM: return "STATUS_DIRECTORY_NOT_RM"; case STATUS_TRANSACTIONS_UNSUPPORTED_REMOTE: return "STATUS_TRANSACTIONS_UNSUPPORTED_REMOTE"; case STATUS_LOG_RESIZE_INVALID_SIZE: return "STATUS_LOG_RESIZE_INVALID_SIZE"; case STATUS_REMOTE_FILE_VERSION_MISMATCH: return "STATUS_REMOTE_FILE_VERSION_MISMATCH"; case STATUS_CRM_PROTOCOL_ALREADY_EXISTS: return "STATUS_CRM_PROTOCOL_ALREADY_EXISTS"; case STATUS_TRANSACTION_PROPAGATION_FAILED: return "STATUS_TRANSACTION_PROPAGATION_FAILED"; case STATUS_CRM_PROTOCOL_NOT_FOUND: return "STATUS_CRM_PROTOCOL_NOT_FOUND"; case STATUS_TRANSACTION_SUPERIOR_EXISTS: return "STATUS_TRANSACTION_SUPERIOR_EXISTS"; case STATUS_TRANSACTION_REQUEST_NOT_VALID: return "STATUS_TRANSACTION_REQUEST_NOT_VALID"; case STATUS_TRANSACTION_NOT_REQUESTED: return "STATUS_TRANSACTION_NOT_REQUESTED"; case STATUS_TRANSACTION_ALREADY_ABORTED: return "STATUS_TRANSACTION_ALREADY_ABORTED"; case STATUS_TRANSACTION_ALREADY_COMMITTED: return "STATUS_TRANSACTION_ALREADY_COMMITTED"; case STATUS_TRANSACTION_INVALID_MARSHALL_BUFFER: return "STATUS_TRANSACTION_INVALID_MARSHALL_BUFFER"; case STATUS_CURRENT_TRANSACTION_NOT_VALID: return "STATUS_CURRENT_TRANSACTION_NOT_VALID"; case STATUS_LOG_GROWTH_FAILED: return "STATUS_LOG_GROWTH_FAILED"; case STATUS_OBJECT_NO_LONGER_EXISTS: return "STATUS_OBJECT_NO_LONGER_EXISTS"; case STATUS_STREAM_MINIVERSION_NOT_FOUND: return "STATUS_STREAM_MINIVERSION_NOT_FOUND"; case STATUS_STREAM_MINIVERSION_NOT_VALID: return "STATUS_STREAM_MINIVERSION_NOT_VALID"; case STATUS_MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION: return "STATUS_MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION"; case STATUS_CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT: return "STATUS_CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT"; case STATUS_CANT_CREATE_MORE_STREAM_MINIVERSIONS: return "STATUS_CANT_CREATE_MORE_STREAM_MINIVERSIONS"; case STATUS_HANDLE_NO_LONGER_VALID: return "STATUS_HANDLE_NO_LONGER_VALID"; case STATUS_LOG_CORRUPTION_DETECTED: return "STATUS_LOG_CORRUPTION_DETECTED"; case STATUS_RM_DISCONNECTED: return "STATUS_RM_DISCONNECTED"; case STATUS_ENLISTMENT_NOT_SUPERIOR: return "STATUS_ENLISTMENT_NOT_SUPERIOR"; case STATUS_FILE_IDENTITY_NOT_PERSISTENT: return "STATUS_FILE_IDENTITY_NOT_PERSISTENT"; case STATUS_CANT_BREAK_TRANSACTIONAL_DEPENDENCY: return "STATUS_CANT_BREAK_TRANSACTIONAL_DEPENDENCY"; case STATUS_CANT_CROSS_RM_BOUNDARY: return "STATUS_CANT_CROSS_RM_BOUNDARY"; case STATUS_TXF_DIR_NOT_EMPTY: return "STATUS_TXF_DIR_NOT_EMPTY"; case STATUS_INDOUBT_TRANSACTIONS_EXIST: return "STATUS_INDOUBT_TRANSACTIONS_EXIST"; case STATUS_TM_VOLATILE: return "STATUS_TM_VOLATILE"; case STATUS_ROLLBACK_TIMER_EXPIRED: return "STATUS_ROLLBACK_TIMER_EXPIRED"; case STATUS_TXF_ATTRIBUTE_CORRUPT: return "STATUS_TXF_ATTRIBUTE_CORRUPT"; case STATUS_EFS_NOT_ALLOWED_IN_TRANSACTION: return "STATUS_EFS_NOT_ALLOWED_IN_TRANSACTION"; case STATUS_TRANSACTIONAL_OPEN_NOT_ALLOWED: return "STATUS_TRANSACTIONAL_OPEN_NOT_ALLOWED"; case STATUS_TRANSACTED_MAPPING_UNSUPPORTED_REMOTE: return "STATUS_TRANSACTED_MAPPING_UNSUPPORTED_REMOTE"; case STATUS_TRANSACTION_REQUIRED_PROMOTION: return "STATUS_TRANSACTION_REQUIRED_PROMOTION"; case STATUS_CANNOT_EXECUTE_FILE_IN_TRANSACTION: return "STATUS_CANNOT_EXECUTE_FILE_IN_TRANSACTION"; case STATUS_TRANSACTIONS_NOT_FROZEN: return "STATUS_TRANSACTIONS_NOT_FROZEN"; case STATUS_TRANSACTION_FREEZE_IN_PROGRESS: return "STATUS_TRANSACTION_FREEZE_IN_PROGRESS"; case STATUS_NOT_SNAPSHOT_VOLUME: return "STATUS_NOT_SNAPSHOT_VOLUME"; case STATUS_NO_SAVEPOINT_WITH_OPEN_FILES: return "STATUS_NO_SAVEPOINT_WITH_OPEN_FILES"; case STATUS_SPARSE_NOT_ALLOWED_IN_TRANSACTION: return "STATUS_SPARSE_NOT_ALLOWED_IN_TRANSACTION"; case STATUS_TM_IDENTITY_MISMATCH: return "STATUS_TM_IDENTITY_MISMATCH"; case STATUS_FLOATED_SECTION: return "STATUS_FLOATED_SECTION"; case STATUS_CANNOT_ACCEPT_TRANSACTED_WORK: return "STATUS_CANNOT_ACCEPT_TRANSACTED_WORK"; case STATUS_CANNOT_ABORT_TRANSACTIONS: return "STATUS_CANNOT_ABORT_TRANSACTIONS"; case STATUS_TRANSACTION_NOT_FOUND: return "STATUS_TRANSACTION_NOT_FOUND"; case STATUS_RESOURCEMANAGER_NOT_FOUND: return "STATUS_RESOURCEMANAGER_NOT_FOUND"; case STATUS_ENLISTMENT_NOT_FOUND: return "STATUS_ENLISTMENT_NOT_FOUND"; case STATUS_TRANSACTIONMANAGER_NOT_FOUND: return "STATUS_TRANSACTIONMANAGER_NOT_FOUND"; case STATUS_TRANSACTIONMANAGER_NOT_ONLINE: return "STATUS_TRANSACTIONMANAGER_NOT_ONLINE"; case STATUS_TRANSACTIONMANAGER_RECOVERY_NAME_COLLISION: return "STATUS_TRANSACTIONMANAGER_RECOVERY_NAME_COLLISION"; case STATUS_TRANSACTION_NOT_ROOT: return "STATUS_TRANSACTION_NOT_ROOT"; case STATUS_TRANSACTION_OBJECT_EXPIRED: return "STATUS_TRANSACTION_OBJECT_EXPIRED"; case STATUS_COMPRESSION_NOT_ALLOWED_IN_TRANSACTION: return "STATUS_COMPRESSION_NOT_ALLOWED_IN_TRANSACTION"; case STATUS_TRANSACTION_RESPONSE_NOT_ENLISTED: return "STATUS_TRANSACTION_RESPONSE_NOT_ENLISTED"; case STATUS_TRANSACTION_RECORD_TOO_LONG: return "STATUS_TRANSACTION_RECORD_TOO_LONG"; case STATUS_NO_LINK_TRACKING_IN_TRANSACTION: return "STATUS_NO_LINK_TRACKING_IN_TRANSACTION"; case STATUS_OPERATION_NOT_SUPPORTED_IN_TRANSACTION: return "STATUS_OPERATION_NOT_SUPPORTED_IN_TRANSACTION"; case STATUS_TRANSACTION_INTEGRITY_VIOLATED: return "STATUS_TRANSACTION_INTEGRITY_VIOLATED"; case STATUS_EXPIRED_HANDLE: return "STATUS_EXPIRED_HANDLE"; case STATUS_TRANSACTION_NOT_ENLISTED: return "STATUS_TRANSACTION_NOT_ENLISTED"; case STATUS_LOG_SECTOR_INVALID: return "STATUS_LOG_SECTOR_INVALID"; case STATUS_LOG_SECTOR_PARITY_INVALID: return "STATUS_LOG_SECTOR_PARITY_INVALID"; case STATUS_LOG_SECTOR_REMAPPED: return "STATUS_LOG_SECTOR_REMAPPED"; case STATUS_LOG_BLOCK_INCOMPLETE: return "STATUS_LOG_BLOCK_INCOMPLETE"; case STATUS_LOG_INVALID_RANGE: return "STATUS_LOG_INVALID_RANGE"; case STATUS_LOG_BLOCKS_EXHAUSTED: return "STATUS_LOG_BLOCKS_EXHAUSTED"; case STATUS_LOG_READ_CONTEXT_INVALID: return "STATUS_LOG_READ_CONTEXT_INVALID"; case STATUS_LOG_RESTART_INVALID: return "STATUS_LOG_RESTART_INVALID"; case STATUS_LOG_BLOCK_VERSION: return "STATUS_LOG_BLOCK_VERSION"; case STATUS_LOG_BLOCK_INVALID: return "STATUS_LOG_BLOCK_INVALID"; case STATUS_LOG_READ_MODE_INVALID: return "STATUS_LOG_READ_MODE_INVALID"; case STATUS_LOG_METADATA_CORRUPT: return "STATUS_LOG_METADATA_CORRUPT"; case STATUS_LOG_METADATA_INVALID: return "STATUS_LOG_METADATA_INVALID"; case STATUS_LOG_METADATA_INCONSISTENT: return "STATUS_LOG_METADATA_INCONSISTENT"; case STATUS_LOG_RESERVATION_INVALID: return "STATUS_LOG_RESERVATION_INVALID"; case STATUS_LOG_CANT_DELETE: return "STATUS_LOG_CANT_DELETE"; case STATUS_LOG_CONTAINER_LIMIT_EXCEEDED: return "STATUS_LOG_CONTAINER_LIMIT_EXCEEDED"; case STATUS_LOG_START_OF_LOG: return "STATUS_LOG_START_OF_LOG"; case STATUS_LOG_POLICY_ALREADY_INSTALLED: return "STATUS_LOG_POLICY_ALREADY_INSTALLED"; case STATUS_LOG_POLICY_NOT_INSTALLED: return "STATUS_LOG_POLICY_NOT_INSTALLED"; case STATUS_LOG_POLICY_INVALID: return "STATUS_LOG_POLICY_INVALID"; case STATUS_LOG_POLICY_CONFLICT: return "STATUS_LOG_POLICY_CONFLICT"; case STATUS_LOG_PINNED_ARCHIVE_TAIL: return "STATUS_LOG_PINNED_ARCHIVE_TAIL"; case STATUS_LOG_RECORD_NONEXISTENT: return "STATUS_LOG_RECORD_NONEXISTENT"; case STATUS_LOG_RECORDS_RESERVED_INVALID: return "STATUS_LOG_RECORDS_RESERVED_INVALID"; case STATUS_LOG_SPACE_RESERVED_INVALID: return "STATUS_LOG_SPACE_RESERVED_INVALID"; case STATUS_LOG_TAIL_INVALID: return "STATUS_LOG_TAIL_INVALID"; case STATUS_LOG_FULL: return "STATUS_LOG_FULL"; case STATUS_LOG_MULTIPLEXED: return "STATUS_LOG_MULTIPLEXED"; case STATUS_LOG_DEDICATED: return "STATUS_LOG_DEDICATED"; case STATUS_LOG_ARCHIVE_NOT_IN_PROGRESS: return "STATUS_LOG_ARCHIVE_NOT_IN_PROGRESS"; case STATUS_LOG_ARCHIVE_IN_PROGRESS: return "STATUS_LOG_ARCHIVE_IN_PROGRESS"; case STATUS_LOG_EPHEMERAL: return "STATUS_LOG_EPHEMERAL"; case STATUS_LOG_NOT_ENOUGH_CONTAINERS: return "STATUS_LOG_NOT_ENOUGH_CONTAINERS"; case STATUS_LOG_CLIENT_ALREADY_REGISTERED: return "STATUS_LOG_CLIENT_ALREADY_REGISTERED"; case STATUS_LOG_CLIENT_NOT_REGISTERED: return "STATUS_LOG_CLIENT_NOT_REGISTERED"; case STATUS_LOG_FULL_HANDLER_IN_PROGRESS: return "STATUS_LOG_FULL_HANDLER_IN_PROGRESS"; case STATUS_LOG_CONTAINER_READ_FAILED: return "STATUS_LOG_CONTAINER_READ_FAILED"; case STATUS_LOG_CONTAINER_WRITE_FAILED: return "STATUS_LOG_CONTAINER_WRITE_FAILED"; case STATUS_LOG_CONTAINER_OPEN_FAILED: return "STATUS_LOG_CONTAINER_OPEN_FAILED"; case STATUS_LOG_CONTAINER_STATE_INVALID: return "STATUS_LOG_CONTAINER_STATE_INVALID"; case STATUS_LOG_STATE_INVALID: return "STATUS_LOG_STATE_INVALID"; case STATUS_LOG_PINNED: return "STATUS_LOG_PINNED"; case STATUS_LOG_METADATA_FLUSH_FAILED: return "STATUS_LOG_METADATA_FLUSH_FAILED"; case STATUS_LOG_INCONSISTENT_SECURITY: return "STATUS_LOG_INCONSISTENT_SECURITY"; case STATUS_LOG_APPENDED_FLUSH_FAILED: return "STATUS_LOG_APPENDED_FLUSH_FAILED"; case STATUS_LOG_PINNED_RESERVATION: return "STATUS_LOG_PINNED_RESERVATION"; case STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD: return "STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD"; case STATUS_FLT_NO_HANDLER_DEFINED: return "STATUS_FLT_NO_HANDLER_DEFINED"; case STATUS_FLT_CONTEXT_ALREADY_DEFINED: return "STATUS_FLT_CONTEXT_ALREADY_DEFINED"; case STATUS_FLT_INVALID_ASYNCHRONOUS_REQUEST: return "STATUS_FLT_INVALID_ASYNCHRONOUS_REQUEST"; case STATUS_FLT_DISALLOW_FAST_IO: return "STATUS_FLT_DISALLOW_FAST_IO"; case STATUS_FLT_INVALID_NAME_REQUEST: return "STATUS_FLT_INVALID_NAME_REQUEST"; case STATUS_FLT_NOT_SAFE_TO_POST_OPERATION: return "STATUS_FLT_NOT_SAFE_TO_POST_OPERATION"; case STATUS_FLT_NOT_INITIALIZED: return "STATUS_FLT_NOT_INITIALIZED"; case STATUS_FLT_FILTER_NOT_READY: return "STATUS_FLT_FILTER_NOT_READY"; case STATUS_FLT_POST_OPERATION_CLEANUP: return "STATUS_FLT_POST_OPERATION_CLEANUP"; case STATUS_FLT_INTERNAL_ERROR: return "STATUS_FLT_INTERNAL_ERROR"; case STATUS_FLT_DELETING_OBJECT: return "STATUS_FLT_DELETING_OBJECT"; case STATUS_FLT_MUST_BE_NONPAGED_POOL: return "STATUS_FLT_MUST_BE_NONPAGED_POOL"; case STATUS_FLT_DUPLICATE_ENTRY: return "STATUS_FLT_DUPLICATE_ENTRY"; case STATUS_FLT_CBDQ_DISABLED: return "STATUS_FLT_CBDQ_DISABLED"; case STATUS_FLT_DO_NOT_ATTACH: return "STATUS_FLT_DO_NOT_ATTACH"; case STATUS_FLT_DO_NOT_DETACH: return "STATUS_FLT_DO_NOT_DETACH"; case STATUS_FLT_INSTANCE_ALTITUDE_COLLISION: return "STATUS_FLT_INSTANCE_ALTITUDE_COLLISION"; case STATUS_FLT_INSTANCE_NAME_COLLISION: return "STATUS_FLT_INSTANCE_NAME_COLLISION"; case STATUS_FLT_FILTER_NOT_FOUND: return "STATUS_FLT_FILTER_NOT_FOUND"; case STATUS_FLT_VOLUME_NOT_FOUND: return "STATUS_FLT_VOLUME_NOT_FOUND"; case STATUS_FLT_INSTANCE_NOT_FOUND: return "STATUS_FLT_INSTANCE_NOT_FOUND"; case STATUS_FLT_CONTEXT_ALLOCATION_NOT_FOUND: return "STATUS_FLT_CONTEXT_ALLOCATION_NOT_FOUND"; case STATUS_FLT_INVALID_CONTEXT_REGISTRATION: return "STATUS_FLT_INVALID_CONTEXT_REGISTRATION"; case STATUS_FLT_NAME_CACHE_MISS: return "STATUS_FLT_NAME_CACHE_MISS"; case STATUS_FLT_NO_DEVICE_OBJECT: return "STATUS_FLT_NO_DEVICE_OBJECT"; case STATUS_FLT_VOLUME_ALREADY_MOUNTED: return "STATUS_FLT_VOLUME_ALREADY_MOUNTED"; case STATUS_FLT_ALREADY_ENLISTED: return "STATUS_FLT_ALREADY_ENLISTED"; case STATUS_FLT_CONTEXT_ALREADY_LINKED: return "STATUS_FLT_CONTEXT_ALREADY_LINKED"; case STATUS_FLT_NO_WAITER_FOR_REPLY: return "STATUS_FLT_NO_WAITER_FOR_REPLY"; case STATUS_MONITOR_NO_DESCRIPTOR: return "STATUS_MONITOR_NO_DESCRIPTOR"; case STATUS_MONITOR_UNKNOWN_DESCRIPTOR_FORMAT: return "STATUS_MONITOR_UNKNOWN_DESCRIPTOR_FORMAT"; case STATUS_MONITOR_INVALID_DESCRIPTOR_CHECKSUM: return "STATUS_MONITOR_INVALID_DESCRIPTOR_CHECKSUM"; case STATUS_MONITOR_INVALID_STANDARD_TIMING_BLOCK: return "STATUS_MONITOR_INVALID_STANDARD_TIMING_BLOCK"; case STATUS_MONITOR_WMI_DATABLOCK_REGISTRATION_FAILED: return "STATUS_MONITOR_WMI_DATABLOCK_REGISTRATION_FAILED"; case STATUS_MONITOR_INVALID_SERIAL_NUMBER_MONDSC_BLOCK: return "STATUS_MONITOR_INVALID_SERIAL_NUMBER_MONDSC_BLOCK"; case STATUS_MONITOR_INVALID_USER_FRIENDLY_MONDSC_BLOCK: return "STATUS_MONITOR_INVALID_USER_FRIENDLY_MONDSC_BLOCK"; case STATUS_MONITOR_NO_MORE_DESCRIPTOR_DATA: return "STATUS_MONITOR_NO_MORE_DESCRIPTOR_DATA"; case STATUS_MONITOR_INVALID_DETAILED_TIMING_BLOCK: return "STATUS_MONITOR_INVALID_DETAILED_TIMING_BLOCK"; case STATUS_MONITOR_INVALID_MANUFACTURE_DATE: return "STATUS_MONITOR_INVALID_MANUFACTURE_DATE"; case STATUS_GRAPHICS_NOT_EXCLUSIVE_MODE_OWNER: return "STATUS_GRAPHICS_NOT_EXCLUSIVE_MODE_OWNER"; case STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER: return "STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER"; case STATUS_GRAPHICS_INVALID_DISPLAY_ADAPTER: return "STATUS_GRAPHICS_INVALID_DISPLAY_ADAPTER"; case STATUS_GRAPHICS_ADAPTER_WAS_RESET: return "STATUS_GRAPHICS_ADAPTER_WAS_RESET"; case STATUS_GRAPHICS_INVALID_DRIVER_MODEL: return "STATUS_GRAPHICS_INVALID_DRIVER_MODEL"; case STATUS_GRAPHICS_PRESENT_MODE_CHANGED: return "STATUS_GRAPHICS_PRESENT_MODE_CHANGED"; case STATUS_GRAPHICS_PRESENT_OCCLUDED: return "STATUS_GRAPHICS_PRESENT_OCCLUDED"; case STATUS_GRAPHICS_PRESENT_DENIED: return "STATUS_GRAPHICS_PRESENT_DENIED"; case STATUS_GRAPHICS_CANNOTCOLORCONVERT: return "STATUS_GRAPHICS_CANNOTCOLORCONVERT"; case STATUS_GRAPHICS_PRESENT_REDIRECTION_DISABLED: return "STATUS_GRAPHICS_PRESENT_REDIRECTION_DISABLED"; case STATUS_GRAPHICS_PRESENT_UNOCCLUDED: return "STATUS_GRAPHICS_PRESENT_UNOCCLUDED"; case STATUS_GRAPHICS_NO_VIDEO_MEMORY: return "STATUS_GRAPHICS_NO_VIDEO_MEMORY"; case STATUS_GRAPHICS_CANT_LOCK_MEMORY: return "STATUS_GRAPHICS_CANT_LOCK_MEMORY"; case STATUS_GRAPHICS_ALLOCATION_BUSY: return "STATUS_GRAPHICS_ALLOCATION_BUSY"; case STATUS_GRAPHICS_TOO_MANY_REFERENCES: return "STATUS_GRAPHICS_TOO_MANY_REFERENCES"; case STATUS_GRAPHICS_TRY_AGAIN_LATER: return "STATUS_GRAPHICS_TRY_AGAIN_LATER"; case STATUS_GRAPHICS_TRY_AGAIN_NOW: return "STATUS_GRAPHICS_TRY_AGAIN_NOW"; case STATUS_GRAPHICS_ALLOCATION_INVALID: return "STATUS_GRAPHICS_ALLOCATION_INVALID"; case STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNAVAILABLE: return "STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNAVAILABLE"; case STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNSUPPORTED: return "STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNSUPPORTED"; case STATUS_GRAPHICS_CANT_EVICT_PINNED_ALLOCATION: return "STATUS_GRAPHICS_CANT_EVICT_PINNED_ALLOCATION"; case STATUS_GRAPHICS_INVALID_ALLOCATION_USAGE: return "STATUS_GRAPHICS_INVALID_ALLOCATION_USAGE"; case STATUS_GRAPHICS_CANT_RENDER_LOCKED_ALLOCATION: return "STATUS_GRAPHICS_CANT_RENDER_LOCKED_ALLOCATION"; case STATUS_GRAPHICS_ALLOCATION_CLOSED: return "STATUS_GRAPHICS_ALLOCATION_CLOSED"; case STATUS_GRAPHICS_INVALID_ALLOCATION_INSTANCE: return "STATUS_GRAPHICS_INVALID_ALLOCATION_INSTANCE"; case STATUS_GRAPHICS_INVALID_ALLOCATION_HANDLE: return "STATUS_GRAPHICS_INVALID_ALLOCATION_HANDLE"; case STATUS_GRAPHICS_WRONG_ALLOCATION_DEVICE: return "STATUS_GRAPHICS_WRONG_ALLOCATION_DEVICE"; case STATUS_GRAPHICS_ALLOCATION_CONTENT_LOST: return "STATUS_GRAPHICS_ALLOCATION_CONTENT_LOST"; case STATUS_GRAPHICS_GPU_EXCEPTION_ON_DEVICE: return "STATUS_GRAPHICS_GPU_EXCEPTION_ON_DEVICE"; case STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY: return "STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY"; case STATUS_GRAPHICS_VIDPN_TOPOLOGY_NOT_SUPPORTED: return "STATUS_GRAPHICS_VIDPN_TOPOLOGY_NOT_SUPPORTED"; case STATUS_GRAPHICS_VIDPN_TOPOLOGY_CURRENTLY_NOT_SUPPORTED: return "STATUS_GRAPHICS_VIDPN_TOPOLOGY_CURRENTLY_NOT_SUPPORTED"; case STATUS_GRAPHICS_INVALID_VIDPN: return "STATUS_GRAPHICS_INVALID_VIDPN"; case STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE: return "STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE"; case STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET: return "STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET"; case STATUS_GRAPHICS_VIDPN_MODALITY_NOT_SUPPORTED: return "STATUS_GRAPHICS_VIDPN_MODALITY_NOT_SUPPORTED"; case STATUS_GRAPHICS_INVALID_VIDPN_SOURCEMODESET: return "STATUS_GRAPHICS_INVALID_VIDPN_SOURCEMODESET"; case STATUS_GRAPHICS_INVALID_VIDPN_TARGETMODESET: return "STATUS_GRAPHICS_INVALID_VIDPN_TARGETMODESET"; case STATUS_GRAPHICS_INVALID_FREQUENCY: return "STATUS_GRAPHICS_INVALID_FREQUENCY"; case STATUS_GRAPHICS_INVALID_ACTIVE_REGION: return "STATUS_GRAPHICS_INVALID_ACTIVE_REGION"; case STATUS_GRAPHICS_INVALID_TOTAL_REGION: return "STATUS_GRAPHICS_INVALID_TOTAL_REGION"; case STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE_MODE: return "STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE_MODE"; case STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET_MODE: return "STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET_MODE"; case STATUS_GRAPHICS_PINNED_MODE_MUST_REMAIN_IN_SET: return "STATUS_GRAPHICS_PINNED_MODE_MUST_REMAIN_IN_SET"; case STATUS_GRAPHICS_PATH_ALREADY_IN_TOPOLOGY: return "STATUS_GRAPHICS_PATH_ALREADY_IN_TOPOLOGY"; case STATUS_GRAPHICS_MODE_ALREADY_IN_MODESET: return "STATUS_GRAPHICS_MODE_ALREADY_IN_MODESET"; case STATUS_GRAPHICS_INVALID_VIDEOPRESENTSOURCESET: return "STATUS_GRAPHICS_INVALID_VIDEOPRESENTSOURCESET"; case STATUS_GRAPHICS_INVALID_VIDEOPRESENTTARGETSET: return "STATUS_GRAPHICS_INVALID_VIDEOPRESENTTARGETSET"; case STATUS_GRAPHICS_SOURCE_ALREADY_IN_SET: return "STATUS_GRAPHICS_SOURCE_ALREADY_IN_SET"; case STATUS_GRAPHICS_TARGET_ALREADY_IN_SET: return "STATUS_GRAPHICS_TARGET_ALREADY_IN_SET"; case STATUS_GRAPHICS_INVALID_VIDPN_PRESENT_PATH: return "STATUS_GRAPHICS_INVALID_VIDPN_PRESENT_PATH"; case STATUS_GRAPHICS_NO_RECOMMENDED_VIDPN_TOPOLOGY: return "STATUS_GRAPHICS_NO_RECOMMENDED_VIDPN_TOPOLOGY"; case STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGESET: return "STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGESET"; case STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE: return "STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE"; case STATUS_GRAPHICS_FREQUENCYRANGE_NOT_IN_SET: return "STATUS_GRAPHICS_FREQUENCYRANGE_NOT_IN_SET"; case STATUS_GRAPHICS_FREQUENCYRANGE_ALREADY_IN_SET: return "STATUS_GRAPHICS_FREQUENCYRANGE_ALREADY_IN_SET"; case STATUS_GRAPHICS_STALE_MODESET: return "STATUS_GRAPHICS_STALE_MODESET"; case STATUS_GRAPHICS_INVALID_MONITOR_SOURCEMODESET: return "STATUS_GRAPHICS_INVALID_MONITOR_SOURCEMODESET"; case STATUS_GRAPHICS_INVALID_MONITOR_SOURCE_MODE: return "STATUS_GRAPHICS_INVALID_MONITOR_SOURCE_MODE"; case STATUS_GRAPHICS_NO_RECOMMENDED_FUNCTIONAL_VIDPN: return "STATUS_GRAPHICS_NO_RECOMMENDED_FUNCTIONAL_VIDPN"; case STATUS_GRAPHICS_MODE_ID_MUST_BE_UNIQUE: return "STATUS_GRAPHICS_MODE_ID_MUST_BE_UNIQUE"; case STATUS_GRAPHICS_EMPTY_ADAPTER_MONITOR_MODE_SUPPORT_INTERSECTION: return "STATUS_GRAPHICS_EMPTY_ADAPTER_MONITOR_MODE_SUPPORT_INTERSECTION"; case STATUS_GRAPHICS_VIDEO_PRESENT_TARGETS_LESS_THAN_SOURCES: return "STATUS_GRAPHICS_VIDEO_PRESENT_TARGETS_LESS_THAN_SOURCES"; case STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY: return "STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY"; case STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_SOURCE: return "STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_SOURCE"; case STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_TARGET: return "STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_TARGET"; case STATUS_GRAPHICS_INVALID_MONITORDESCRIPTORSET: return "STATUS_GRAPHICS_INVALID_MONITORDESCRIPTORSET"; case STATUS_GRAPHICS_INVALID_MONITORDESCRIPTOR: return "STATUS_GRAPHICS_INVALID_MONITORDESCRIPTOR"; case STATUS_GRAPHICS_MONITORDESCRIPTOR_NOT_IN_SET: return "STATUS_GRAPHICS_MONITORDESCRIPTOR_NOT_IN_SET"; case STATUS_GRAPHICS_MONITORDESCRIPTOR_ALREADY_IN_SET: return "STATUS_GRAPHICS_MONITORDESCRIPTOR_ALREADY_IN_SET"; case STATUS_GRAPHICS_MONITORDESCRIPTOR_ID_MUST_BE_UNIQUE: return "STATUS_GRAPHICS_MONITORDESCRIPTOR_ID_MUST_BE_UNIQUE"; case STATUS_GRAPHICS_INVALID_VIDPN_TARGET_SUBSET_TYPE: return "STATUS_GRAPHICS_INVALID_VIDPN_TARGET_SUBSET_TYPE"; case STATUS_GRAPHICS_RESOURCES_NOT_RELATED: return "STATUS_GRAPHICS_RESOURCES_NOT_RELATED"; case STATUS_GRAPHICS_SOURCE_ID_MUST_BE_UNIQUE: return "STATUS_GRAPHICS_SOURCE_ID_MUST_BE_UNIQUE"; case STATUS_GRAPHICS_TARGET_ID_MUST_BE_UNIQUE: return "STATUS_GRAPHICS_TARGET_ID_MUST_BE_UNIQUE"; case STATUS_GRAPHICS_NO_AVAILABLE_VIDPN_TARGET: return "STATUS_GRAPHICS_NO_AVAILABLE_VIDPN_TARGET"; case STATUS_GRAPHICS_MONITOR_COULD_NOT_BE_ASSOCIATED_WITH_ADAPTER: return "STATUS_GRAPHICS_MONITOR_COULD_NOT_BE_ASSOCIATED_WITH_ADAPTER"; case STATUS_GRAPHICS_NO_VIDPNMGR: return "STATUS_GRAPHICS_NO_VIDPNMGR"; case STATUS_GRAPHICS_NO_ACTIVE_VIDPN: return "STATUS_GRAPHICS_NO_ACTIVE_VIDPN"; case STATUS_GRAPHICS_STALE_VIDPN_TOPOLOGY: return "STATUS_GRAPHICS_STALE_VIDPN_TOPOLOGY"; case STATUS_GRAPHICS_MONITOR_NOT_CONNECTED: return "STATUS_GRAPHICS_MONITOR_NOT_CONNECTED"; case STATUS_GRAPHICS_SOURCE_NOT_IN_TOPOLOGY: return "STATUS_GRAPHICS_SOURCE_NOT_IN_TOPOLOGY"; case STATUS_GRAPHICS_INVALID_PRIMARYSURFACE_SIZE: return "STATUS_GRAPHICS_INVALID_PRIMARYSURFACE_SIZE"; case STATUS_GRAPHICS_INVALID_VISIBLEREGION_SIZE: return "STATUS_GRAPHICS_INVALID_VISIBLEREGION_SIZE"; case STATUS_GRAPHICS_INVALID_STRIDE: return "STATUS_GRAPHICS_INVALID_STRIDE"; case STATUS_GRAPHICS_INVALID_PIXELFORMAT: return "STATUS_GRAPHICS_INVALID_PIXELFORMAT"; case STATUS_GRAPHICS_INVALID_COLORBASIS: return "STATUS_GRAPHICS_INVALID_COLORBASIS"; case STATUS_GRAPHICS_INVALID_PIXELVALUEACCESSMODE: return "STATUS_GRAPHICS_INVALID_PIXELVALUEACCESSMODE"; case STATUS_GRAPHICS_TARGET_NOT_IN_TOPOLOGY: return "STATUS_GRAPHICS_TARGET_NOT_IN_TOPOLOGY"; case STATUS_GRAPHICS_NO_DISPLAY_MODE_MANAGEMENT_SUPPORT: return "STATUS_GRAPHICS_NO_DISPLAY_MODE_MANAGEMENT_SUPPORT"; case STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE: return "STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE"; case STATUS_GRAPHICS_CANT_ACCESS_ACTIVE_VIDPN: return "STATUS_GRAPHICS_CANT_ACCESS_ACTIVE_VIDPN"; case STATUS_GRAPHICS_INVALID_PATH_IMPORTANCE_ORDINAL: return "STATUS_GRAPHICS_INVALID_PATH_IMPORTANCE_ORDINAL"; case STATUS_GRAPHICS_INVALID_PATH_CONTENT_GEOMETRY_TRANSFORMATION: return "STATUS_GRAPHICS_INVALID_PATH_CONTENT_GEOMETRY_TRANSFORMATION"; case STATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_SUPPORTED: return "STATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_SUPPORTED"; case STATUS_GRAPHICS_INVALID_GAMMA_RAMP: return "STATUS_GRAPHICS_INVALID_GAMMA_RAMP"; case STATUS_GRAPHICS_GAMMA_RAMP_NOT_SUPPORTED: return "STATUS_GRAPHICS_GAMMA_RAMP_NOT_SUPPORTED"; case STATUS_GRAPHICS_MULTISAMPLING_NOT_SUPPORTED: return "STATUS_GRAPHICS_MULTISAMPLING_NOT_SUPPORTED"; case STATUS_GRAPHICS_MODE_NOT_IN_MODESET: return "STATUS_GRAPHICS_MODE_NOT_IN_MODESET"; case STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY_RECOMMENDATION_REASON: return "STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY_RECOMMENDATION_REASON"; case STATUS_GRAPHICS_INVALID_PATH_CONTENT_TYPE: return "STATUS_GRAPHICS_INVALID_PATH_CONTENT_TYPE"; case STATUS_GRAPHICS_INVALID_COPYPROTECTION_TYPE: return "STATUS_GRAPHICS_INVALID_COPYPROTECTION_TYPE"; case STATUS_GRAPHICS_UNASSIGNED_MODESET_ALREADY_EXISTS: return "STATUS_GRAPHICS_UNASSIGNED_MODESET_ALREADY_EXISTS"; case STATUS_GRAPHICS_INVALID_SCANLINE_ORDERING: return "STATUS_GRAPHICS_INVALID_SCANLINE_ORDERING"; case STATUS_GRAPHICS_TOPOLOGY_CHANGES_NOT_ALLOWED: return "STATUS_GRAPHICS_TOPOLOGY_CHANGES_NOT_ALLOWED"; case STATUS_GRAPHICS_NO_AVAILABLE_IMPORTANCE_ORDINALS: return "STATUS_GRAPHICS_NO_AVAILABLE_IMPORTANCE_ORDINALS"; case STATUS_GRAPHICS_INCOMPATIBLE_PRIVATE_FORMAT: return "STATUS_GRAPHICS_INCOMPATIBLE_PRIVATE_FORMAT"; case STATUS_GRAPHICS_INVALID_MODE_PRUNING_ALGORITHM: return "STATUS_GRAPHICS_INVALID_MODE_PRUNING_ALGORITHM"; case STATUS_GRAPHICS_INVALID_MONITOR_CAPABILITY_ORIGIN: return "STATUS_GRAPHICS_INVALID_MONITOR_CAPABILITY_ORIGIN"; case STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE_CONSTRAINT: return "STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE_CONSTRAINT"; case STATUS_GRAPHICS_MAX_NUM_PATHS_REACHED: return "STATUS_GRAPHICS_MAX_NUM_PATHS_REACHED"; case STATUS_GRAPHICS_CANCEL_VIDPN_TOPOLOGY_AUGMENTATION: return "STATUS_GRAPHICS_CANCEL_VIDPN_TOPOLOGY_AUGMENTATION"; case STATUS_GRAPHICS_INVALID_CLIENT_TYPE: return "STATUS_GRAPHICS_INVALID_CLIENT_TYPE"; case STATUS_GRAPHICS_CLIENTVIDPN_NOT_SET: return "STATUS_GRAPHICS_CLIENTVIDPN_NOT_SET"; case STATUS_GRAPHICS_SPECIFIED_CHILD_ALREADY_CONNECTED: return "STATUS_GRAPHICS_SPECIFIED_CHILD_ALREADY_CONNECTED"; case STATUS_GRAPHICS_CHILD_DESCRIPTOR_NOT_SUPPORTED: return "STATUS_GRAPHICS_CHILD_DESCRIPTOR_NOT_SUPPORTED"; case STATUS_GRAPHICS_NOT_A_LINKED_ADAPTER: return "STATUS_GRAPHICS_NOT_A_LINKED_ADAPTER"; case STATUS_GRAPHICS_LEADLINK_NOT_ENUMERATED: return "STATUS_GRAPHICS_LEADLINK_NOT_ENUMERATED"; case STATUS_GRAPHICS_CHAINLINKS_NOT_ENUMERATED: return "STATUS_GRAPHICS_CHAINLINKS_NOT_ENUMERATED"; case STATUS_GRAPHICS_ADAPTER_CHAIN_NOT_READY: return "STATUS_GRAPHICS_ADAPTER_CHAIN_NOT_READY"; case STATUS_GRAPHICS_CHAINLINKS_NOT_STARTED: return "STATUS_GRAPHICS_CHAINLINKS_NOT_STARTED"; case STATUS_GRAPHICS_CHAINLINKS_NOT_POWERED_ON: return "STATUS_GRAPHICS_CHAINLINKS_NOT_POWERED_ON"; case STATUS_GRAPHICS_INCONSISTENT_DEVICE_LINK_STATE: return "STATUS_GRAPHICS_INCONSISTENT_DEVICE_LINK_STATE"; case STATUS_GRAPHICS_NOT_POST_DEVICE_DRIVER: return "STATUS_GRAPHICS_NOT_POST_DEVICE_DRIVER"; case STATUS_GRAPHICS_ADAPTER_ACCESS_NOT_EXCLUDED: return "STATUS_GRAPHICS_ADAPTER_ACCESS_NOT_EXCLUDED"; case STATUS_GRAPHICS_OPM_NOT_SUPPORTED: return "STATUS_GRAPHICS_OPM_NOT_SUPPORTED"; case STATUS_GRAPHICS_COPP_NOT_SUPPORTED: return "STATUS_GRAPHICS_COPP_NOT_SUPPORTED"; case STATUS_GRAPHICS_UAB_NOT_SUPPORTED: return "STATUS_GRAPHICS_UAB_NOT_SUPPORTED"; case STATUS_GRAPHICS_OPM_INVALID_ENCRYPTED_PARAMETERS: return "STATUS_GRAPHICS_OPM_INVALID_ENCRYPTED_PARAMETERS"; case STATUS_GRAPHICS_OPM_NO_PROTECTED_OUTPUTS_EXIST: return "STATUS_GRAPHICS_OPM_NO_PROTECTED_OUTPUTS_EXIST"; case STATUS_GRAPHICS_OPM_INTERNAL_ERROR: return "STATUS_GRAPHICS_OPM_INTERNAL_ERROR"; case STATUS_GRAPHICS_OPM_INVALID_HANDLE: return "STATUS_GRAPHICS_OPM_INVALID_HANDLE"; case STATUS_GRAPHICS_PVP_INVALID_CERTIFICATE_LENGTH: return "STATUS_GRAPHICS_PVP_INVALID_CERTIFICATE_LENGTH"; case STATUS_GRAPHICS_OPM_SPANNING_MODE_ENABLED: return "STATUS_GRAPHICS_OPM_SPANNING_MODE_ENABLED"; case STATUS_GRAPHICS_OPM_THEATER_MODE_ENABLED: return "STATUS_GRAPHICS_OPM_THEATER_MODE_ENABLED"; case STATUS_GRAPHICS_PVP_HFS_FAILED: return "STATUS_GRAPHICS_PVP_HFS_FAILED"; case STATUS_GRAPHICS_OPM_INVALID_SRM: return "STATUS_GRAPHICS_OPM_INVALID_SRM"; case STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_HDCP: return "STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_HDCP"; case STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_ACP: return "STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_ACP"; case STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_CGMSA: return "STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_CGMSA"; case STATUS_GRAPHICS_OPM_HDCP_SRM_NEVER_SET: return "STATUS_GRAPHICS_OPM_HDCP_SRM_NEVER_SET"; case STATUS_GRAPHICS_OPM_RESOLUTION_TOO_HIGH: return "STATUS_GRAPHICS_OPM_RESOLUTION_TOO_HIGH"; case STATUS_GRAPHICS_OPM_ALL_HDCP_HARDWARE_ALREADY_IN_USE: return "STATUS_GRAPHICS_OPM_ALL_HDCP_HARDWARE_ALREADY_IN_USE"; case STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_NO_LONGER_EXISTS: return "STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_NO_LONGER_EXISTS"; case STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_COPP_SEMANTICS: return "STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_COPP_SEMANTICS"; case STATUS_GRAPHICS_OPM_INVALID_INFORMATION_REQUEST: return "STATUS_GRAPHICS_OPM_INVALID_INFORMATION_REQUEST"; case STATUS_GRAPHICS_OPM_DRIVER_INTERNAL_ERROR: return "STATUS_GRAPHICS_OPM_DRIVER_INTERNAL_ERROR"; case STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_OPM_SEMANTICS: return "STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_OPM_SEMANTICS"; case STATUS_GRAPHICS_OPM_SIGNALING_NOT_SUPPORTED: return "STATUS_GRAPHICS_OPM_SIGNALING_NOT_SUPPORTED"; case STATUS_GRAPHICS_OPM_INVALID_CONFIGURATION_REQUEST: return "STATUS_GRAPHICS_OPM_INVALID_CONFIGURATION_REQUEST"; case STATUS_GRAPHICS_I2C_NOT_SUPPORTED: return "STATUS_GRAPHICS_I2C_NOT_SUPPORTED"; case STATUS_GRAPHICS_I2C_DEVICE_DOES_NOT_EXIST: return "STATUS_GRAPHICS_I2C_DEVICE_DOES_NOT_EXIST"; case STATUS_GRAPHICS_I2C_ERROR_TRANSMITTING_DATA: return "STATUS_GRAPHICS_I2C_ERROR_TRANSMITTING_DATA"; case STATUS_GRAPHICS_I2C_ERROR_RECEIVING_DATA: return "STATUS_GRAPHICS_I2C_ERROR_RECEIVING_DATA"; case STATUS_GRAPHICS_DDCCI_VCP_NOT_SUPPORTED: return "STATUS_GRAPHICS_DDCCI_VCP_NOT_SUPPORTED"; case STATUS_GRAPHICS_DDCCI_INVALID_DATA: return "STATUS_GRAPHICS_DDCCI_INVALID_DATA"; case STATUS_GRAPHICS_DDCCI_MONITOR_RETURNED_INVALID_TIMING_STATUS_BYTE: return "STATUS_GRAPHICS_DDCCI_MONITOR_RETURNED_INVALID_TIMING_STATUS_BYTE"; case STATUS_GRAPHICS_DDCCI_INVALID_CAPABILITIES_STRING: return "STATUS_GRAPHICS_DDCCI_INVALID_CAPABILITIES_STRING"; case STATUS_GRAPHICS_MCA_INTERNAL_ERROR: return "STATUS_GRAPHICS_MCA_INTERNAL_ERROR"; case STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_COMMAND: return "STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_COMMAND"; case STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_LENGTH: return "STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_LENGTH"; case STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_CHECKSUM: return "STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_CHECKSUM"; case STATUS_GRAPHICS_INVALID_PHYSICAL_MONITOR_HANDLE: return "STATUS_GRAPHICS_INVALID_PHYSICAL_MONITOR_HANDLE"; case STATUS_GRAPHICS_MONITOR_NO_LONGER_EXISTS: return "STATUS_GRAPHICS_MONITOR_NO_LONGER_EXISTS"; case STATUS_GRAPHICS_ONLY_CONSOLE_SESSION_SUPPORTED: return "STATUS_GRAPHICS_ONLY_CONSOLE_SESSION_SUPPORTED"; case STATUS_GRAPHICS_NO_DISPLAY_DEVICE_CORRESPONDS_TO_NAME: return "STATUS_GRAPHICS_NO_DISPLAY_DEVICE_CORRESPONDS_TO_NAME"; case STATUS_GRAPHICS_DISPLAY_DEVICE_NOT_ATTACHED_TO_DESKTOP: return "STATUS_GRAPHICS_DISPLAY_DEVICE_NOT_ATTACHED_TO_DESKTOP"; case STATUS_GRAPHICS_MIRRORING_DEVICES_NOT_SUPPORTED: return "STATUS_GRAPHICS_MIRRORING_DEVICES_NOT_SUPPORTED"; case STATUS_GRAPHICS_INVALID_POINTER: return "STATUS_GRAPHICS_INVALID_POINTER"; case STATUS_GRAPHICS_NO_MONITORS_CORRESPOND_TO_DISPLAY_DEVICE: return "STATUS_GRAPHICS_NO_MONITORS_CORRESPOND_TO_DISPLAY_DEVICE"; case STATUS_GRAPHICS_PARAMETER_ARRAY_TOO_SMALL: return "STATUS_GRAPHICS_PARAMETER_ARRAY_TOO_SMALL"; case STATUS_GRAPHICS_INTERNAL_ERROR: return "STATUS_GRAPHICS_INTERNAL_ERROR"; case STATUS_GRAPHICS_SESSION_TYPE_CHANGE_IN_PROGRESS: return "STATUS_GRAPHICS_SESSION_TYPE_CHANGE_IN_PROGRESS"; case STATUS_FVE_LOCKED_VOLUME: return "STATUS_FVE_LOCKED_VOLUME"; case STATUS_FVE_NOT_ENCRYPTED: return "STATUS_FVE_NOT_ENCRYPTED"; case STATUS_FVE_BAD_INFORMATION: return "STATUS_FVE_BAD_INFORMATION"; case STATUS_FVE_TOO_SMALL: return "STATUS_FVE_TOO_SMALL"; case STATUS_FVE_FAILED_WRONG_FS: return "STATUS_FVE_FAILED_WRONG_FS"; case STATUS_FVE_FS_NOT_EXTENDED: return "STATUS_FVE_FS_NOT_EXTENDED"; case STATUS_FVE_FS_MOUNTED: return "STATUS_FVE_FS_MOUNTED"; case STATUS_FVE_NO_LICENSE: return "STATUS_FVE_NO_LICENSE"; case STATUS_FVE_ACTION_NOT_ALLOWED: return "STATUS_FVE_ACTION_NOT_ALLOWED"; case STATUS_FVE_BAD_DATA: return "STATUS_FVE_BAD_DATA"; case STATUS_FVE_VOLUME_NOT_BOUND: return "STATUS_FVE_VOLUME_NOT_BOUND"; case STATUS_FVE_NOT_DATA_VOLUME: return "STATUS_FVE_NOT_DATA_VOLUME"; case STATUS_FVE_CONV_READ_ERROR: return "STATUS_FVE_CONV_READ_ERROR"; case STATUS_FVE_CONV_WRITE_ERROR: return "STATUS_FVE_CONV_WRITE_ERROR"; case STATUS_FVE_OVERLAPPED_UPDATE: return "STATUS_FVE_OVERLAPPED_UPDATE"; case STATUS_FVE_FAILED_SECTOR_SIZE: return "STATUS_FVE_FAILED_SECTOR_SIZE"; case STATUS_FVE_FAILED_AUTHENTICATION: return "STATUS_FVE_FAILED_AUTHENTICATION"; case STATUS_FVE_NOT_OS_VOLUME: return "STATUS_FVE_NOT_OS_VOLUME"; case STATUS_FVE_KEYFILE_NOT_FOUND: return "STATUS_FVE_KEYFILE_NOT_FOUND"; case STATUS_FVE_KEYFILE_INVALID: return "STATUS_FVE_KEYFILE_INVALID"; case STATUS_FVE_KEYFILE_NO_VMK: return "STATUS_FVE_KEYFILE_NO_VMK"; case STATUS_FVE_TPM_DISABLED: return "STATUS_FVE_TPM_DISABLED"; case STATUS_FVE_TPM_SRK_AUTH_NOT_ZERO: return "STATUS_FVE_TPM_SRK_AUTH_NOT_ZERO"; case STATUS_FVE_TPM_INVALID_PCR: return "STATUS_FVE_TPM_INVALID_PCR"; case STATUS_FVE_TPM_NO_VMK: return "STATUS_FVE_TPM_NO_VMK"; case STATUS_FVE_PIN_INVALID: return "STATUS_FVE_PIN_INVALID"; case STATUS_FVE_AUTH_INVALID_APPLICATION: return "STATUS_FVE_AUTH_INVALID_APPLICATION"; case STATUS_FVE_AUTH_INVALID_CONFIG: return "STATUS_FVE_AUTH_INVALID_CONFIG"; case STATUS_FVE_DEBUGGER_ENABLED: return "STATUS_FVE_DEBUGGER_ENABLED"; case STATUS_FVE_DRY_RUN_FAILED: return "STATUS_FVE_DRY_RUN_FAILED"; case STATUS_FVE_BAD_METADATA_POINTER: return "STATUS_FVE_BAD_METADATA_POINTER"; case STATUS_FVE_OLD_METADATA_COPY: return "STATUS_FVE_OLD_METADATA_COPY"; case STATUS_FVE_REBOOT_REQUIRED: return "STATUS_FVE_REBOOT_REQUIRED"; case STATUS_FVE_RAW_ACCESS: return "STATUS_FVE_RAW_ACCESS"; case STATUS_FVE_RAW_BLOCKED: return "STATUS_FVE_RAW_BLOCKED"; case STATUS_FVE_NO_FEATURE_LICENSE: return "STATUS_FVE_NO_FEATURE_LICENSE"; case STATUS_FVE_POLICY_USER_DISABLE_RDV_NOT_ALLOWED: return "STATUS_FVE_POLICY_USER_DISABLE_RDV_NOT_ALLOWED"; case STATUS_FVE_CONV_RECOVERY_FAILED: return "STATUS_FVE_CONV_RECOVERY_FAILED"; case STATUS_FVE_VIRTUALIZED_SPACE_TOO_BIG: return "STATUS_FVE_VIRTUALIZED_SPACE_TOO_BIG"; case STATUS_FVE_VOLUME_TOO_SMALL: return "STATUS_FVE_VOLUME_TOO_SMALL"; case STATUS_FWP_CALLOUT_NOT_FOUND: return "STATUS_FWP_CALLOUT_NOT_FOUND"; case STATUS_FWP_CONDITION_NOT_FOUND: return "STATUS_FWP_CONDITION_NOT_FOUND"; case STATUS_FWP_FILTER_NOT_FOUND: return "STATUS_FWP_FILTER_NOT_FOUND"; case STATUS_FWP_LAYER_NOT_FOUND: return "STATUS_FWP_LAYER_NOT_FOUND"; case STATUS_FWP_PROVIDER_NOT_FOUND: return "STATUS_FWP_PROVIDER_NOT_FOUND"; case STATUS_FWP_PROVIDER_CONTEXT_NOT_FOUND: return "STATUS_FWP_PROVIDER_CONTEXT_NOT_FOUND"; case STATUS_FWP_SUBLAYER_NOT_FOUND: return "STATUS_FWP_SUBLAYER_NOT_FOUND"; case STATUS_FWP_NOT_FOUND: return "STATUS_FWP_NOT_FOUND"; case STATUS_FWP_ALREADY_EXISTS: return "STATUS_FWP_ALREADY_EXISTS"; case STATUS_FWP_IN_USE: return "STATUS_FWP_IN_USE"; case STATUS_FWP_DYNAMIC_SESSION_IN_PROGRESS: return "STATUS_FWP_DYNAMIC_SESSION_IN_PROGRESS"; case STATUS_FWP_WRONG_SESSION: return "STATUS_FWP_WRONG_SESSION"; case STATUS_FWP_NO_TXN_IN_PROGRESS: return "STATUS_FWP_NO_TXN_IN_PROGRESS"; case STATUS_FWP_TXN_IN_PROGRESS: return "STATUS_FWP_TXN_IN_PROGRESS"; case STATUS_FWP_TXN_ABORTED: return "STATUS_FWP_TXN_ABORTED"; case STATUS_FWP_SESSION_ABORTED: return "STATUS_FWP_SESSION_ABORTED"; case STATUS_FWP_INCOMPATIBLE_TXN: return "STATUS_FWP_INCOMPATIBLE_TXN"; case STATUS_FWP_TIMEOUT: return "STATUS_FWP_TIMEOUT"; case STATUS_FWP_NET_EVENTS_DISABLED: return "STATUS_FWP_NET_EVENTS_DISABLED"; case STATUS_FWP_INCOMPATIBLE_LAYER: return "STATUS_FWP_INCOMPATIBLE_LAYER"; case STATUS_FWP_KM_CLIENTS_ONLY: return "STATUS_FWP_KM_CLIENTS_ONLY"; case STATUS_FWP_LIFETIME_MISMATCH: return "STATUS_FWP_LIFETIME_MISMATCH"; case STATUS_FWP_BUILTIN_OBJECT: return "STATUS_FWP_BUILTIN_OBJECT"; case STATUS_FWP_NOTIFICATION_DROPPED: return "STATUS_FWP_NOTIFICATION_DROPPED"; case STATUS_FWP_TRAFFIC_MISMATCH: return "STATUS_FWP_TRAFFIC_MISMATCH"; case STATUS_FWP_INCOMPATIBLE_SA_STATE: return "STATUS_FWP_INCOMPATIBLE_SA_STATE"; case STATUS_FWP_NULL_POINTER: return "STATUS_FWP_NULL_POINTER"; case STATUS_FWP_INVALID_ENUMERATOR: return "STATUS_FWP_INVALID_ENUMERATOR"; case STATUS_FWP_INVALID_FLAGS: return "STATUS_FWP_INVALID_FLAGS"; case STATUS_FWP_INVALID_NET_MASK: return "STATUS_FWP_INVALID_NET_MASK"; case STATUS_FWP_INVALID_RANGE: return "STATUS_FWP_INVALID_RANGE"; case STATUS_FWP_INVALID_INTERVAL: return "STATUS_FWP_INVALID_INTERVAL"; case STATUS_FWP_ZERO_LENGTH_ARRAY: return "STATUS_FWP_ZERO_LENGTH_ARRAY"; case STATUS_FWP_NULL_DISPLAY_NAME: return "STATUS_FWP_NULL_DISPLAY_NAME"; case STATUS_FWP_INVALID_ACTION_TYPE: return "STATUS_FWP_INVALID_ACTION_TYPE"; case STATUS_FWP_INVALID_WEIGHT: return "STATUS_FWP_INVALID_WEIGHT"; case STATUS_FWP_MATCH_TYPE_MISMATCH: return "STATUS_FWP_MATCH_TYPE_MISMATCH"; case STATUS_FWP_TYPE_MISMATCH: return "STATUS_FWP_TYPE_MISMATCH"; case STATUS_FWP_OUT_OF_BOUNDS: return "STATUS_FWP_OUT_OF_BOUNDS"; case STATUS_FWP_RESERVED: return "STATUS_FWP_RESERVED"; case STATUS_FWP_DUPLICATE_CONDITION: return "STATUS_FWP_DUPLICATE_CONDITION"; case STATUS_FWP_DUPLICATE_KEYMOD: return "STATUS_FWP_DUPLICATE_KEYMOD"; case STATUS_FWP_ACTION_INCOMPATIBLE_WITH_LAYER: return "STATUS_FWP_ACTION_INCOMPATIBLE_WITH_LAYER"; case STATUS_FWP_ACTION_INCOMPATIBLE_WITH_SUBLAYER: return "STATUS_FWP_ACTION_INCOMPATIBLE_WITH_SUBLAYER"; case STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_LAYER: return "STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_LAYER"; case STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_CALLOUT: return "STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_CALLOUT"; case STATUS_FWP_INCOMPATIBLE_AUTH_METHOD: return "STATUS_FWP_INCOMPATIBLE_AUTH_METHOD"; case STATUS_FWP_INCOMPATIBLE_DH_GROUP: return "STATUS_FWP_INCOMPATIBLE_DH_GROUP"; case STATUS_FWP_EM_NOT_SUPPORTED: return "STATUS_FWP_EM_NOT_SUPPORTED"; case STATUS_FWP_NEVER_MATCH: return "STATUS_FWP_NEVER_MATCH"; case STATUS_FWP_PROVIDER_CONTEXT_MISMATCH: return "STATUS_FWP_PROVIDER_CONTEXT_MISMATCH"; case STATUS_FWP_INVALID_PARAMETER: return "STATUS_FWP_INVALID_PARAMETER"; case STATUS_FWP_TOO_MANY_SUBLAYERS: return "STATUS_FWP_TOO_MANY_SUBLAYERS"; case STATUS_FWP_CALLOUT_NOTIFICATION_FAILED: return "STATUS_FWP_CALLOUT_NOTIFICATION_FAILED"; case STATUS_FWP_DUPLICATE_AUTH_METHOD: return "STATUS_FWP_DUPLICATE_AUTH_METHOD"; case STATUS_FWP_TCPIP_NOT_READY: return "STATUS_FWP_TCPIP_NOT_READY"; case STATUS_FWP_INJECT_HANDLE_CLOSING: return "STATUS_FWP_INJECT_HANDLE_CLOSING"; case STATUS_FWP_INJECT_HANDLE_STALE: return "STATUS_FWP_INJECT_HANDLE_STALE"; case STATUS_FWP_CANNOT_PEND: return "STATUS_FWP_CANNOT_PEND"; case STATUS_NDIS_CLOSING: return "STATUS_NDIS_CLOSING"; case STATUS_NDIS_BAD_VERSION: return "STATUS_NDIS_BAD_VERSION"; case STATUS_NDIS_BAD_CHARACTERISTICS: return "STATUS_NDIS_BAD_CHARACTERISTICS"; case STATUS_NDIS_ADAPTER_NOT_FOUND: return "STATUS_NDIS_ADAPTER_NOT_FOUND"; case STATUS_NDIS_OPEN_FAILED: return "STATUS_NDIS_OPEN_FAILED"; case STATUS_NDIS_DEVICE_FAILED: return "STATUS_NDIS_DEVICE_FAILED"; case STATUS_NDIS_MULTICAST_FULL: return "STATUS_NDIS_MULTICAST_FULL"; case STATUS_NDIS_MULTICAST_EXISTS: return "STATUS_NDIS_MULTICAST_EXISTS"; case STATUS_NDIS_MULTICAST_NOT_FOUND: return "STATUS_NDIS_MULTICAST_NOT_FOUND"; case STATUS_NDIS_REQUEST_ABORTED: return "STATUS_NDIS_REQUEST_ABORTED"; case STATUS_NDIS_RESET_IN_PROGRESS: return "STATUS_NDIS_RESET_IN_PROGRESS"; case STATUS_NDIS_INVALID_PACKET: return "STATUS_NDIS_INVALID_PACKET"; case STATUS_NDIS_INVALID_DEVICE_REQUEST: return "STATUS_NDIS_INVALID_DEVICE_REQUEST"; case STATUS_NDIS_ADAPTER_NOT_READY: return "STATUS_NDIS_ADAPTER_NOT_READY"; case STATUS_NDIS_INVALID_LENGTH: return "STATUS_NDIS_INVALID_LENGTH"; case STATUS_NDIS_INVALID_DATA: return "STATUS_NDIS_INVALID_DATA"; case STATUS_NDIS_BUFFER_TOO_SHORT: return "STATUS_NDIS_BUFFER_TOO_SHORT"; case STATUS_NDIS_INVALID_OID: return "STATUS_NDIS_INVALID_OID"; case STATUS_NDIS_ADAPTER_REMOVED: return "STATUS_NDIS_ADAPTER_REMOVED"; case STATUS_NDIS_UNSUPPORTED_MEDIA: return "STATUS_NDIS_UNSUPPORTED_MEDIA"; case STATUS_NDIS_GROUP_ADDRESS_IN_USE: return "STATUS_NDIS_GROUP_ADDRESS_IN_USE"; case STATUS_NDIS_FILE_NOT_FOUND: return "STATUS_NDIS_FILE_NOT_FOUND"; case STATUS_NDIS_ERROR_READING_FILE: return "STATUS_NDIS_ERROR_READING_FILE"; case STATUS_NDIS_ALREADY_MAPPED: return "STATUS_NDIS_ALREADY_MAPPED"; case STATUS_NDIS_RESOURCE_CONFLICT: return "STATUS_NDIS_RESOURCE_CONFLICT"; case STATUS_NDIS_MEDIA_DISCONNECTED: return "STATUS_NDIS_MEDIA_DISCONNECTED"; case STATUS_NDIS_INVALID_ADDRESS: return "STATUS_NDIS_INVALID_ADDRESS"; case STATUS_NDIS_PAUSED: return "STATUS_NDIS_PAUSED"; case STATUS_NDIS_INTERFACE_NOT_FOUND: return "STATUS_NDIS_INTERFACE_NOT_FOUND"; case STATUS_NDIS_UNSUPPORTED_REVISION: return "STATUS_NDIS_UNSUPPORTED_REVISION"; case STATUS_NDIS_INVALID_PORT: return "STATUS_NDIS_INVALID_PORT"; case STATUS_NDIS_INVALID_PORT_STATE: return "STATUS_NDIS_INVALID_PORT_STATE"; case STATUS_NDIS_LOW_POWER_STATE: return "STATUS_NDIS_LOW_POWER_STATE"; case STATUS_NDIS_NOT_SUPPORTED: return "STATUS_NDIS_NOT_SUPPORTED"; case STATUS_NDIS_OFFLOAD_POLICY: return "STATUS_NDIS_OFFLOAD_POLICY"; case STATUS_NDIS_OFFLOAD_CONNECTION_REJECTED: return "STATUS_NDIS_OFFLOAD_CONNECTION_REJECTED"; case STATUS_NDIS_OFFLOAD_PATH_REJECTED: return "STATUS_NDIS_OFFLOAD_PATH_REJECTED"; case STATUS_NDIS_DOT11_AUTO_CONFIG_ENABLED: return "STATUS_NDIS_DOT11_AUTO_CONFIG_ENABLED"; case STATUS_NDIS_DOT11_MEDIA_IN_USE: return "STATUS_NDIS_DOT11_MEDIA_IN_USE"; case STATUS_NDIS_DOT11_POWER_STATE_INVALID: return "STATUS_NDIS_DOT11_POWER_STATE_INVALID"; case STATUS_NDIS_PM_WOL_PATTERN_LIST_FULL: return "STATUS_NDIS_PM_WOL_PATTERN_LIST_FULL"; case STATUS_NDIS_PM_PROTOCOL_OFFLOAD_LIST_FULL: return "STATUS_NDIS_PM_PROTOCOL_OFFLOAD_LIST_FULL"; case STATUS_IPSEC_BAD_SPI: return "STATUS_IPSEC_BAD_SPI"; case STATUS_IPSEC_SA_LIFETIME_EXPIRED: return "STATUS_IPSEC_SA_LIFETIME_EXPIRED"; case STATUS_IPSEC_WRONG_SA: return "STATUS_IPSEC_WRONG_SA"; case STATUS_IPSEC_REPLAY_CHECK_FAILED: return "STATUS_IPSEC_REPLAY_CHECK_FAILED"; case STATUS_IPSEC_INVALID_PACKET: return "STATUS_IPSEC_INVALID_PACKET"; case STATUS_IPSEC_INTEGRITY_CHECK_FAILED: return "STATUS_IPSEC_INTEGRITY_CHECK_FAILED"; case STATUS_IPSEC_CLEAR_TEXT_DROP: return "STATUS_IPSEC_CLEAR_TEXT_DROP"; case STATUS_IPSEC_AUTH_FIREWALL_DROP: return "STATUS_IPSEC_AUTH_FIREWALL_DROP"; case STATUS_IPSEC_THROTTLE_DROP: return "STATUS_IPSEC_THROTTLE_DROP"; case STATUS_IPSEC_DOSP_BLOCK: return "STATUS_IPSEC_DOSP_BLOCK"; case STATUS_IPSEC_DOSP_RECEIVED_MULTICAST: return "STATUS_IPSEC_DOSP_RECEIVED_MULTICAST"; case STATUS_IPSEC_DOSP_INVALID_PACKET: return "STATUS_IPSEC_DOSP_INVALID_PACKET"; case STATUS_IPSEC_DOSP_STATE_LOOKUP_FAILED: return "STATUS_IPSEC_DOSP_STATE_LOOKUP_FAILED"; case STATUS_IPSEC_DOSP_MAX_ENTRIES: return "STATUS_IPSEC_DOSP_MAX_ENTRIES"; case STATUS_IPSEC_DOSP_KEYMOD_NOT_ALLOWED: return "STATUS_IPSEC_DOSP_KEYMOD_NOT_ALLOWED"; case STATUS_IPSEC_DOSP_MAX_PER_IP_RATELIMIT_QUEUES: return "STATUS_IPSEC_DOSP_MAX_PER_IP_RATELIMIT_QUEUES"; case STATUS_VOLMGR_MIRROR_NOT_SUPPORTED: return "STATUS_VOLMGR_MIRROR_NOT_SUPPORTED"; case STATUS_VOLMGR_RAID5_NOT_SUPPORTED: return "STATUS_VOLMGR_RAID5_NOT_SUPPORTED"; case STATUS_VIRTDISK_PROVIDER_NOT_FOUND: return "STATUS_VIRTDISK_PROVIDER_NOT_FOUND"; case STATUS_VIRTDISK_NOT_VIRTUAL_DISK: return "STATUS_VIRTDISK_NOT_VIRTUAL_DISK"; case STATUS_VHD_PARENT_VHD_ACCESS_DENIED: return "STATUS_VHD_PARENT_VHD_ACCESS_DENIED"; case STATUS_VHD_CHILD_PARENT_SIZE_MISMATCH: return "STATUS_VHD_CHILD_PARENT_SIZE_MISMATCH"; case STATUS_VHD_DIFFERENCING_CHAIN_CYCLE_DETECTED: return "STATUS_VHD_DIFFERENCING_CHAIN_CYCLE_DETECTED"; case STATUS_VHD_DIFFERENCING_CHAIN_ERROR_IN_PARENT: return "STATUS_VHD_DIFFERENCING_CHAIN_ERROR_IN_PARENT"; case STATUS_CASE_DIFFERING_NAMES_IN_DIR: return "STATUS_CASE_DIFFERING_NAMES_IN_DIR"; default: return std::format("Status {:08x}", (uint32_t)s); } } class ntstatus_error : public std::exception { public: ntstatus_error(NTSTATUS Status) : Status(Status) { msg = ntstatus_to_string(Status); } const char* what() const noexcept override { return msg.c_str(); } NTSTATUS Status; std::string msg; }; class formatted_error : public std::exception { public: template formatted_error(std::string_view s, Args&&... args) : msg(std::vformat(s, std::make_format_args(args...))) { } const char* what() const noexcept { return msg.c_str(); } private: std::string msg; }; template T query_information(HANDLE h); template class varbuf { public: T* operator*() { return (T*)buf.data(); } operator T*() { return (T*)buf.data(); } operator const T*() const { return (const T*)buf.data(); } std::vector buf; }; // test.cpp unique_handle create_file(std::u16string_view path, ACCESS_MASK access, ULONG atts, ULONG share, ULONG dispo, ULONG options, ULONG_PTR exp_info, std::optional allocation = std::nullopt); template std::vector> query_dir(const std::u16string& dir, std::u16string_view filter); varbuf query_all_information(HANDLE h); void test(const std::string& msg, const std::function& func); void exp_status(const std::function& func, NTSTATUS Status); std::u16string query_file_name_information(HANDLE h, bool normalized = false); void disable_token_privileges(HANDLE token); std::string u16string_to_string(std::u16string_view sv); extern enum fs_type fstype; // create.cpp void test_create(HANDLE token, const std::u16string& dir); void test_open_id(HANDLE token, const std::u16string& dir); // supersede.cpp void test_supersede(const std::u16string& dir); // overwrite.cpp void test_overwrite(const std::u16string& dir); // io.cpp void test_io(HANDLE token, const std::u16string& dir); std::vector random_data(size_t len); void write_file(HANDLE h, std::span data, std::optional offset = std::nullopt); void set_end_of_file(HANDLE h, uint64_t eof); std::vector read_file(HANDLE h, ULONG len, std::optional offset = std::nullopt); void write_file_wait(HANDLE h, std::span data, std::optional offset = std::nullopt); std::vector read_file_wait(HANDLE h, ULONG len, std::optional offset = std::nullopt); void set_allocation(HANDLE h, uint64_t alloc); void set_valid_data_length(HANDLE h, uint64_t vdl); void set_zero_data(HANDLE h, uint64_t start, uint64_t end); unique_handle create_event(); void adjust_token_privileges(HANDLE token, const LUID_AND_ATTRIBUTES& priv); // mmap.cpp void test_mmap(const std::u16string& dir); unique_handle create_section(ACCESS_MASK access, std::optional max_size, ULONG prot, ULONG atts, HANDLE file); std::vector pe_image(std::span data); // rename.cpp void test_rename(const std::u16string& dir); void test_rename_ex(HANDLE token, const std::u16string& dir); void set_rename_information(HANDLE h, bool replace_if_exists, HANDLE root_dir, std::u16string_view filename); // delete.cpp void test_delete(const std::u16string& dir); void test_delete_ex(HANDLE token, const std::u16string& dir); void set_disposition_information(HANDLE h, bool delete_file); void set_disposition_information_ex(HANDLE h, uint32_t flags); // links.cpp void test_links(HANDLE token, const std::u16string& dir); void test_links_ex(HANDLE token, const std::u16string& dir); std::vector> query_links(HANDLE h); void set_link_information(HANDLE h, bool replace_if_exists, HANDLE root_dir, std::u16string_view filename); // oplock.cpp void test_oplocks_i(HANDLE token, const std::u16string& dir); void test_oplocks_ii(HANDLE token, const std::u16string& dir); void test_oplocks_batch(HANDLE token, const std::u16string& dir); void test_oplocks_filter(HANDLE token, const std::u16string& dir); void test_oplocks_r(HANDLE token, const std::u16string& dir); void test_oplocks_rw(HANDLE token, const std::u16string& dir); void test_oplocks_rh(HANDLE token, const std::u16string& dir); void test_oplocks_rwh(HANDLE token, const std::u16string& dir); // cs.cpp void test_cs(const std::u16string& dir); // reparse.cpp void test_reparse(HANDLE token, const std::u16string& dir); // streams.cpp void test_streams(const std::u16string& dir); // ea.cpp void test_ea(const std::u16string& dir); void write_ea(HANDLE h, std::string_view name, std::string_view value, bool need_ea = false); // fileinfo.cpp void test_fileinfo(const std::u16string& dir); void set_basic_information(HANDLE h, int64_t creation_time, int64_t last_access_time, int64_t last_write_time, int64_t change_time, uint32_t attributes); // security.cpp void test_security(HANDLE token, const std::u16string& dir); void set_dacl(HANDLE h, ACCESS_MASK access); ================================================ FILE: src/tests/test.rc.in ================================================ #define APSTUDIO_READONLY_SYMBOLS #include #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United Kingdom) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK #pragma code_page(1252) ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "080904b0" BEGIN VALUE "FileDescription", "WinBtrfs test program" VALUE "FileVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" VALUE "InternalName", "test" VALUE "LegalCopyright", "Copyright (c) Mark Harmstone 2021-24" VALUE "OriginalFilename", "test.exe" VALUE "ProductName", "WinBtrfs" VALUE "ProductVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x809, 1200 END END 1 RT_MANIFEST "@CMAKE_CURRENT_SOURCE_DIR@/src/tests/manifest.xml" #endif // English (United Kingdom) resources ///////////////////////////////////////////////////////////////////////////// ================================================ FILE: src/treefuncs.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include "crc32c.h" __attribute__((nonnull(1,3,4,5))) NTSTATUS load_tree(device_extension* Vcb, uint64_t addr, uint8_t* buf, root* r, tree** pt) { tree_header* th; tree* t; tree_data* td; uint8_t h; bool inserted; LIST_ENTRY* le; th = (tree_header*)buf; t = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG); if (!t) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } if (th->level > 0) { t->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(tree_nonpaged), ALLOC_TAG); if (!t->nonpaged) { ERR("out of memory\n"); ExFreePool(t); return STATUS_INSUFFICIENT_RESOURCES; } ExInitializeFastMutex(&t->nonpaged->mutex); } else t->nonpaged = NULL; RtlCopyMemory(&t->header, th, sizeof(tree_header)); t->hash = calc_crc32c(0xffffffff, (uint8_t*)&addr, sizeof(uint64_t)); t->has_address = true; t->Vcb = Vcb; t->parent = NULL; t->root = r; t->paritem = NULL; t->size = 0; t->new_address = 0; t->has_new_address = false; t->updated_extents = false; t->write = false; t->uniqueness_determined = false; InitializeListHead(&t->itemlist); if (t->header.level == 0) { // leaf node leaf_node* ln = (leaf_node*)(buf + sizeof(tree_header)); unsigned int i; if ((t->header.num_items * sizeof(leaf_node)) + sizeof(tree_header) > Vcb->superblock.node_size) { ERR("tree at %I64x has more items than expected (%x)\n", addr, t->header.num_items); ExFreePool(t); return STATUS_INSUFFICIENT_RESOURCES; } for (i = 0; i < t->header.num_items; i++) { td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside); if (!td) { ERR("out of memory\n"); ExFreePool(t); return STATUS_INSUFFICIENT_RESOURCES; } td->key = ln[i].key; if (ln[i].size > 0) td->data = buf + sizeof(tree_header) + ln[i].offset; else td->data = NULL; if (ln[i].size + sizeof(tree_header) + sizeof(leaf_node) > Vcb->superblock.node_size) { ERR("overlarge item in tree %I64x: %u > %Iu\n", addr, ln[i].size, Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node)); ExFreeToPagedLookasideList(&t->Vcb->tree_data_lookaside, td); ExFreePool(t); return STATUS_INTERNAL_ERROR; } td->size = (uint16_t)ln[i].size; td->ignore = false; td->inserted = false; InsertTailList(&t->itemlist, &td->list_entry); t->size += ln[i].size; } t->size += t->header.num_items * sizeof(leaf_node); t->buf = buf; } else { internal_node* in = (internal_node*)(buf + sizeof(tree_header)); unsigned int i; if ((t->header.num_items * sizeof(internal_node)) + sizeof(tree_header) > Vcb->superblock.node_size) { ERR("tree at %I64x has more items than expected (%x)\n", addr, t->header.num_items); ExFreePool(t); return STATUS_INSUFFICIENT_RESOURCES; } for (i = 0; i < t->header.num_items; i++) { td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside); if (!td) { ERR("out of memory\n"); ExFreePool(t); return STATUS_INSUFFICIENT_RESOURCES; } td->key = in[i].key; td->treeholder.address = in[i].address; td->treeholder.generation = in[i].generation; td->treeholder.tree = NULL; td->ignore = false; td->inserted = false; InsertTailList(&t->itemlist, &td->list_entry); } t->size = t->header.num_items * sizeof(internal_node); t->buf = NULL; } ExAcquireFastMutex(&Vcb->trees_list_mutex); InsertTailList(&Vcb->trees, &t->list_entry); h = t->hash >> 24; if (!Vcb->trees_ptrs[h]) { uint8_t h2 = h; le = Vcb->trees_hash.Flink; if (h2 > 0) { h2--; do { if (Vcb->trees_ptrs[h2]) { le = Vcb->trees_ptrs[h2]; break; } h2--; } while (h2 > 0); } } else le = Vcb->trees_ptrs[h]; inserted = false; while (le != &Vcb->trees_hash) { tree* t2 = CONTAINING_RECORD(le, tree, list_entry_hash); if (t2->hash >= t->hash) { InsertHeadList(le->Blink, &t->list_entry_hash); inserted = true; break; } le = le->Flink; } if (!inserted) InsertTailList(&Vcb->trees_hash, &t->list_entry_hash); if (!Vcb->trees_ptrs[h] || t->list_entry_hash.Flink == Vcb->trees_ptrs[h]) Vcb->trees_ptrs[h] = &t->list_entry_hash; ExReleaseFastMutex(&Vcb->trees_list_mutex); TRACE("returning %p\n", t); *pt = t; return STATUS_SUCCESS; } __attribute__((nonnull(1,2,3,4))) static NTSTATUS do_load_tree2(device_extension* Vcb, tree_holder* th, uint8_t* buf, root* r, tree* t, tree_data* td) { if (!th->tree) { NTSTATUS Status; tree* nt; Status = load_tree(Vcb, th->address, buf, r, &nt); if (!NT_SUCCESS(Status)) { ERR("load_tree returned %08lx\n", Status); return Status; } nt->parent = t; #ifdef DEBUG_PARANOID if (t && t->header.level <= nt->header.level) int3; #endif nt->paritem = td; th->tree = nt; } return STATUS_SUCCESS; } __attribute__((nonnull(1,2,3))) NTSTATUS do_load_tree(device_extension* Vcb, tree_holder* th, root* r, tree* t, tree_data* td, PIRP Irp) { NTSTATUS Status; uint8_t* buf; chunk* c; buf = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.node_size, ALLOC_TAG); if (!buf) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } Status = read_data(Vcb, th->address, Vcb->superblock.node_size, NULL, true, buf, NULL, &c, Irp, th->generation, false, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned 0x%08lx\n", Status); ExFreePool(buf); return Status; } if (t) ExAcquireFastMutex(&t->nonpaged->mutex); else ExAcquireResourceExclusiveLite(&r->nonpaged->load_tree_lock, true); Status = do_load_tree2(Vcb, th, buf, r, t, td); if (t) ExReleaseFastMutex(&t->nonpaged->mutex); else ExReleaseResourceLite(&r->nonpaged->load_tree_lock); if (!th->tree || th->tree->buf != buf) ExFreePool(buf); if (!NT_SUCCESS(Status)) { ERR("do_load_tree2 returned %08lx\n", Status); return Status; } return Status; } __attribute__((nonnull(1))) void free_tree(tree* t) { tree* par; root* r = t->root; // No need to acquire lock, as this is only ever called while Vcb->tree_lock held exclusively par = t->parent; if (r && r->treeholder.tree != t) r = NULL; if (par) { if (t->paritem) t->paritem->treeholder.tree = NULL; } while (!IsListEmpty(&t->itemlist)) { tree_data* td = CONTAINING_RECORD(RemoveHeadList(&t->itemlist), tree_data, list_entry); if (t->header.level == 0 && td->data && td->inserted) ExFreePool(td->data); ExFreeToPagedLookasideList(&t->Vcb->tree_data_lookaside, td); } RemoveEntryList(&t->list_entry); if (r) r->treeholder.tree = NULL; if (t->list_entry_hash.Flink) { uint8_t h = t->hash >> 24; if (t->Vcb->trees_ptrs[h] == &t->list_entry_hash) { if (t->list_entry_hash.Flink != &t->Vcb->trees_hash) { tree* t2 = CONTAINING_RECORD(t->list_entry_hash.Flink, tree, list_entry_hash); if ((t2->hash >> 24) == h) t->Vcb->trees_ptrs[h] = &t2->list_entry_hash; else t->Vcb->trees_ptrs[h] = NULL; } else t->Vcb->trees_ptrs[h] = NULL; } RemoveEntryList(&t->list_entry_hash); } if (t->buf) ExFreePool(t->buf); if (t->nonpaged) ExFreePool(t->nonpaged); ExFreePool(t); } __attribute__((nonnull(1))) static __inline tree_data* first_item(tree* t) { LIST_ENTRY* le = t->itemlist.Flink; if (le == &t->itemlist) return NULL; return CONTAINING_RECORD(le, tree_data, list_entry); } __attribute__((nonnull(1,2))) static __inline tree_data* prev_item(tree* t, tree_data* td) { LIST_ENTRY* le = td->list_entry.Blink; if (le == &t->itemlist) return NULL; return CONTAINING_RECORD(le, tree_data, list_entry); } __attribute__((nonnull(1,2))) static __inline tree_data* next_item(tree* t, tree_data* td) { LIST_ENTRY* le = td->list_entry.Flink; if (le == &t->itemlist) return NULL; return CONTAINING_RECORD(le, tree_data, list_entry); } __attribute__((nonnull(1,2,3,4))) static NTSTATUS next_item2(device_extension* Vcb, tree* t, tree_data* td, traverse_ptr* tp) { tree_data* td2 = next_item(t, td); tree* t2; if (td2) { tp->tree = t; tp->item = td2; return STATUS_SUCCESS; } t2 = t; do { td2 = t2->paritem; t2 = t2->parent; } while (td2 && !next_item(t2, td2)); if (!td2) return STATUS_NOT_FOUND; td2 = next_item(t2, td2); return find_item_to_level(Vcb, t2->root, tp, &td2->key, false, t->header.level, NULL); } __attribute__((nonnull(1,2,3,4,5))) NTSTATUS skip_to_difference(device_extension* Vcb, traverse_ptr* tp, traverse_ptr* tp2, bool* ended1, bool* ended2) { NTSTATUS Status; tree *t1, *t2; tree_data *td1, *td2; t1 = tp->tree; t2 = tp2->tree; do { td1 = t1->paritem; td2 = t2->paritem; t1 = t1->parent; t2 = t2->parent; } while (t1 && t2 && t1->header.address == t2->header.address); while (true) { traverse_ptr tp3, tp4; Status = next_item2(Vcb, t1, td1, &tp3); if (Status == STATUS_NOT_FOUND) *ended1 = true; else if (!NT_SUCCESS(Status)) { ERR("next_item2 returned %08lx\n", Status); return Status; } Status = next_item2(Vcb, t2, td2, &tp4); if (Status == STATUS_NOT_FOUND) *ended2 = true; else if (!NT_SUCCESS(Status)) { ERR("next_item2 returned %08lx\n", Status); return Status; } if (*ended1 || *ended2) { if (!*ended1) { Status = find_item(Vcb, t1->root, tp, &tp3.item->key, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } } else if (!*ended2) { Status = find_item(Vcb, t2->root, tp2, &tp4.item->key, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } } return STATUS_SUCCESS; } if (tp3.tree->header.address != tp4.tree->header.address) { Status = find_item(Vcb, t1->root, tp, &tp3.item->key, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } Status = find_item(Vcb, t2->root, tp2, &tp4.item->key, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } return STATUS_SUCCESS; } t1 = tp3.tree; td1 = tp3.item; t2 = tp4.tree; td2 = tp4.item; } } __attribute__((nonnull(1,2,3,4))) static NTSTATUS find_item_in_tree(device_extension* Vcb, tree* t, traverse_ptr* tp, const KEY* searchkey, bool ignore, uint8_t level, PIRP Irp) { int cmp; tree_data *td, *lasttd; KEY key2; cmp = 1; td = first_item(t); lasttd = NULL; if (!td) return STATUS_NOT_FOUND; key2 = *searchkey; do { cmp = keycmp(key2, td->key); if (cmp == 1) { lasttd = td; td = next_item(t, td); } if (t->header.level == 0 && cmp == 0 && !ignore && td && td->ignore) { tree_data* origtd = td; while (td && td->ignore) td = next_item(t, td); if (td) { cmp = keycmp(key2, td->key); if (cmp != 0) { td = origtd; cmp = 0; } } else td = origtd; } } while (td && cmp == 1); if ((cmp == -1 || !td) && lasttd) td = lasttd; if (t->header.level == 0) { if (td->ignore && !ignore) { traverse_ptr oldtp; oldtp.tree = t; oldtp.item = td; while (find_prev_item(Vcb, &oldtp, tp, Irp)) { if (!tp->item->ignore) return STATUS_SUCCESS; oldtp = *tp; } // if no valid entries before where item should be, look afterwards instead oldtp.tree = t; oldtp.item = td; while (find_next_item(Vcb, &oldtp, tp, true, Irp)) { if (!tp->item->ignore) return STATUS_SUCCESS; oldtp = *tp; } return STATUS_NOT_FOUND; } else { tp->tree = t; tp->item = td; } return STATUS_SUCCESS; } else { NTSTATUS Status; while (td && td->treeholder.tree && IsListEmpty(&td->treeholder.tree->itemlist)) { td = prev_item(t, td); } if (!td) return STATUS_NOT_FOUND; if (t->header.level <= level) { tp->tree = t; tp->item = td; return STATUS_SUCCESS; } if (!td->treeholder.tree) { Status = do_load_tree(Vcb, &td->treeholder, t->root, t, td, Irp); if (!NT_SUCCESS(Status)) { ERR("do_load_tree returned %08lx\n", Status); return Status; } } Status = find_item_in_tree(Vcb, td->treeholder.tree, tp, searchkey, ignore, level, Irp); return Status; } } __attribute__((nonnull(1,2,3,4))) NTSTATUS find_item(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _Out_ traverse_ptr* tp, _In_ const KEY* searchkey, _In_ bool ignore, _In_opt_ PIRP Irp) { NTSTATUS Status; if (!r->treeholder.tree) { Status = do_load_tree(Vcb, &r->treeholder, r, NULL, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("do_load_tree returned %08lx\n", Status); return Status; } } Status = find_item_in_tree(Vcb, r->treeholder.tree, tp, searchkey, ignore, 0, Irp); if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) { ERR("find_item_in_tree returned %08lx\n", Status); } return Status; } __attribute__((nonnull(1,2,3,4))) NTSTATUS find_item_to_level(device_extension* Vcb, root* r, traverse_ptr* tp, const KEY* searchkey, bool ignore, uint8_t level, PIRP Irp) { NTSTATUS Status; if (!r->treeholder.tree) { Status = do_load_tree(Vcb, &r->treeholder, r, NULL, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("do_load_tree returned %08lx\n", Status); return Status; } } Status = find_item_in_tree(Vcb, r->treeholder.tree, tp, searchkey, ignore, level, Irp); if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) { ERR("find_item_in_tree returned %08lx\n", Status); } if (Status == STATUS_NOT_FOUND) { tp->tree = r->treeholder.tree; tp->item = NULL; } return Status; } __attribute__((nonnull(1,2,3))) bool find_next_item(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, const traverse_ptr* tp, traverse_ptr* next_tp, bool ignore, PIRP Irp) { tree* t; tree_data *td = NULL, *next; NTSTATUS Status; next = next_item(tp->tree, tp->item); if (!ignore) { while (next && next->ignore) next = next_item(tp->tree, next); } if (next) { next_tp->tree = tp->tree; next_tp->item = next; #ifdef DEBUG_PARANOID if (!ignore && next_tp->item->ignore) { ERR("error - returning ignored item\n"); int3; } #endif return true; } if (!tp->tree->parent) return false; t = tp->tree; do { if (t->parent) { td = next_item(t->parent, t->paritem); if (td) break; } t = t->parent; } while (t); if (!t) return false; if (!td->treeholder.tree) { Status = do_load_tree(Vcb, &td->treeholder, t->parent->root, t->parent, td, Irp); if (!NT_SUCCESS(Status)) { ERR("do_load_tree returned %08lx\n", Status); return false; } } t = td->treeholder.tree; while (t->header.level != 0) { tree_data* fi; fi = first_item(t); if (!fi) return false; if (!fi->treeholder.tree) { Status = do_load_tree(Vcb, &fi->treeholder, t->parent->root, t, fi, Irp); if (!NT_SUCCESS(Status)) { ERR("do_load_tree returned %08lx\n", Status); return false; } } t = fi->treeholder.tree; } next_tp->tree = t; next_tp->item = first_item(t); if (!next_tp->item) return false; if (!ignore && next_tp->item->ignore) { traverse_ptr ntp2; bool b; while ((b = find_next_item(Vcb, next_tp, &ntp2, true, Irp))) { *next_tp = ntp2; if (!next_tp->item->ignore) break; } if (!b) return false; } #ifdef DEBUG_PARANOID if (!ignore && next_tp->item->ignore) { ERR("error - returning ignored item\n"); int3; } #endif return true; } __attribute__((nonnull(1))) static __inline tree_data* last_item(tree* t) { LIST_ENTRY* le = t->itemlist.Blink; if (le == &t->itemlist) return NULL; return CONTAINING_RECORD(le, tree_data, list_entry); } __attribute__((nonnull(1,2,3))) bool find_prev_item(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, const traverse_ptr* tp, traverse_ptr* prev_tp, PIRP Irp) { tree* t; tree_data* td; NTSTATUS Status; // FIXME - support ignore flag if (prev_item(tp->tree, tp->item)) { prev_tp->tree = tp->tree; prev_tp->item = prev_item(tp->tree, tp->item); return true; } if (!tp->tree->parent) return false; t = tp->tree; while (t && (!t->parent || !prev_item(t->parent, t->paritem))) { t = t->parent; } if (!t) return false; td = prev_item(t->parent, t->paritem); if (!td->treeholder.tree) { Status = do_load_tree(Vcb, &td->treeholder, t->parent->root, t->parent, td, Irp); if (!NT_SUCCESS(Status)) { ERR("do_load_tree returned %08lx\n", Status); return false; } } t = td->treeholder.tree; while (t->header.level != 0) { tree_data* li; li = last_item(t); if (!li->treeholder.tree) { Status = do_load_tree(Vcb, &li->treeholder, t->parent->root, t, li, Irp); if (!NT_SUCCESS(Status)) { ERR("do_load_tree returned %08lx\n", Status); return false; } } t = li->treeholder.tree; } prev_tp->tree = t; prev_tp->item = last_item(t); return true; } __attribute__((nonnull(1,2))) void free_trees_root(device_extension* Vcb, root* r) { LIST_ENTRY* le; ULONG level; for (level = 0; level <= 255; level++) { bool empty = true; le = Vcb->trees.Flink; while (le != &Vcb->trees) { LIST_ENTRY* nextle = le->Flink; tree* t = CONTAINING_RECORD(le, tree, list_entry); if (t->root == r) { if (t->header.level == level) { bool top = !t->paritem; empty = false; free_tree(t); if (top && r->treeholder.tree == t) r->treeholder.tree = NULL; if (IsListEmpty(&Vcb->trees)) return; } else if (t->header.level > level) empty = false; } le = nextle; } if (empty) break; } } __attribute__((nonnull(1))) void free_trees(device_extension* Vcb) { LIST_ENTRY* le; ULONG level; for (level = 0; level <= 255; level++) { bool empty = true; le = Vcb->trees.Flink; while (le != &Vcb->trees) { LIST_ENTRY* nextle = le->Flink; tree* t = CONTAINING_RECORD(le, tree, list_entry); root* r = t->root; if (t->header.level == level) { bool top = !t->paritem; empty = false; free_tree(t); if (top && r->treeholder.tree == t) r->treeholder.tree = NULL; if (IsListEmpty(&Vcb->trees)) break; } else if (t->header.level > level) empty = false; le = nextle; } if (empty) break; } reap_filerefs(Vcb, Vcb->root_fileref); reap_fcbs(Vcb); } #ifdef _MSC_VER #pragma warning(push) #pragma warning(suppress: 28194) #endif __attribute__((nonnull(1,3))) void add_rollback(_In_ LIST_ENTRY* rollback, _In_ enum rollback_type type, _In_ __drv_aliasesMem void* ptr) { rollback_item* ri; ri = ExAllocatePoolWithTag(PagedPool, sizeof(rollback_item), ALLOC_TAG); if (!ri) { ERR("out of memory\n"); return; } ri->type = type; ri->ptr = ptr; InsertTailList(rollback, &ri->list_entry); } #ifdef _MSC_VER #pragma warning(pop) #endif #ifdef _MSC_VER #pragma warning(push) #pragma warning(suppress: 28194) #endif __attribute__((nonnull(1,2))) NTSTATUS insert_tree_item(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _In_ uint64_t obj_id, _In_ uint8_t obj_type, _In_ uint64_t offset, _In_reads_bytes_opt_(size) _When_(return >= 0, __drv_aliasesMem) void* data, _In_ uint16_t size, _Out_opt_ traverse_ptr* ptp, _In_opt_ PIRP Irp) { traverse_ptr tp; KEY searchkey; int cmp; tree_data *td, *paritem; tree* t; #ifdef _DEBUG LIST_ENTRY* le; KEY firstitem = {0xcccccccccccccccc,0xcc,0xcccccccccccccccc}; #endif NTSTATUS Status; TRACE("(%p, %p, %I64x, %x, %I64x, %p, %x, %p)\n", Vcb, r, obj_id, obj_type, offset, data, size, ptp); searchkey.obj_id = obj_id; searchkey.obj_type = obj_type; searchkey.offset = offset; Status = find_item(Vcb, r, &tp, &searchkey, true, Irp); if (Status == STATUS_NOT_FOUND) { if (!r->treeholder.tree) { Status = do_load_tree(Vcb, &r->treeholder, r, NULL, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("do_load_tree returned %08lx\n", Status); return Status; } } if (r->treeholder.tree && r->treeholder.tree->header.num_items == 0) { tp.tree = r->treeholder.tree; tp.item = NULL; } else { ERR("error: unable to load tree for root %I64x\n", r->id); return STATUS_INTERNAL_ERROR; } } else if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; } TRACE("tp.item = %p\n", tp.item); if (tp.item) { TRACE("tp.item->key = %p\n", &tp.item->key); cmp = keycmp(searchkey, tp.item->key); if (cmp == 0 && !tp.item->ignore) { ERR("error: key (%I64x,%x,%I64x) already present\n", obj_id, obj_type, offset); #ifdef DEBUG_PARANOID int3; #endif return STATUS_INTERNAL_ERROR; } } else cmp = -1; td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside); if (!td) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } td->key = searchkey; td->size = size; td->data = data; td->ignore = false; td->inserted = true; #ifdef _DEBUG le = tp.tree->itemlist.Flink; while (le != &tp.tree->itemlist) { tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry); firstitem = td2->key; break; } 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); #endif if (cmp == -1) { // very first key in root InsertHeadList(&tp.tree->itemlist, &td->list_entry); paritem = tp.tree->paritem; while (paritem) { if (!keycmp(paritem->key, tp.item->key)) { paritem->key = searchkey; } else break; paritem = paritem->treeholder.tree->paritem; } } else if (cmp == 0) InsertHeadList(tp.item->list_entry.Blink, &td->list_entry); // make sure non-deleted item is before deleted ones else InsertHeadList(&tp.item->list_entry, &td->list_entry); tp.tree->header.num_items++; tp.tree->size += size + sizeof(leaf_node); if (!tp.tree->write) { tp.tree->write = true; Vcb->need_write = true; } if (ptp) *ptp = tp; t = tp.tree; while (t) { if (t->paritem && t->paritem->ignore) { t->paritem->ignore = false; t->parent->header.num_items++; t->parent->size += sizeof(internal_node); } t->header.generation = Vcb->superblock.generation; t = t->parent; } return STATUS_SUCCESS; } #ifdef _MSC_VER #pragma warning(pop) #endif __attribute__((nonnull(1,2))) NTSTATUS delete_tree_item(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _Inout_ traverse_ptr* tp) { tree* t; uint64_t gen; 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"); #ifdef DEBUG_PARANOID if (tp->item->ignore) { 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); int3; return STATUS_INTERNAL_ERROR; } #endif tp->item->ignore = true; if (!tp->tree->write) { tp->tree->write = true; Vcb->need_write = true; } tp->tree->header.num_items--; if (tp->tree->header.level == 0) tp->tree->size -= sizeof(leaf_node) + tp->item->size; else tp->tree->size -= sizeof(internal_node); gen = tp->tree->Vcb->superblock.generation; t = tp->tree; while (t) { t->header.generation = gen; t = t->parent; } return STATUS_SUCCESS; } __attribute__((nonnull(1))) void clear_rollback(LIST_ENTRY* rollback) { while (!IsListEmpty(rollback)) { LIST_ENTRY* le = RemoveHeadList(rollback); rollback_item* ri = CONTAINING_RECORD(le, rollback_item, list_entry); switch (ri->type) { case ROLLBACK_ADD_SPACE: case ROLLBACK_SUBTRACT_SPACE: case ROLLBACK_INSERT_EXTENT: case ROLLBACK_DELETE_EXTENT: ExFreePool(ri->ptr); break; default: break; } ExFreePool(ri); } } __attribute__((nonnull(1,2))) void do_rollback(device_extension* Vcb, LIST_ENTRY* rollback) { NTSTATUS Status; rollback_item* ri; while (!IsListEmpty(rollback)) { LIST_ENTRY* le = RemoveTailList(rollback); ri = CONTAINING_RECORD(le, rollback_item, list_entry); switch (ri->type) { case ROLLBACK_INSERT_EXTENT: { rollback_extent* re = ri->ptr; re->ext->ignore = true; switch (re->ext->extent_data.type) { case EXTENT_TYPE_REGULAR: case EXTENT_TYPE_PREALLOC: { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)re->ext->extent_data.data; if (ed2->size != 0) { chunk* c = get_chunk_from_address(Vcb, ed2->address); if (c) { Status = update_changed_extent_ref(Vcb, c, ed2->address, ed2->size, re->fcb->subvol->id, re->fcb->inode, re->ext->offset - ed2->offset, -1, re->fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, NULL); if (!NT_SUCCESS(Status)) ERR("update_changed_extent_ref returned %08lx\n", Status); } re->fcb->inode_item.st_blocks -= ed2->num_bytes; } break; } case EXTENT_TYPE_INLINE: re->fcb->inode_item.st_blocks -= re->ext->extent_data.decoded_size; break; } ExFreePool(re); break; } case ROLLBACK_DELETE_EXTENT: { rollback_extent* re = ri->ptr; re->ext->ignore = false; switch (re->ext->extent_data.type) { case EXTENT_TYPE_REGULAR: case EXTENT_TYPE_PREALLOC: { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)re->ext->extent_data.data; if (ed2->size != 0) { chunk* c = get_chunk_from_address(Vcb, ed2->address); if (c) { Status = update_changed_extent_ref(Vcb, c, ed2->address, ed2->size, re->fcb->subvol->id, re->fcb->inode, re->ext->offset - ed2->offset, 1, re->fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, NULL); if (!NT_SUCCESS(Status)) ERR("update_changed_extent_ref returned %08lx\n", Status); } re->fcb->inode_item.st_blocks += ed2->num_bytes; } break; } case EXTENT_TYPE_INLINE: re->fcb->inode_item.st_blocks += re->ext->extent_data.decoded_size; break; } ExFreePool(re); break; } case ROLLBACK_ADD_SPACE: case ROLLBACK_SUBTRACT_SPACE: { rollback_space* rs = ri->ptr; if (rs->chunk) acquire_chunk_lock(rs->chunk, Vcb); if (ri->type == ROLLBACK_ADD_SPACE) space_list_subtract2(rs->list, rs->list_size, rs->address, rs->length, NULL, NULL); else space_list_add2(rs->list, rs->list_size, rs->address, rs->length, NULL, NULL); if (rs->chunk) { if (ri->type == ROLLBACK_ADD_SPACE) rs->chunk->used += rs->length; else rs->chunk->used -= rs->length; } if (rs->chunk) { LIST_ENTRY* le2 = le->Blink; while (le2 != rollback) { LIST_ENTRY* le3 = le2->Blink; rollback_item* ri2 = CONTAINING_RECORD(le2, rollback_item, list_entry); if (ri2->type == ROLLBACK_ADD_SPACE || ri2->type == ROLLBACK_SUBTRACT_SPACE) { rollback_space* rs2 = ri2->ptr; if (rs2->chunk == rs->chunk) { if (ri2->type == ROLLBACK_ADD_SPACE) { space_list_subtract2(rs2->list, rs2->list_size, rs2->address, rs2->length, NULL, NULL); rs->chunk->used += rs2->length; } else { space_list_add2(rs2->list, rs2->list_size, rs2->address, rs2->length, NULL, NULL); rs->chunk->used -= rs2->length; } ExFreePool(rs2); RemoveEntryList(&ri2->list_entry); ExFreePool(ri2); } } le2 = le3; } release_chunk_lock(rs->chunk, Vcb); } ExFreePool(rs); break; } } ExFreePool(ri); } } __attribute__((nonnull(1,2,3))) static NTSTATUS find_tree_end(tree* t, KEY* tree_end, bool* no_end) { tree* p; p = t; do { tree_data* pi; if (!p->parent) { tree_end->obj_id = 0xffffffffffffffff; tree_end->obj_type = 0xff; tree_end->offset = 0xffffffffffffffff; *no_end = true; return STATUS_SUCCESS; } pi = p->paritem; if (pi->list_entry.Flink != &p->parent->itemlist) { tree_data* td = CONTAINING_RECORD(pi->list_entry.Flink, tree_data, list_entry); *tree_end = td->key; *no_end = false; return STATUS_SUCCESS; } p = p->parent; } while (p); return STATUS_INTERNAL_ERROR; } __attribute__((nonnull(1,2))) void clear_batch_list(device_extension* Vcb, LIST_ENTRY* batchlist) { while (!IsListEmpty(batchlist)) { LIST_ENTRY* le = RemoveHeadList(batchlist); batch_root* br = CONTAINING_RECORD(le, batch_root, list_entry); while (!IsListEmpty(&br->items_ind)) { batch_item_ind* bii = CONTAINING_RECORD(RemoveHeadList(&br->items_ind), batch_item_ind, list_entry); while (!IsListEmpty(&bii->items)) { batch_item* bi = CONTAINING_RECORD(RemoveHeadList(&bii->items), batch_item, list_entry); ExFreeToPagedLookasideList(&Vcb->batch_item_lookaside, bi); } ExFreePool(bii); } ExFreePool(br); } } __attribute__((nonnull(1,2,3))) static void add_delete_inode_extref(device_extension* Vcb, batch_item* bi, LIST_ENTRY* listhead) { batch_item* bi2; LIST_ENTRY* le; INODE_REF* delir = (INODE_REF*)bi->data; INODE_EXTREF* ier; TRACE("entry in INODE_REF not found, adding Batch_DeleteInodeExtRef entry\n"); bi2 = ExAllocateFromPagedLookasideList(&Vcb->batch_item_lookaside); if (!bi2) { ERR("out of memory\n"); return; } ier = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_EXTREF) - 1 + delir->n, ALLOC_TAG); if (!ier) { ERR("out of memory\n"); ExFreePool(bi2); return; } ier->dir = bi->key.offset; ier->index = delir->index; ier->n = delir->n; RtlCopyMemory(ier->name, delir->name, delir->n); bi2->key.obj_id = bi->key.obj_id; bi2->key.obj_type = TYPE_INODE_EXTREF; bi2->key.offset = calc_crc32c((uint32_t)bi->key.offset, (uint8_t*)ier->name, ier->n); bi2->data = ier; bi2->datalen = sizeof(INODE_EXTREF) - 1 + ier->n; bi2->operation = Batch_DeleteInodeExtRef; le = bi->list_entry.Flink; while (le != listhead) { batch_item* bi3 = CONTAINING_RECORD(le, batch_item, list_entry); if (keycmp(bi3->key, bi2->key) != -1) { InsertHeadList(le->Blink, &bi2->list_entry); return; } le = le->Flink; } InsertTailList(listhead, &bi2->list_entry); } __attribute__((nonnull(1,2,3,4,6,7))) static NTSTATUS handle_batch_collision(device_extension* Vcb, batch_item* bi, tree* t, tree_data* td, tree_data* newtd, LIST_ENTRY* listhead, bool* ignore) { if (bi->operation == Batch_Delete || bi->operation == Batch_SetXattr || bi->operation == Batch_DirItem || bi->operation == Batch_InodeRef || bi->operation == Batch_InodeExtRef || bi->operation == Batch_DeleteDirItem || bi->operation == Batch_DeleteInodeRef || bi->operation == Batch_DeleteInodeExtRef || bi->operation == Batch_DeleteXattr) { uint16_t maxlen = (uint16_t)(Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node)); switch (bi->operation) { case Batch_SetXattr: { if (td->size < sizeof(DIR_ITEM)) { 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)); } else { uint8_t* newdata; ULONG size = td->size; DIR_ITEM* newxa = (DIR_ITEM*)bi->data; DIR_ITEM* xa = (DIR_ITEM*)td->data; while (true) { ULONG oldxasize; if (size < sizeof(DIR_ITEM) || size < sizeof(DIR_ITEM) - 1 + xa->m + xa->n) { ERR("(%I64x,%x,%I64x) was truncated\n", bi->key.obj_id, bi->key.obj_type, bi->key.offset); break; } oldxasize = sizeof(DIR_ITEM) - 1 + xa->m + xa->n; if (xa->n == newxa->n && RtlCompareMemory(newxa->name, xa->name, xa->n) == xa->n) { uint64_t pos; // replace if (td->size + bi->datalen - oldxasize > maxlen) ERR("DIR_ITEM would be over maximum size, truncating (%u + %u - %lu > %u)\n", td->size, bi->datalen, oldxasize, maxlen); newdata = ExAllocatePoolWithTag(PagedPool, td->size + bi->datalen - oldxasize, ALLOC_TAG); if (!newdata) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } pos = (uint8_t*)xa - td->data; if (pos + oldxasize < td->size) // copy after changed xattr RtlCopyMemory(newdata + pos + bi->datalen, td->data + pos + oldxasize, (ULONG)(td->size - pos - oldxasize)); if (pos > 0) { // copy before changed xattr RtlCopyMemory(newdata, td->data, (ULONG)pos); xa = (DIR_ITEM*)(newdata + pos); } else xa = (DIR_ITEM*)newdata; RtlCopyMemory(xa, bi->data, bi->datalen); bi->datalen = (uint16_t)min(td->size + bi->datalen - oldxasize, maxlen); ExFreePool(bi->data); bi->data = newdata; break; } if ((uint8_t*)xa - (uint8_t*)td->data + oldxasize >= size) { // not found, add to end of data if (td->size + bi->datalen > maxlen) ERR("DIR_ITEM would be over maximum size, truncating (%u + %u > %u)\n", td->size, bi->datalen, maxlen); newdata = ExAllocatePoolWithTag(PagedPool, td->size + bi->datalen, ALLOC_TAG); if (!newdata) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newdata, td->data, td->size); xa = (DIR_ITEM*)((uint8_t*)newdata + td->size); RtlCopyMemory(xa, bi->data, bi->datalen); bi->datalen = min(bi->datalen + td->size, maxlen); ExFreePool(bi->data); bi->data = newdata; break; } else { xa = (DIR_ITEM*)&xa->name[xa->m + xa->n]; size -= oldxasize; } } } break; } case Batch_DirItem: { uint8_t* newdata; if (td->size + bi->datalen > maxlen) { ERR("DIR_ITEM would be over maximum size (%u + %u > %u)\n", td->size, bi->datalen, maxlen); return STATUS_INTERNAL_ERROR; } newdata = ExAllocatePoolWithTag(PagedPool, td->size + bi->datalen, ALLOC_TAG); if (!newdata) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newdata, td->data, td->size); RtlCopyMemory(newdata + td->size, bi->data, bi->datalen); bi->datalen += td->size; ExFreePool(bi->data); bi->data = newdata; break; } case Batch_InodeRef: { uint8_t* newdata; if (td->size + bi->datalen > maxlen) { if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) { INODE_REF* ir = (INODE_REF*)bi->data; INODE_EXTREF* ier; uint16_t ierlen; batch_item* bi2; LIST_ENTRY* le; bool inserted = false; TRACE("INODE_REF would be too long, adding INODE_EXTREF instead\n"); ierlen = (uint16_t)(offsetof(INODE_EXTREF, name[0]) + ir->n); ier = ExAllocatePoolWithTag(PagedPool, ierlen, ALLOC_TAG); if (!ier) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ier->dir = bi->key.offset; ier->index = ir->index; ier->n = ir->n; RtlCopyMemory(ier->name, ir->name, ier->n); bi2 = ExAllocateFromPagedLookasideList(&Vcb->batch_item_lookaside); if (!bi2) { ERR("out of memory\n"); ExFreePool(ier); return STATUS_INSUFFICIENT_RESOURCES; } bi2->key.obj_id = bi->key.obj_id; bi2->key.obj_type = TYPE_INODE_EXTREF; bi2->key.offset = calc_crc32c((uint32_t)ier->dir, (uint8_t*)ier->name, ier->n); bi2->data = ier; bi2->datalen = ierlen; bi2->operation = Batch_InodeExtRef; le = bi->list_entry.Flink; while (le != listhead) { batch_item* bi3 = CONTAINING_RECORD(le, batch_item, list_entry); if (keycmp(bi3->key, bi2->key) != -1) { InsertHeadList(le->Blink, &bi2->list_entry); inserted = true; } le = le->Flink; } if (!inserted) InsertTailList(listhead, &bi2->list_entry); *ignore = true; return STATUS_SUCCESS; } else { ERR("INODE_REF would be over maximum size (%u + %u > %u)\n", td->size, bi->datalen, maxlen); return STATUS_INTERNAL_ERROR; } } newdata = ExAllocatePoolWithTag(PagedPool, td->size + bi->datalen, ALLOC_TAG); if (!newdata) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newdata, td->data, td->size); RtlCopyMemory(newdata + td->size, bi->data, bi->datalen); bi->datalen += td->size; ExFreePool(bi->data); bi->data = newdata; break; } case Batch_InodeExtRef: { uint8_t* newdata; if (td->size + bi->datalen > maxlen) { ERR("INODE_EXTREF would be over maximum size (%u + %u > %u)\n", td->size, bi->datalen, maxlen); return STATUS_INTERNAL_ERROR; } newdata = ExAllocatePoolWithTag(PagedPool, td->size + bi->datalen, ALLOC_TAG); if (!newdata) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(newdata, td->data, td->size); RtlCopyMemory(newdata + td->size, bi->data, bi->datalen); bi->datalen += td->size; ExFreePool(bi->data); bi->data = newdata; break; } case Batch_DeleteDirItem: { if (td->size < sizeof(DIR_ITEM)) { ERR("DIR_ITEM was %u bytes, expected at least %Iu\n", td->size, sizeof(DIR_ITEM)); return STATUS_INTERNAL_ERROR; } else { DIR_ITEM *di, *deldi; LONG len; deldi = (DIR_ITEM*)bi->data; di = (DIR_ITEM*)td->data; len = td->size; do { if (di->m == deldi->m && di->n == deldi->n && RtlCompareMemory(di->name, deldi->name, di->n + di->m) == di->n + di->m) { uint16_t newlen = td->size - (sizeof(DIR_ITEM) - sizeof(char) + di->n + di->m); if (newlen == 0) { TRACE("deleting DIR_ITEM\n"); } else { uint8_t *newdi = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *dioff; tree_data* td2; if (!newdi) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } TRACE("modifying DIR_ITEM\n"); if ((uint8_t*)di > td->data) { RtlCopyMemory(newdi, td->data, (uint8_t*)di - td->data); dioff = newdi + ((uint8_t*)di - td->data); } else { dioff = newdi; } if ((uint8_t*)&di->name[di->n + di->m] < td->data + td->size) RtlCopyMemory(dioff, &di->name[di->n + di->m], td->size - ((uint8_t*)&di->name[di->n + di->m] - td->data)); td2 = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside); if (!td2) { ERR("out of memory\n"); ExFreePool(newdi); return STATUS_INSUFFICIENT_RESOURCES; } td2->key = bi->key; td2->size = newlen; td2->data = newdi; td2->ignore = false; td2->inserted = true; InsertHeadList(td->list_entry.Blink, &td2->list_entry); t->header.num_items++; t->size += newlen + sizeof(leaf_node); t->write = true; } break; } len -= sizeof(DIR_ITEM) - sizeof(char) + di->n + di->m; di = (DIR_ITEM*)&di->name[di->n + di->m]; if (len == 0) { TRACE("could not find DIR_ITEM to delete\n"); *ignore = true; return STATUS_SUCCESS; } } while (len > 0); } break; } case Batch_DeleteInodeRef: { if (td->size < sizeof(INODE_REF)) { ERR("INODE_REF was %u bytes, expected at least %Iu\n", td->size, sizeof(INODE_REF)); return STATUS_INTERNAL_ERROR; } else { INODE_REF *ir, *delir; ULONG len; bool changed = false; delir = (INODE_REF*)bi->data; ir = (INODE_REF*)td->data; len = td->size; do { uint16_t itemlen; if (len < sizeof(INODE_REF) || len < offsetof(INODE_REF, name[0]) + ir->n) { ERR("INODE_REF was truncated\n"); break; } itemlen = (uint16_t)offsetof(INODE_REF, name[0]) + ir->n; if (ir->n == delir->n && RtlCompareMemory(ir->name, delir->name, ir->n) == ir->n) { uint16_t newlen = td->size - itemlen; changed = true; if (newlen == 0) TRACE("deleting INODE_REF\n"); else { uint8_t *newir = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *iroff; tree_data* td2; if (!newir) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } TRACE("modifying INODE_REF\n"); if ((uint8_t*)ir > td->data) { RtlCopyMemory(newir, td->data, (uint8_t*)ir - td->data); iroff = newir + ((uint8_t*)ir - td->data); } else { iroff = newir; } if ((uint8_t*)&ir->name[ir->n] < td->data + td->size) RtlCopyMemory(iroff, &ir->name[ir->n], td->size - ((uint8_t*)&ir->name[ir->n] - td->data)); td2 = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside); if (!td2) { ERR("out of memory\n"); ExFreePool(newir); return STATUS_INSUFFICIENT_RESOURCES; } td2->key = bi->key; td2->size = newlen; td2->data = newir; td2->ignore = false; td2->inserted = true; InsertHeadList(td->list_entry.Blink, &td2->list_entry); t->header.num_items++; t->size += newlen + sizeof(leaf_node); t->write = true; } break; } if (len > itemlen) { len -= itemlen; ir = (INODE_REF*)&ir->name[ir->n]; } else break; } while (len > 0); if (!changed) { if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) { TRACE("entry in INODE_REF not found, adding Batch_DeleteInodeExtRef entry\n"); add_delete_inode_extref(Vcb, bi, listhead); *ignore = true; return STATUS_SUCCESS; } else WARN("entry not found in INODE_REF\n"); } } break; } case Batch_DeleteInodeExtRef: { if (td->size < sizeof(INODE_EXTREF)) { ERR("INODE_EXTREF was %u bytes, expected at least %Iu\n", td->size, sizeof(INODE_EXTREF)); return STATUS_INTERNAL_ERROR; } else { INODE_EXTREF *ier, *delier; ULONG len; delier = (INODE_EXTREF*)bi->data; ier = (INODE_EXTREF*)td->data; len = td->size; do { uint16_t itemlen; if (len < sizeof(INODE_EXTREF) || len < offsetof(INODE_EXTREF, name[0]) + ier->n) { ERR("INODE_REF was truncated\n"); break; } itemlen = (uint16_t)offsetof(INODE_EXTREF, name[0]) + ier->n; if (ier->dir == delier->dir && ier->n == delier->n && RtlCompareMemory(ier->name, delier->name, ier->n) == ier->n) { uint16_t newlen = td->size - itemlen; if (newlen == 0) TRACE("deleting INODE_EXTREF\n"); else { uint8_t *newier = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *ieroff; tree_data* td2; if (!newier) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } TRACE("modifying INODE_EXTREF\n"); if ((uint8_t*)ier > td->data) { RtlCopyMemory(newier, td->data, (uint8_t*)ier - td->data); ieroff = newier + ((uint8_t*)ier - td->data); } else { ieroff = newier; } if ((uint8_t*)&ier->name[ier->n] < td->data + td->size) RtlCopyMemory(ieroff, &ier->name[ier->n], td->size - ((uint8_t*)&ier->name[ier->n] - td->data)); td2 = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside); if (!td2) { ERR("out of memory\n"); ExFreePool(newier); return STATUS_INSUFFICIENT_RESOURCES; } td2->key = bi->key; td2->size = newlen; td2->data = newier; td2->ignore = false; td2->inserted = true; InsertHeadList(td->list_entry.Blink, &td2->list_entry); t->header.num_items++; t->size += newlen + sizeof(leaf_node); t->write = true; } break; } if (len > itemlen) { len -= itemlen; ier = (INODE_EXTREF*)&ier->name[ier->n]; } else break; } while (len > 0); } break; } case Batch_DeleteXattr: { if (td->size < sizeof(DIR_ITEM)) { ERR("XATTR_ITEM was %u bytes, expected at least %Iu\n", td->size, sizeof(DIR_ITEM)); return STATUS_INTERNAL_ERROR; } else { DIR_ITEM *di, *deldi; LONG len; deldi = (DIR_ITEM*)bi->data; di = (DIR_ITEM*)td->data; len = td->size; do { if (di->n == deldi->n && RtlCompareMemory(di->name, deldi->name, di->n) == di->n) { uint16_t newlen = td->size - ((uint16_t)offsetof(DIR_ITEM, name[0]) + di->n + di->m); if (newlen == 0) TRACE("deleting XATTR_ITEM\n"); else { uint8_t *newdi = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *dioff; tree_data* td2; if (!newdi) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } TRACE("modifying XATTR_ITEM\n"); if ((uint8_t*)di > td->data) { RtlCopyMemory(newdi, td->data, (uint8_t*)di - td->data); dioff = newdi + ((uint8_t*)di - td->data); } else dioff = newdi; if ((uint8_t*)&di->name[di->n + di->m] < td->data + td->size) RtlCopyMemory(dioff, &di->name[di->n + di->m], td->size - ((uint8_t*)&di->name[di->n + di->m] - td->data)); td2 = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside); if (!td2) { ERR("out of memory\n"); ExFreePool(newdi); return STATUS_INSUFFICIENT_RESOURCES; } td2->key = bi->key; td2->size = newlen; td2->data = newdi; td2->ignore = false; td2->inserted = true; InsertHeadList(td->list_entry.Blink, &td2->list_entry); t->header.num_items++; t->size += newlen + sizeof(leaf_node); t->write = true; } break; } len -= sizeof(DIR_ITEM) - sizeof(char) + di->n + di->m; di = (DIR_ITEM*)&di->name[di->n + di->m]; if (len == 0) { TRACE("could not find DIR_ITEM to delete\n"); *ignore = true; return STATUS_SUCCESS; } } while (len > 0); } break; } case Batch_Delete: break; default: ERR("unexpected batch operation type\n"); return STATUS_INTERNAL_ERROR; } // delete old item if (!td->ignore) { td->ignore = true; t->header.num_items--; t->size -= sizeof(leaf_node) + td->size; t->write = true; } if (newtd) { newtd->data = bi->data; newtd->size = bi->datalen; InsertHeadList(td->list_entry.Blink, &newtd->list_entry); } } else { ERR("(%I64x,%x,%I64x) already exists\n", bi->key.obj_id, bi->key.obj_type, bi->key.offset); return STATUS_INTERNAL_ERROR; } *ignore = false; return STATUS_SUCCESS; } __attribute__((nonnull(1,2))) static NTSTATUS commit_batch_list_root(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, batch_root* br, PIRP Irp) { LIST_ENTRY items; LIST_ENTRY* le; NTSTATUS Status; TRACE("root: %I64x\n", br->r->id); InitializeListHead(&items); // move sub-lists into one big list while (!IsListEmpty(&br->items_ind)) { batch_item_ind* bii = CONTAINING_RECORD(RemoveHeadList(&br->items_ind), batch_item_ind, list_entry); items.Blink->Flink = bii->items.Flink; bii->items.Flink->Blink = items.Blink; items.Blink = bii->items.Blink; bii->items.Blink->Flink = &items; ExFreePool(bii); } le = items.Flink; while (le != &items) { batch_item* bi = CONTAINING_RECORD(le, batch_item, list_entry); LIST_ENTRY* le2; traverse_ptr tp; KEY tree_end; bool no_end; tree_data *td, *listhead; int cmp; tree* t; bool ignore = false; TRACE("(%I64x,%x,%I64x)\n", bi->key.obj_id, bi->key.obj_type, bi->key.offset); Status = find_item(Vcb, br->r, &tp, &bi->key, true, Irp); if (!NT_SUCCESS(Status)) { // FIXME - handle STATUS_NOT_FOUND ERR("find_item returned %08lx\n", Status); return Status; } Status = find_tree_end(tp.tree, &tree_end, &no_end); if (!NT_SUCCESS(Status)) { ERR("find_tree_end returned %08lx\n", Status); return Status; } if (bi->operation == Batch_DeleteInode) { if (tp.item->key.obj_id == bi->key.obj_id) { bool ended = false; td = tp.item; if (!tp.item->ignore) { tp.item->ignore = true; tp.tree->header.num_items--; tp.tree->size -= tp.item->size + sizeof(leaf_node); tp.tree->write = true; } le2 = tp.item->list_entry.Flink; while (le2 != &tp.tree->itemlist) { td = CONTAINING_RECORD(le2, tree_data, list_entry); if (td->key.obj_id == bi->key.obj_id) { if (!td->ignore) { td->ignore = true; tp.tree->header.num_items--; tp.tree->size -= td->size + sizeof(leaf_node); tp.tree->write = true; } } else { ended = true; break; } le2 = le2->Flink; } while (!ended) { traverse_ptr next_tp; tp.item = td; if (!find_next_item(Vcb, &tp, &next_tp, false, Irp)) break; tp = next_tp; le2 = &tp.item->list_entry; while (le2 != &tp.tree->itemlist) { td = CONTAINING_RECORD(le2, tree_data, list_entry); if (td->key.obj_id == bi->key.obj_id) { if (!td->ignore) { td->ignore = true; tp.tree->header.num_items--; tp.tree->size -= td->size + sizeof(leaf_node); tp.tree->write = true; } } else { ended = true; break; } le2 = le2->Flink; } } } } else if (bi->operation == Batch_DeleteExtentData) { 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)) { traverse_ptr tp2; if (find_next_item(Vcb, &tp, &tp2, false, Irp)) { if (tp2.item->key.obj_id == bi->key.obj_id && tp2.item->key.obj_type == bi->key.obj_type) { tp = tp2; Status = find_tree_end(tp.tree, &tree_end, &no_end); if (!NT_SUCCESS(Status)) { ERR("find_tree_end returned %08lx\n", Status); return Status; } } } } if (tp.item->key.obj_id == bi->key.obj_id && tp.item->key.obj_type == bi->key.obj_type) { bool ended = false; td = tp.item; if (!tp.item->ignore) { tp.item->ignore = true; tp.tree->header.num_items--; tp.tree->size -= tp.item->size + sizeof(leaf_node); tp.tree->write = true; } le2 = tp.item->list_entry.Flink; while (le2 != &tp.tree->itemlist) { td = CONTAINING_RECORD(le2, tree_data, list_entry); if (td->key.obj_id == bi->key.obj_id && td->key.obj_type == bi->key.obj_type) { if (!td->ignore) { td->ignore = true; tp.tree->header.num_items--; tp.tree->size -= td->size + sizeof(leaf_node); tp.tree->write = true; } } else { ended = true; break; } le2 = le2->Flink; } while (!ended) { traverse_ptr next_tp; tp.item = td; if (!find_next_item(Vcb, &tp, &next_tp, false, Irp)) break; tp = next_tp; le2 = &tp.item->list_entry; while (le2 != &tp.tree->itemlist) { td = CONTAINING_RECORD(le2, tree_data, list_entry); if (td->key.obj_id == bi->key.obj_id && td->key.obj_type == bi->key.obj_type) { if (!td->ignore) { td->ignore = true; tp.tree->header.num_items--; tp.tree->size -= td->size + sizeof(leaf_node); tp.tree->write = true; } } else { ended = true; break; } le2 = le2->Flink; } } } } else if (bi->operation == Batch_DeleteFreeSpace) { if (tp.item->key.obj_id >= bi->key.obj_id && tp.item->key.obj_id < bi->key.obj_id + bi->key.offset) { bool ended = false; td = tp.item; if (!tp.item->ignore) { tp.item->ignore = true; tp.tree->header.num_items--; tp.tree->size -= tp.item->size + sizeof(leaf_node); tp.tree->write = true; } le2 = tp.item->list_entry.Flink; while (le2 != &tp.tree->itemlist) { td = CONTAINING_RECORD(le2, tree_data, list_entry); if (td->key.obj_id >= bi->key.obj_id && td->key.obj_id < bi->key.obj_id + bi->key.offset) { if (!td->ignore) { td->ignore = true; tp.tree->header.num_items--; tp.tree->size -= td->size + sizeof(leaf_node); tp.tree->write = true; } } else { ended = true; break; } le2 = le2->Flink; } while (!ended) { traverse_ptr next_tp; tp.item = td; if (!find_next_item(Vcb, &tp, &next_tp, false, Irp)) break; tp = next_tp; le2 = &tp.item->list_entry; while (le2 != &tp.tree->itemlist) { td = CONTAINING_RECORD(le2, tree_data, list_entry); if (td->key.obj_id >= bi->key.obj_id && td->key.obj_id < bi->key.obj_id + bi->key.offset) { if (!td->ignore) { td->ignore = true; tp.tree->header.num_items--; tp.tree->size -= td->size + sizeof(leaf_node); tp.tree->write = true; } } else { ended = true; break; } le2 = le2->Flink; } } } } else { if (bi->operation == Batch_Delete || bi->operation == Batch_DeleteDirItem || bi->operation == Batch_DeleteInodeRef || bi->operation == Batch_DeleteInodeExtRef || bi->operation == Batch_DeleteXattr) td = NULL; else { td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside); if (!td) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } td->key = bi->key; td->size = bi->datalen; td->data = bi->data; td->ignore = false; td->inserted = true; } cmp = keycmp(bi->key, tp.item->key); if (cmp == -1) { // very first key in root if (td) { tree_data* paritem; InsertHeadList(&tp.tree->itemlist, &td->list_entry); paritem = tp.tree->paritem; while (paritem) { if (!keycmp(paritem->key, tp.item->key)) { paritem->key = bi->key; } else break; paritem = paritem->treeholder.tree->paritem; } } } else if (cmp == 0) { // item already exists if (tp.item->ignore) { if (td) InsertHeadList(tp.item->list_entry.Blink, &td->list_entry); } else { Status = handle_batch_collision(Vcb, bi, tp.tree, tp.item, td, &items, &ignore); if (!NT_SUCCESS(Status)) { ERR("handle_batch_collision returned %08lx\n", Status); #ifdef _DEBUG int3; #endif if (td) ExFreeToPagedLookasideList(&Vcb->tree_data_lookaside, td); return Status; } } } else if (td) { InsertHeadList(&tp.item->list_entry, &td->list_entry); } if (bi->operation == Batch_DeleteInodeRef && cmp != 0 && Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) { add_delete_inode_extref(Vcb, bi, &items); } if (!ignore && td) { tp.tree->header.num_items++; tp.tree->size += bi->datalen + sizeof(leaf_node); tp.tree->write = true; listhead = td; } else listhead = tp.item; while (listhead->list_entry.Blink != &tp.tree->itemlist) { tree_data* prevtd = CONTAINING_RECORD(listhead->list_entry.Blink, tree_data, list_entry); if (!keycmp(prevtd->key, listhead->key)) listhead = prevtd; else break; } le2 = le->Flink; while (le2 != &items) { batch_item* bi2 = CONTAINING_RECORD(le2, batch_item, list_entry); if (bi2->operation == Batch_DeleteInode || bi2->operation == Batch_DeleteExtentData || bi2->operation == Batch_DeleteFreeSpace) break; if (no_end || keycmp(bi2->key, tree_end) == -1) { LIST_ENTRY* le3; bool inserted = false; ignore = false; if (bi2->operation == Batch_Delete || bi2->operation == Batch_DeleteDirItem || bi2->operation == Batch_DeleteInodeRef || bi2->operation == Batch_DeleteInodeExtRef || bi2->operation == Batch_DeleteXattr) td = NULL; else { td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside); if (!td) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } td->key = bi2->key; td->size = bi2->datalen; td->data = bi2->data; td->ignore = false; td->inserted = true; } le3 = &listhead->list_entry; while (le3 != &tp.tree->itemlist) { tree_data* td2 = CONTAINING_RECORD(le3, tree_data, list_entry); cmp = keycmp(bi2->key, td2->key); if (cmp == 0) { if (td2->ignore) { if (td) { InsertHeadList(le3->Blink, &td->list_entry); inserted = true; } else if (bi2->operation == Batch_DeleteInodeRef && Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) { add_delete_inode_extref(Vcb, bi2, &items); } } else { Status = handle_batch_collision(Vcb, bi2, tp.tree, td2, td, &items, &ignore); if (!NT_SUCCESS(Status)) { ERR("handle_batch_collision returned %08lx\n", Status); #ifdef _DEBUG int3; #endif return Status; } } inserted = true; break; } else if (cmp == -1) { if (td) { InsertHeadList(le3->Blink, &td->list_entry); inserted = true; } else if (bi2->operation == Batch_DeleteInodeRef && Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) { add_delete_inode_extref(Vcb, bi2, &items); } break; } le3 = le3->Flink; } if (td) { if (!inserted) InsertTailList(&tp.tree->itemlist, &td->list_entry); if (!ignore) { tp.tree->header.num_items++; tp.tree->size += bi2->datalen + sizeof(leaf_node); listhead = td; } } else if (!inserted && bi2->operation == Batch_DeleteInodeRef && Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) { add_delete_inode_extref(Vcb, bi2, &items); } while (listhead->list_entry.Blink != &tp.tree->itemlist) { tree_data* prevtd = CONTAINING_RECORD(listhead->list_entry.Blink, tree_data, list_entry); if (!keycmp(prevtd->key, listhead->key)) listhead = prevtd; else break; } le = le2; } else break; le2 = le2->Flink; } t = tp.tree; while (t) { if (t->paritem && t->paritem->ignore) { t->paritem->ignore = false; t->parent->header.num_items++; t->parent->size += sizeof(internal_node); } t->header.generation = Vcb->superblock.generation; t = t->parent; } } le = le->Flink; } // FIXME - remove as we are going along while (!IsListEmpty(&items)) { batch_item* bi = CONTAINING_RECORD(RemoveHeadList(&items), batch_item, list_entry); if ((bi->operation == Batch_DeleteDirItem || bi->operation == Batch_DeleteInodeRef || bi->operation == Batch_DeleteInodeExtRef || bi->operation == Batch_DeleteXattr) && bi->data) ExFreePool(bi->data); ExFreeToPagedLookasideList(&Vcb->batch_item_lookaside, bi); } return STATUS_SUCCESS; } __attribute__((nonnull(1,2))) NTSTATUS commit_batch_list(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, LIST_ENTRY* batchlist, PIRP Irp) { NTSTATUS Status; while (!IsListEmpty(batchlist)) { LIST_ENTRY* le = RemoveHeadList(batchlist); batch_root* br2 = CONTAINING_RECORD(le, batch_root, list_entry); Status = commit_batch_list_root(Vcb, br2, Irp); if (!NT_SUCCESS(Status)) { ERR("commit_batch_list_root returned %08lx\n", Status); return Status; } ExFreePool(br2); } return STATUS_SUCCESS; } ================================================ FILE: src/ubtrfs/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by ubtrfs.rc // // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 101 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: src/ubtrfs/ubtrfs.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include #include #include #include #define WIN32_NO_STATUS #include #include #include #include #include #include #include #include #include #include #include #include "../btrfs.h" #include "../btrfsioctl.h" #include "../crc32c.h" #include "../zstd/lib/common/xxhash.h" #if defined(_X86_) || defined(_AMD64_) #ifndef _MSC_VER #include #else #include #endif #endif #define SHA256_HASH_SIZE 32 void calc_sha256(uint8_t* hash, const void* input, size_t len); #define BLAKE2_HASH_SIZE 32 void blake2b(void *out, size_t outlen, const void* in, size_t inlen); #define FSCTL_LOCK_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 6, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_UNLOCK_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 7, METHOD_BUFFERED, FILE_ANY_ACCESS) #define FSCTL_DISMOUNT_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 8, METHOD_BUFFERED, FILE_ANY_ACCESS) #ifndef _MSC_VER // not in mingw yet #define DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED 0x80000000 #endif #ifdef __cplusplus extern "C" { #endif NTSTATUS NTAPI NtWriteFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key); NTSTATUS NTAPI NtReadFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key); #ifdef __cplusplus } #endif // These are undocumented, and what comes from format.exe typedef struct { void* table; void* unk1; WCHAR* string; } DSTRING; typedef struct { void* table; } STREAM_MESSAGE; #define FORMAT_FLAG_QUICK_FORMAT 0x00000001 #define FORMAT_FLAG_UNKNOWN1 0x00000002 #define FORMAT_FLAG_DISMOUNT_FIRST 0x00000004 #define FORMAT_FLAG_UNKNOWN2 0x00000040 #define FORMAT_FLAG_LARGE_RECORDS 0x00000100 #define FORMAT_FLAG_INTEGRITY_DISABLE 0x00000100 typedef struct { uint16_t unk1; uint16_t unk2; uint32_t flags; DSTRING* label; } options; FORCEINLINE VOID InitializeListHead(PLIST_ENTRY ListHead) { ListHead->Flink = ListHead->Blink = ListHead; } FORCEINLINE VOID InsertTailList(PLIST_ENTRY ListHead, PLIST_ENTRY Entry) { PLIST_ENTRY Blink; Blink = ListHead->Blink; Entry->Flink = ListHead; Entry->Blink = Blink; Blink->Flink = Entry; ListHead->Blink = Entry; } typedef struct { KEY key; uint16_t size; void* data; LIST_ENTRY list_entry; } btrfs_item; typedef struct { uint64_t address; uint64_t size; LIST_ENTRY list_entry; } used_space_extent; typedef struct { uint64_t offset; CHUNK_ITEM* chunk_item; uint64_t lastoff; uint64_t used; LIST_ENTRY used_space; LIST_ENTRY list_entry; } btrfs_chunk; typedef struct { uint64_t id; tree_header header; btrfs_chunk* c; LIST_ENTRY items; LIST_ENTRY list_entry; } btrfs_root; typedef struct { DEV_ITEM dev_item; uint64_t last_alloc; } btrfs_dev; #define keycmp(key1, key2)\ ((key1.obj_id < key2.obj_id) ? -1 :\ ((key1.obj_id > key2.obj_id) ? 1 :\ ((key1.obj_type < key2.obj_type) ? -1 :\ ((key1.obj_type > key2.obj_type) ? 1 :\ ((key1.offset < key2.offset) ? -1 :\ ((key1.offset > key2.offset) ? 1 :\ 0)))))) HMODULE module; ULONG def_sector_size = 0, def_node_size = 0; uint64_t def_incompat_flags = BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF | BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA; uint64_t def_compat_ro_flags = 0; uint16_t def_csum_type = CSUM_TYPE_CRC32C; // the following definitions come from fmifs.h in ReactOS typedef struct { ULONG Lines; PCHAR Output; } TEXTOUTPUT, *PTEXTOUTPUT; typedef enum { FMIFS_UNKNOWN0, FMIFS_UNKNOWN1, FMIFS_UNKNOWN2, FMIFS_UNKNOWN3, FMIFS_UNKNOWN4, FMIFS_UNKNOWN5, FMIFS_UNKNOWN6, FMIFS_UNKNOWN7, FMIFS_FLOPPY, FMIFS_UNKNOWN9, FMIFS_UNKNOWN10, FMIFS_REMOVABLE, FMIFS_HARDDISK, FMIFS_UNKNOWN13, FMIFS_UNKNOWN14, FMIFS_UNKNOWN15, FMIFS_UNKNOWN16, FMIFS_UNKNOWN17, FMIFS_UNKNOWN18, FMIFS_UNKNOWN19, FMIFS_UNKNOWN20, FMIFS_UNKNOWN21, FMIFS_UNKNOWN22, FMIFS_UNKNOWN23, } FMIFS_MEDIA_FLAG; typedef enum { PROGRESS, DONEWITHSTRUCTURE, UNKNOWN2, UNKNOWN3, UNKNOWN4, UNKNOWN5, INSUFFICIENTRIGHTS, FSNOTSUPPORTED, VOLUMEINUSE, UNKNOWN9, UNKNOWNA, DONE, UNKNOWNC, UNKNOWND, OUTPUT, STRUCTUREPROGRESS, CLUSTERSIZETOOSMALL, } CALLBACKCOMMAND; typedef BOOLEAN (NTAPI* PFMIFSCALLBACK)(CALLBACKCOMMAND Command, ULONG SubAction, PVOID ActionInfo); static bool IsListEmpty(LIST_ENTRY* head) { return head->Flink == head; } static LIST_ENTRY* RemoveHeadList(LIST_ENTRY* head) { LIST_ENTRY *flink, *entry; entry = head->Flink; flink = entry->Flink; head->Flink = flink; flink->Blink = head; return entry; } NTSTATUS WINAPI ChkdskEx(PUNICODE_STRING DriveRoot, BOOLEAN FixErrors, BOOLEAN Verbose, BOOLEAN CheckOnlyIfDirty, BOOLEAN ScanDrive, PFMIFSCALLBACK Callback) { // STUB if (Callback) { TEXTOUTPUT TextOut; TextOut.Lines = 1; TextOut.Output = "stub, not implemented"; Callback(OUTPUT, 0, &TextOut); } return STATUS_SUCCESS; } static btrfs_root* add_root(LIST_ENTRY* roots, uint64_t id) { btrfs_root* root; root = malloc(sizeof(btrfs_root)); root->id = id; RtlZeroMemory(&root->header, sizeof(tree_header)); InitializeListHead(&root->items); InsertTailList(roots, &root->list_entry); return root; } static void free_roots(LIST_ENTRY* roots) { LIST_ENTRY* le; le = roots->Flink; while (le != roots) { LIST_ENTRY *le2 = le->Flink, *le3; btrfs_root* r = CONTAINING_RECORD(le, btrfs_root, list_entry); le3 = r->items.Flink; while (le3 != &r->items) { LIST_ENTRY* le4 = le3->Flink; btrfs_item* item = CONTAINING_RECORD(le3, btrfs_item, list_entry); if (item->data) free(item->data); free(item); le3 = le4; } free(r); le = le2; } } static void free_chunks(LIST_ENTRY* chunks) { LIST_ENTRY* le; le = chunks->Flink; while (le != chunks) { LIST_ENTRY *le2 = le->Flink; btrfs_chunk* c = CONTAINING_RECORD(le, btrfs_chunk, list_entry); while (!IsListEmpty(&c->used_space)) { used_space_extent* use = CONTAINING_RECORD(RemoveHeadList(&c->used_space), used_space_extent, list_entry); free(use); } free(c->chunk_item); free(c); le = le2; } } static void add_item(btrfs_root* r, uint64_t obj_id, uint8_t obj_type, uint64_t offset, void* data, uint16_t size) { LIST_ENTRY* le; btrfs_item* item; item = malloc(sizeof(btrfs_item)); item->key.obj_id = obj_id; item->key.obj_type = obj_type; item->key.offset = offset; item->size = size; if (size == 0) item->data = NULL; else { item->data = malloc(size); memcpy(item->data, data, size); } le = r->items.Flink; while (le != &r->items) { btrfs_item* i2 = CONTAINING_RECORD(le, btrfs_item, list_entry); if (keycmp(item->key, i2->key) != 1) { InsertTailList(le, &item->list_entry); return; } le = le->Flink; } InsertTailList(&r->items, &item->list_entry); } static uint64_t find_chunk_offset(uint64_t size, uint64_t offset, btrfs_dev* dev, btrfs_root* dev_root, BTRFS_UUID* chunkuuid) { uint64_t off; DEV_EXTENT de; off = dev->last_alloc; dev->last_alloc += size; dev->dev_item.bytes_used += size; de.chunktree = BTRFS_ROOT_CHUNK; de.objid = 0x100; de.address = offset; de.length = size; de.chunktree_uuid = *chunkuuid; add_item(dev_root, dev->dev_item.dev_id, TYPE_DEV_EXTENT, off, &de, sizeof(DEV_EXTENT)); return off; } static 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) { uint64_t off, size; uint16_t stripes, i; btrfs_chunk* c; LIST_ENTRY* le; CHUNK_ITEM_STRIPE* cis; uint64_t stripe_length = max(sector_size, 0x10000); off = 0xc00000; le = chunks->Flink; while (le != chunks) { c = CONTAINING_RECORD(le, btrfs_chunk, list_entry); if (c->offset + c->chunk_item->size > off) off = c->offset + c->chunk_item->size; le = le->Flink; } if (flags & BLOCK_FLAG_METADATA) { if (dev->dev_item.num_bytes > 0xC80000000) // 50 GB size = 0x40000000; // 1 GB else size = 0x10000000; // 256 MB } else // BLOCK_FLAG_SYSTEM size = 0x800000; size = min(size, dev->dev_item.num_bytes / 10); // cap at 10% size &= ~(stripe_length - 1); stripes = flags & BLOCK_FLAG_DUPLICATE ? 2 : 1; if (dev->dev_item.num_bytes - dev->dev_item.bytes_used < stripes * size) // not enough space return NULL; c = malloc(sizeof(btrfs_chunk)); c->offset = off; c->lastoff = off; c->used = 0; InitializeListHead(&c->used_space); c->chunk_item = malloc(sizeof(CHUNK_ITEM) + (stripes * sizeof(CHUNK_ITEM_STRIPE))); c->chunk_item->size = size; c->chunk_item->root_id = BTRFS_ROOT_EXTENT; c->chunk_item->stripe_length = stripe_length; c->chunk_item->type = flags; c->chunk_item->opt_io_alignment = stripe_length; c->chunk_item->opt_io_width = stripe_length; c->chunk_item->sector_size = sector_size; c->chunk_item->num_stripes = stripes; c->chunk_item->sub_stripes = 0; cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; for (i = 0; i < stripes; i++) { cis[i].dev_id = dev->dev_item.dev_id; cis[i].offset = find_chunk_offset(size, c->offset, dev, dev_root, chunkuuid); cis[i].dev_uuid = dev->dev_item.device_uuid; } add_item(chunk_root, 0x100, TYPE_CHUNK_ITEM, c->offset, c->chunk_item, sizeof(CHUNK_ITEM) + (stripes * sizeof(CHUNK_ITEM_STRIPE))); InsertTailList(chunks, &c->list_entry); return c; } static bool superblock_collision(btrfs_chunk* c, uint64_t address) { CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; uint64_t stripe = (address - c->offset) / c->chunk_item->stripe_length; uint16_t i, j; for (i = 0; i < c->chunk_item->num_stripes; i++) { j = 0; while (superblock_addrs[j] != 0) { if (superblock_addrs[j] >= cis[i].offset) { uint64_t stripe2 = (superblock_addrs[j] - cis[i].offset) / c->chunk_item->stripe_length; if (stripe2 == stripe) return true; } j++; } } return false; } static uint64_t get_next_address(btrfs_chunk* c) { uint64_t addr; addr = c->lastoff; while (superblock_collision(c, addr)) { addr = addr - ((addr - c->offset) % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; if (addr >= c->offset + c->chunk_item->size) // chunk has been exhausted return 0; } return addr; } typedef struct { EXTENT_ITEM ei; uint8_t type; TREE_BLOCK_REF tbr; } EXTENT_ITEM_METADATA; typedef struct { EXTENT_ITEM ei; EXTENT_ITEM2 ei2; uint8_t type; TREE_BLOCK_REF tbr; } EXTENT_ITEM_METADATA2; static void assign_addresses(LIST_ENTRY* roots, btrfs_chunk* sys_chunk, btrfs_chunk* metadata_chunk, uint32_t node_size, btrfs_root* root_root, btrfs_root* extent_root, bool skinny) { LIST_ENTRY* le; le = roots->Flink; while (le != roots) { btrfs_root* r = CONTAINING_RECORD(le, btrfs_root, list_entry); btrfs_chunk* c = r->id == BTRFS_ROOT_CHUNK ? sys_chunk : metadata_chunk; bool done_use = false; r->header.address = get_next_address(c); if (!IsListEmpty(&c->used_space)) { used_space_extent* use = CONTAINING_RECORD(c->used_space.Blink, used_space_extent, list_entry); if (use->address + use->size == r->header.address) { use->size += node_size; done_use = true; } } if (!done_use) { used_space_extent* use = malloc(sizeof(used_space_extent)); use->address = r->header.address; use->size = node_size; InsertTailList(&c->used_space, &use->list_entry); } r->c = c; c->lastoff = r->header.address + node_size; c->used += node_size; if (skinny) { EXTENT_ITEM_METADATA eim; eim.ei.refcount = 1; eim.ei.generation = 1; eim.ei.flags = EXTENT_ITEM_TREE_BLOCK; eim.type = TYPE_TREE_BLOCK_REF; eim.tbr.offset = r->id; add_item(extent_root, r->header.address, TYPE_METADATA_ITEM, 0, &eim, sizeof(EXTENT_ITEM_METADATA)); } else { EXTENT_ITEM_METADATA2 eim2; KEY firstitem; if (r->items.Flink == &r->items) { firstitem.obj_id = 0; firstitem.obj_type = 0; firstitem.offset = 0; } else { btrfs_item* bi = CONTAINING_RECORD(r->items.Flink, btrfs_item, list_entry); firstitem = bi->key; } eim2.ei.refcount = 1; eim2.ei.generation = 1; eim2.ei.flags = EXTENT_ITEM_TREE_BLOCK; eim2.ei2.firstitem = firstitem; eim2.ei2.level = 0; eim2.type = TYPE_TREE_BLOCK_REF; eim2.tbr.offset = r->id; add_item(extent_root, r->header.address, TYPE_EXTENT_ITEM, node_size, &eim2, sizeof(EXTENT_ITEM_METADATA2)); } if (r->id != BTRFS_ROOT_ROOT && r->id != BTRFS_ROOT_CHUNK) { ROOT_ITEM ri; memset(&ri, 0, sizeof(ROOT_ITEM)); ri.inode.generation = 1; ri.inode.st_size = 3; ri.inode.st_blocks = node_size; ri.inode.st_nlink = 1; ri.inode.st_mode = 040755; ri.generation = 1; ri.objid = r->id == 5 || r->id >= 0x100 ? SUBVOL_ROOT_INODE : 0; ri.block_number = r->header.address; ri.bytes_used = node_size; ri.num_references = 1; ri.generation2 = ri.generation; add_item(root_root, r->id, TYPE_ROOT_ITEM, 0, &ri, sizeof(ROOT_ITEM)); } le = le->Flink; } } static NTSTATUS write_data(HANDLE h, uint64_t address, btrfs_chunk* c, void* data, ULONG size) { NTSTATUS Status; uint16_t i; IO_STATUS_BLOCK iosb; LARGE_INTEGER off; CHUNK_ITEM_STRIPE* cis; cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; for (i = 0; i < c->chunk_item->num_stripes; i++) { off.QuadPart = cis[i].offset + address - c->offset; Status = NtWriteFile(h, NULL, NULL, NULL, &iosb, data, size, &off, NULL); if (!NT_SUCCESS(Status)) return Status; } return STATUS_SUCCESS; } static void calc_tree_checksum(tree_header* th, uint32_t node_size) { switch (def_csum_type) { case CSUM_TYPE_CRC32C: *(uint32_t*)th = ~calc_crc32c(0xffffffff, (uint8_t*)&th->fs_uuid, node_size - sizeof(th->csum)); break; case CSUM_TYPE_XXHASH: *(uint64_t*)th = XXH64((uint8_t*)&th->fs_uuid, node_size - sizeof(th->csum), 0); break; case CSUM_TYPE_SHA256: calc_sha256((uint8_t*)th, &th->fs_uuid, node_size - sizeof(th->csum)); break; case CSUM_TYPE_BLAKE2: blake2b((uint8_t*)th, BLAKE2_HASH_SIZE, &th->fs_uuid, node_size - sizeof(th->csum)); break; } } static NTSTATUS write_roots(HANDLE h, LIST_ENTRY* roots, uint32_t node_size, BTRFS_UUID* fsuuid, BTRFS_UUID* chunkuuid) { LIST_ENTRY *le, *le2; NTSTATUS Status; uint8_t* tree; tree = malloc(node_size); le = roots->Flink; while (le != roots) { btrfs_root* r = CONTAINING_RECORD(le, btrfs_root, list_entry); uint8_t* dp; leaf_node* ln; memset(tree, 0, node_size); r->header.num_items = 0; r->header.fs_uuid = *fsuuid; r->header.flags = HEADER_FLAG_MIXED_BACKREF | HEADER_FLAG_WRITTEN; r->header.chunk_tree_uuid = *chunkuuid; r->header.generation = 1; r->header.tree_id = r->id; ln = (leaf_node*)(tree + sizeof(tree_header)); dp = tree + node_size; le2 = r->items.Flink; while (le2 != &r->items) { btrfs_item* item = CONTAINING_RECORD(le2, btrfs_item, list_entry); ln->key = item->key; ln->size = item->size; if (item->size > 0) { dp -= item->size; memcpy(dp, item->data, item->size); } ln->offset = (uint32_t)(dp - tree - sizeof(tree_header)); ln = &ln[1]; r->header.num_items++; le2 = le2->Flink; } memcpy(tree, &r->header, sizeof(tree_header)); calc_tree_checksum((tree_header*)tree, node_size); Status = write_data(h, r->header.address, r->c, tree, node_size); if (!NT_SUCCESS(Status)) { free(tree); return Status; } le = le->Flink; } free(tree); return STATUS_SUCCESS; } static void get_uuid(BTRFS_UUID* uuid) { uint8_t i; for (i = 0; i < 16; i+=2) { ULONG r = rand(); uuid->uuid[i] = (r & 0xff00) >> 8; uuid->uuid[i+1] = r & 0xff; } } static void init_device(btrfs_dev* dev, uint64_t id, uint64_t size, BTRFS_UUID* fsuuid, uint32_t sector_size) { dev->dev_item.dev_id = id; dev->dev_item.num_bytes = size; dev->dev_item.bytes_used = 0; dev->dev_item.optimal_io_align = sector_size; dev->dev_item.optimal_io_width = sector_size; dev->dev_item.minimal_io_size = sector_size; dev->dev_item.type = 0; dev->dev_item.generation = 0; dev->dev_item.start_offset = 0; dev->dev_item.dev_group = 0; dev->dev_item.seek_speed = 0; dev->dev_item.bandwidth = 0; get_uuid(&dev->dev_item.device_uuid); dev->dev_item.fs_uuid = *fsuuid; dev->last_alloc = 0x100000; // skip first megabyte } static void calc_superblock_checksum(superblock* sb) { switch (def_csum_type) { case CSUM_TYPE_CRC32C: *(uint32_t*)sb = ~calc_crc32c(0xffffffff, (uint8_t*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum)); break; case CSUM_TYPE_XXHASH: *(uint64_t*)sb = XXH64(&sb->uuid, sizeof(superblock) - sizeof(sb->checksum), 0); break; case CSUM_TYPE_SHA256: calc_sha256((uint8_t*)sb, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum)); break; case CSUM_TYPE_BLAKE2: blake2b((uint8_t*)sb, BLAKE2_HASH_SIZE, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum)); break; } } static NTSTATUS write_superblocks(HANDLE h, btrfs_dev* dev, btrfs_root* chunk_root, btrfs_root* root_root, btrfs_root* extent_root, btrfs_chunk* sys_chunk, uint32_t node_size, BTRFS_UUID* fsuuid, uint32_t sector_size, PUNICODE_STRING label, uint64_t incompat_flags, uint64_t compat_ro_flags) { NTSTATUS Status; IO_STATUS_BLOCK iosb; ULONG sblen; int i; superblock* sb; KEY* key; uint64_t bytes_used; LIST_ENTRY* le; sblen = sizeof(*sb); if (sblen & (sector_size - 1)) sblen = (sblen & sector_size) + sector_size; bytes_used = 0; le = extent_root->items.Flink; while (le != &extent_root->items) { btrfs_item* item = CONTAINING_RECORD(le, btrfs_item, list_entry); if (item->key.obj_type == TYPE_EXTENT_ITEM) bytes_used += item->key.offset; else if (item->key.obj_type == TYPE_METADATA_ITEM) bytes_used += node_size; le = le->Flink; } sb = malloc(sblen); memset(sb, 0, sblen); sb->uuid = *fsuuid; sb->flags = 1; sb->magic = BTRFS_MAGIC; sb->generation = 1; sb->root_tree_addr = root_root->header.address; sb->chunk_tree_addr = chunk_root->header.address; sb->total_bytes = dev->dev_item.num_bytes; sb->bytes_used = bytes_used; sb->root_dir_objectid = BTRFS_ROOT_TREEDIR; sb->num_devices = 1; sb->sector_size = sector_size; sb->node_size = node_size; sb->leaf_size = node_size; sb->stripe_size = sector_size; sb->n = sizeof(KEY) + sizeof(CHUNK_ITEM) + (sys_chunk->chunk_item->num_stripes * sizeof(CHUNK_ITEM_STRIPE)); sb->chunk_root_generation = 1; sb->compat_ro_flags = compat_ro_flags; sb->incompat_flags = incompat_flags; sb->csum_type = def_csum_type; memcpy(&sb->dev_item, &dev->dev_item, sizeof(DEV_ITEM)); if (label->Length > 0) { ULONG utf8len; for (unsigned int i = 0; i < label->Length / sizeof(WCHAR); i++) { if (label->Buffer[i] == '/' || label->Buffer[i] == '\\') { free(sb); return STATUS_INVALID_VOLUME_LABEL; } } utf8len = WideCharToMultiByte(CP_UTF8, 0, label->Buffer, label->Length / sizeof(WCHAR), NULL, 0, NULL, NULL); if (utf8len == 0 || utf8len > MAX_LABEL_SIZE) { free(sb); return STATUS_INVALID_VOLUME_LABEL; } if (WideCharToMultiByte(CP_UTF8, 0, label->Buffer, label->Length / sizeof(WCHAR), sb->label, utf8len, NULL, NULL) == 0) { free(sb); return STATUS_INVALID_VOLUME_LABEL; } } sb->cache_generation = 0xffffffffffffffff; key = (KEY*)sb->sys_chunk_array; key->obj_id = 0x100; key->obj_type = TYPE_CHUNK_ITEM; key->offset = sys_chunk->offset; memcpy(&key[1], sys_chunk->chunk_item, sizeof(CHUNK_ITEM) + (sys_chunk->chunk_item->num_stripes * sizeof(CHUNK_ITEM_STRIPE))); i = 0; while (superblock_addrs[i] != 0) { LARGE_INTEGER off; if (superblock_addrs[i] > dev->dev_item.num_bytes) break; sb->sb_phys_addr = superblock_addrs[i]; calc_superblock_checksum(sb); off.QuadPart = superblock_addrs[i]; Status = NtWriteFile(h, NULL, NULL, NULL, &iosb, sb, sblen, &off, NULL); if (!NT_SUCCESS(Status)) { free(sb); return Status; } i++; } free(sb); return STATUS_SUCCESS; } static __inline void win_time_to_unix(LARGE_INTEGER t, BTRFS_TIME* out) { ULONGLONG l = t.QuadPart - 116444736000000000; out->seconds = l / 10000000; out->nanoseconds = (l % 10000000) * 100; } static void add_inode_ref(btrfs_root* r, uint64_t inode, uint64_t parent, uint64_t index, const char* name) { uint16_t name_len = (uint16_t)strlen(name); INODE_REF* ir = malloc(offsetof(INODE_REF, name[0]) + name_len); ir->index = 0; ir->n = name_len; memcpy(ir->name, name, name_len); add_item(r, inode, TYPE_INODE_REF, parent, ir, (uint16_t)offsetof(INODE_REF, name[0]) + ir->n); free(ir); } static void init_fs_tree(btrfs_root* r, uint32_t node_size) { INODE_ITEM ii; FILETIME filetime; LARGE_INTEGER time; memset(&ii, 0, sizeof(INODE_ITEM)); ii.generation = 1; ii.st_blocks = node_size; ii.st_nlink = 1; ii.st_mode = 040755; GetSystemTimeAsFileTime(&filetime); time.LowPart = filetime.dwLowDateTime; time.HighPart = filetime.dwHighDateTime; win_time_to_unix(time, &ii.st_atime); ii.st_ctime = ii.st_mtime = ii.st_atime; add_item(r, SUBVOL_ROOT_INODE, TYPE_INODE_ITEM, 0, &ii, sizeof(INODE_ITEM)); add_inode_ref(r, SUBVOL_ROOT_INODE, SUBVOL_ROOT_INODE, 0, ".."); } static void add_block_group_items(LIST_ENTRY* chunks, btrfs_root* root) { LIST_ENTRY* le; le = chunks->Flink; while (le != chunks) { btrfs_chunk* c = CONTAINING_RECORD(le, btrfs_chunk, list_entry); BLOCK_GROUP_ITEM bgi; bgi.used = c->used; bgi.chunk_tree = 0x100; bgi.flags = c->chunk_item->type; add_item(root, c->offset, TYPE_BLOCK_GROUP_ITEM, c->chunk_item->size, &bgi, sizeof(BLOCK_GROUP_ITEM)); le = le->Flink; } } static NTSTATUS clear_first_megabyte(HANDLE h) { NTSTATUS Status; IO_STATUS_BLOCK iosb; LARGE_INTEGER zero; uint8_t* mb; mb = malloc(0x100000); memset(mb, 0, 0x100000); zero.QuadPart = 0; Status = NtWriteFile(h, NULL, NULL, NULL, &iosb, mb, 0x100000, &zero, NULL); free(mb); return Status; } static bool is_ssd(HANDLE h) { ULONG aptelen; ATA_PASS_THROUGH_EX* apte; IO_STATUS_BLOCK iosb; NTSTATUS Status; IDENTIFY_DEVICE_DATA* idd; aptelen = sizeof(ATA_PASS_THROUGH_EX) + 512; apte = malloc(aptelen); RtlZeroMemory(apte, aptelen); apte->Length = sizeof(ATA_PASS_THROUGH_EX); apte->AtaFlags = ATA_FLAGS_DATA_IN; apte->DataTransferLength = aptelen - sizeof(ATA_PASS_THROUGH_EX); apte->TimeOutValue = 3; apte->DataBufferOffset = apte->Length; apte->CurrentTaskFile[6] = IDE_COMMAND_IDENTIFY; Status = NtDeviceIoControlFile(h, NULL, NULL, NULL, &iosb, IOCTL_ATA_PASS_THROUGH, apte, aptelen, apte, aptelen); if (NT_SUCCESS(Status)) { idd = (IDENTIFY_DEVICE_DATA*)((uint8_t*)apte + sizeof(ATA_PASS_THROUGH_EX)); if (idd->NominalMediaRotationRate == 1) { free(apte); return true; } } free(apte); return false; } static void add_dir_item(btrfs_root* root, uint64_t inode, uint32_t hash, uint64_t key_objid, uint8_t key_type, uint64_t key_offset, uint64_t transid, uint8_t type, const char* name) { uint16_t name_len = (uint16_t)strlen(name); DIR_ITEM* di = malloc(offsetof(DIR_ITEM, name[0]) + name_len); di->key.obj_id = key_objid; di->key.obj_type = key_type; di->key.offset = key_offset; di->transid = transid; di->m = 0; di->n = name_len; di->type = type; memcpy(di->name, name, name_len); add_item(root, inode, TYPE_DIR_ITEM, hash, di, (uint16_t)(offsetof(DIR_ITEM, name[0]) + di->m + di->n)); free(di); } static void set_default_subvol(btrfs_root* root_root, uint32_t node_size) { INODE_ITEM ii; FILETIME filetime; LARGE_INTEGER time; static const char default_subvol[] = "default"; static const uint32_t default_hash = 0x8dbfc2d2; add_inode_ref(root_root, BTRFS_ROOT_FSTREE, BTRFS_ROOT_TREEDIR, 0, default_subvol); memset(&ii, 0, sizeof(INODE_ITEM)); ii.generation = 1; ii.st_blocks = node_size; ii.st_nlink = 1; ii.st_mode = 040755; GetSystemTimeAsFileTime(&filetime); time.LowPart = filetime.dwLowDateTime; time.HighPart = filetime.dwHighDateTime; win_time_to_unix(time, &ii.st_atime); ii.st_ctime = ii.st_mtime = ii.otime = ii.st_atime; add_item(root_root, BTRFS_ROOT_TREEDIR, TYPE_INODE_ITEM, 0, &ii, sizeof(INODE_ITEM)); add_inode_ref(root_root, BTRFS_ROOT_TREEDIR, BTRFS_ROOT_TREEDIR, 0, ".."); add_dir_item(root_root, BTRFS_ROOT_TREEDIR, default_hash, BTRFS_ROOT_FSTREE, TYPE_ROOT_ITEM, 0xffffffffffffffff, 0, BTRFS_TYPE_DIRECTORY, default_subvol); } static void populate_free_space_root(LIST_ENTRY* chunks, btrfs_root* free_space_root) { LIST_ENTRY* le; le = chunks->Flink; while (le != chunks) { btrfs_chunk* c = CONTAINING_RECORD(le, btrfs_chunk, list_entry); uint64_t last_end = c->offset; LIST_ENTRY* le2; FREE_SPACE_INFO fsi; fsi.count = 0; fsi.flags = 0; le2 = c->used_space.Flink; while (le2 != &c->used_space) { used_space_extent* use = CONTAINING_RECORD(le2, used_space_extent, list_entry); if (use->address > last_end) { add_item(free_space_root, last_end, TYPE_FREE_SPACE_EXTENT, use->address - last_end, NULL, 0); fsi.count++; } last_end = use->address + use->size; le2 = le2->Flink; } if (c->offset + c->chunk_item->size > last_end) { add_item(free_space_root, last_end, TYPE_FREE_SPACE_EXTENT, c->offset + c->chunk_item->size - last_end, NULL, 0); fsi.count++; } add_item(free_space_root, c->offset, TYPE_FREE_SPACE_INFO, c->chunk_item->size, &fsi, sizeof(fsi)); le = le->Flink; } } static NTSTATUS write_btrfs(HANDLE h, uint64_t size, PUNICODE_STRING label, uint32_t sector_size, uint32_t node_size, uint64_t incompat_flags, uint64_t compat_ro_flags) { NTSTATUS Status; LIST_ENTRY roots, chunks; btrfs_root *root_root, *chunk_root, *extent_root, *dev_root, *fs_root, *reloc_root, *block_group_root, *free_space_root; btrfs_chunk *sys_chunk, *metadata_chunk; btrfs_dev dev; BTRFS_UUID fsuuid, chunkuuid; bool ssd; uint64_t metadata_flags; srand((unsigned int)time(0)); get_uuid(&fsuuid); get_uuid(&chunkuuid); InitializeListHead(&roots); InitializeListHead(&chunks); root_root = add_root(&roots, BTRFS_ROOT_ROOT); chunk_root = add_root(&roots, BTRFS_ROOT_CHUNK); extent_root = add_root(&roots, BTRFS_ROOT_EXTENT); dev_root = add_root(&roots, BTRFS_ROOT_DEVTREE); add_root(&roots, BTRFS_ROOT_CHECKSUM); fs_root = add_root(&roots, BTRFS_ROOT_FSTREE); reloc_root = add_root(&roots, BTRFS_ROOT_DATA_RELOC); if (compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE) free_space_root = add_root(&roots, BTRFS_ROOT_FREE_SPACE); else free_space_root = NULL; if (compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE) block_group_root = add_root(&roots, BTRFS_ROOT_BLOCK_GROUP); else block_group_root = NULL; init_device(&dev, 1, size, &fsuuid, sector_size); ssd = is_ssd(h); sys_chunk = add_chunk(&chunks, BLOCK_FLAG_SYSTEM | (ssd ? 0 : BLOCK_FLAG_DUPLICATE), chunk_root, &dev, dev_root, &chunkuuid, sector_size); if (!sys_chunk) return STATUS_INTERNAL_ERROR; metadata_flags = BLOCK_FLAG_METADATA; if (!ssd && !(incompat_flags & BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS)) metadata_flags |= BLOCK_FLAG_DUPLICATE; if (incompat_flags & BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS) metadata_flags |= BLOCK_FLAG_DATA; metadata_chunk = add_chunk(&chunks, metadata_flags, chunk_root, &dev, dev_root, &chunkuuid, sector_size); if (!metadata_chunk) return STATUS_INTERNAL_ERROR; add_item(chunk_root, 1, TYPE_DEV_ITEM, dev.dev_item.dev_id, &dev.dev_item, sizeof(DEV_ITEM)); set_default_subvol(root_root, node_size); init_fs_tree(fs_root, node_size); init_fs_tree(reloc_root, node_size); assign_addresses(&roots, sys_chunk, metadata_chunk, node_size, root_root, extent_root, incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA); add_block_group_items(&chunks, block_group_root ? block_group_root : extent_root); if (free_space_root) populate_free_space_root(&chunks, free_space_root); Status = write_roots(h, &roots, node_size, &fsuuid, &chunkuuid); if (!NT_SUCCESS(Status)) return Status; Status = clear_first_megabyte(h); if (!NT_SUCCESS(Status)) return Status; Status = write_superblocks(h, &dev, chunk_root, root_root, extent_root, sys_chunk, node_size, &fsuuid, sector_size, label, incompat_flags, compat_ro_flags); if (!NT_SUCCESS(Status)) return Status; free_roots(&roots); free_chunks(&chunks); return STATUS_SUCCESS; } static bool look_for_device(btrfs_filesystem* bfs, BTRFS_UUID* devuuid) { uint32_t i; btrfs_filesystem_device* dev; for (i = 0; i < bfs->num_devices; i++) { if (i == 0) dev = &bfs->device; else dev = (btrfs_filesystem_device*)((uint8_t*)dev + offsetof(btrfs_filesystem_device, name[0]) + dev->name_length); if (RtlCompareMemory(&dev->uuid, devuuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) return true; } return false; } static bool check_superblock_checksum(superblock* sb) { switch (sb->csum_type) { case CSUM_TYPE_CRC32C: { uint32_t crc32 = ~calc_crc32c(0xffffffff, (uint8_t*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum)); return crc32 == *(uint32_t*)sb; } case CSUM_TYPE_XXHASH: { uint64_t hash = XXH64(&sb->uuid, sizeof(superblock) - sizeof(sb->checksum), 0); return hash == *(uint64_t*)sb; } case CSUM_TYPE_SHA256: { uint8_t hash[SHA256_HASH_SIZE]; calc_sha256(hash, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum)); return !memcmp(hash, sb, SHA256_HASH_SIZE); } case CSUM_TYPE_BLAKE2: { uint8_t hash[BLAKE2_HASH_SIZE]; blake2b(hash, sizeof(hash), &sb->uuid, sizeof(superblock) - sizeof(sb->checksum)); return !memcmp(hash, sb, BLAKE2_HASH_SIZE); } default: return false; } } static bool is_mounted_multi_device(HANDLE h, uint32_t sector_size) { NTSTATUS Status; superblock* sb; ULONG sblen; IO_STATUS_BLOCK iosb; LARGE_INTEGER off; BTRFS_UUID fsuuid, devuuid; UNICODE_STRING us; OBJECT_ATTRIBUTES atts; HANDLE h2; btrfs_filesystem *bfs = NULL, *bfs2; ULONG bfssize; bool ret = false; static WCHAR btrfs[] = L"\\Btrfs"; sblen = sizeof(*sb); if (sblen & (sector_size - 1)) sblen = (sblen & sector_size) + sector_size; sb = malloc(sblen); off.QuadPart = superblock_addrs[0]; Status = NtReadFile(h, NULL, NULL, NULL, &iosb, sb, sblen, &off, NULL); if (!NT_SUCCESS(Status)) { free(sb); return false; } if (sb->magic != BTRFS_MAGIC) { free(sb); return false; } if (!check_superblock_checksum(sb)) { free(sb); return false; } fsuuid = sb->uuid; devuuid = sb->dev_item.device_uuid; free(sb); us.Length = us.MaximumLength = (USHORT)(wcslen(btrfs) * sizeof(WCHAR)); us.Buffer = btrfs; InitializeObjectAttributes(&atts, &us, 0, NULL, NULL); Status = NtOpenFile(&h2, SYNCHRONIZE | FILE_READ_ATTRIBUTES, &atts, &iosb, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_ALERT); if (!NT_SUCCESS(Status)) // not a problem, it usually just means the driver isn't loaded return false; bfssize = 0; do { bfssize += 1024; if (bfs) free(bfs); bfs = malloc(bfssize); Status = NtDeviceIoControlFile(h2, NULL, NULL, NULL, &iosb, IOCTL_BTRFS_QUERY_FILESYSTEMS, NULL, 0, bfs, bfssize); } while (Status == STATUS_BUFFER_OVERFLOW); if (!NT_SUCCESS(Status)) goto end; if (bfs->num_devices != 0) { bfs2 = bfs; while (true) { if (RtlCompareMemory(&bfs2->uuid, &fsuuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { if (bfs2->num_devices == 1) ret = false; else ret = look_for_device(bfs2, &devuuid); goto end; } if (bfs2->next_entry == 0) break; else bfs2 = (btrfs_filesystem*)((uint8_t*)bfs2 + bfs2->next_entry); } } end: NtClose(h2); if (bfs) free(bfs); return ret; } static void do_full_trim(HANDLE h) { IO_STATUS_BLOCK iosb; DEVICE_MANAGE_DATA_SET_ATTRIBUTES dmdsa; RtlZeroMemory(&dmdsa, sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES)); dmdsa.Size = sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES); dmdsa.Action = DeviceDsmAction_Trim; dmdsa.Flags = DEVICE_DSM_FLAG_ENTIRE_DATA_SET_RANGE | DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED; dmdsa.ParameterBlockOffset = 0; dmdsa.ParameterBlockLength = 0; dmdsa.DataSetRangesOffset = 0; dmdsa.DataSetRangesLength = 0; NtDeviceIoControlFile(h, NULL, NULL, NULL, &iosb, IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES, &dmdsa, sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), NULL, 0); } static bool is_power_of_two(ULONG i) { return ((i != 0) && !(i & (i - 1))); } #if defined(_X86_) || defined(_AMD64_) static void check_cpu() { unsigned int cpuInfo[4]; bool have_sse42; #ifndef _MSC_VER __get_cpuid(1, &cpuInfo[0], &cpuInfo[1], &cpuInfo[2], &cpuInfo[3]); have_sse42 = cpuInfo[2] & bit_SSE4_2; #else __cpuid(cpuInfo, 1); have_sse42 = cpuInfo[2] & (1 << 20); #endif if (have_sse42) calc_crc32c = calc_crc32c_hw; } #endif static NTSTATUS NTAPI FormatEx2(PUNICODE_STRING DriveRoot, FMIFS_MEDIA_FLAG MediaFlag, PUNICODE_STRING Label, BOOLEAN QuickFormat, ULONG ClusterSize, PFMIFSCALLBACK Callback) { NTSTATUS Status; HANDLE h, btrfsh; OBJECT_ATTRIBUTES attr; IO_STATUS_BLOCK iosb; GET_LENGTH_INFORMATION gli; DISK_GEOMETRY dg; uint32_t sector_size, node_size; UNICODE_STRING btrfsus; HANDLE token; TOKEN_PRIVILEGES tp; LUID luid; uint64_t incompat_flags, compat_ro_flags; UNICODE_STRING empty_label; static WCHAR btrfs[] = L"\\Btrfs"; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) return STATUS_PRIVILEGE_NOT_HELD; if (!LookupPrivilegeValueW(NULL, L"SeManageVolumePrivilege", &luid)) { CloseHandle(token); return STATUS_PRIVILEGE_NOT_HELD; } tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) { CloseHandle(token); return STATUS_PRIVILEGE_NOT_HELD; } CloseHandle(token); #if defined(_X86_) || defined(_AMD64_) check_cpu(); #endif if (def_csum_type != CSUM_TYPE_CRC32C && def_csum_type != CSUM_TYPE_XXHASH && def_csum_type != CSUM_TYPE_SHA256 && def_csum_type != CSUM_TYPE_BLAKE2) return STATUS_INVALID_PARAMETER; InitializeObjectAttributes(&attr, DriveRoot, OBJ_CASE_INSENSITIVE, NULL, NULL); Status = NtOpenFile(&h, FILE_GENERIC_READ | FILE_GENERIC_WRITE, &attr, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_ALERT); if (!NT_SUCCESS(Status)) return Status; Status = NtDeviceIoControlFile(h, NULL, NULL, NULL, &iosb, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli)); if (!NT_SUCCESS(Status)) { NtClose(h); return Status; } // MSDN tells us to use IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, but there are // some instances where it fails and IOCTL_DISK_GET_DRIVE_GEOMETRY succeeds - // such as with spanned volumes. Status = NtDeviceIoControlFile(h, NULL, NULL, NULL, &iosb, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &dg, sizeof(dg)); if (!NT_SUCCESS(Status)) { NtClose(h); return Status; } if (def_sector_size == 0) { sector_size = dg.BytesPerSector; if (sector_size == 0x200 || sector_size == 0) sector_size = 0x1000; } else { if (dg.BytesPerSector != 0 && (def_sector_size < dg.BytesPerSector || def_sector_size % dg.BytesPerSector != 0 || !is_power_of_two(def_sector_size / dg.BytesPerSector))) { NtClose(h); return STATUS_INVALID_PARAMETER; } sector_size = def_sector_size; } if (def_node_size == 0) node_size = 0x4000; else { if (def_node_size < sector_size || def_node_size % sector_size != 0 || !is_power_of_two(def_node_size / sector_size)) { NtClose(h); return STATUS_INVALID_PARAMETER; } node_size = def_node_size; } if (Callback) { ULONG pc = 0; Callback(PROGRESS, 0, (PVOID)&pc); } NtFsControlFile(h, NULL, NULL, NULL, &iosb, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0); if (is_mounted_multi_device(h, sector_size)) { Status = STATUS_ACCESS_DENIED; goto end; } do_full_trim(h); incompat_flags = def_incompat_flags; incompat_flags |= BTRFS_INCOMPAT_FLAGS_MIXED_BACKREF | BTRFS_INCOMPAT_FLAGS_BIG_METADATA; compat_ro_flags = def_compat_ro_flags; if (!Label) { empty_label.Buffer = NULL; empty_label.Length = empty_label.MaximumLength = 0; Label = &empty_label; } Status = write_btrfs(h, gli.Length.QuadPart, Label, sector_size, node_size, incompat_flags, compat_ro_flags); NtFsControlFile(h, NULL, NULL, NULL, &iosb, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0); end: NtFsControlFile(h, NULL, NULL, NULL, &iosb, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0); NtClose(h); if (NT_SUCCESS(Status)) { btrfsus.Buffer = btrfs; btrfsus.Length = btrfsus.MaximumLength = (USHORT)(wcslen(btrfs) * sizeof(WCHAR)); InitializeObjectAttributes(&attr, &btrfsus, 0, NULL, NULL); Status = NtOpenFile(&btrfsh, FILE_GENERIC_READ | FILE_GENERIC_WRITE, &attr, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_ALERT); if (NT_SUCCESS(Status)) { MOUNTDEV_NAME* mdn; ULONG mdnsize; mdnsize = (ULONG)(offsetof(MOUNTDEV_NAME, Name[0]) + DriveRoot->Length); mdn = malloc(mdnsize); mdn->NameLength = DriveRoot->Length; memcpy(mdn->Name, DriveRoot->Buffer, DriveRoot->Length); NtDeviceIoControlFile(btrfsh, NULL, NULL, NULL, &iosb, IOCTL_BTRFS_PROBE_VOLUME, mdn, mdnsize, NULL, 0); free(mdn); NtClose(btrfsh); } Status = STATUS_SUCCESS; } if (Callback) { bool success = NT_SUCCESS(Status); Callback(DONE, 0, (PVOID)&success); } return Status; } BOOL __stdcall FormatEx(DSTRING* root, STREAM_MESSAGE* message, options* opts, uint32_t unk1) { UNICODE_STRING DriveRoot, Label; NTSTATUS Status; if (!root || !root->string) return false; DriveRoot.Length = DriveRoot.MaximumLength = (USHORT)(wcslen(root->string) * sizeof(WCHAR)); DriveRoot.Buffer = root->string; if (opts && opts->label && opts->label->string) { Label.Length = Label.MaximumLength = (USHORT)(wcslen(opts->label->string) * sizeof(WCHAR)); Label.Buffer = opts->label->string; } else { Label.Length = Label.MaximumLength = 0; Label.Buffer = NULL; } Status = FormatEx2(&DriveRoot, FMIFS_HARDDISK, &Label, opts && opts->flags & FORMAT_FLAG_QUICK_FORMAT, 0, NULL); return NT_SUCCESS(Status); } void __stdcall SetSizes(ULONG sector, ULONG node) { if (sector != 0) def_sector_size = sector; if (node != 0) def_node_size = node; } void __stdcall SetIncompatFlags(uint64_t incompat_flags) { def_incompat_flags = incompat_flags; } void __stdcall SetCompatROFlags(uint64_t compat_ro_flags) { def_compat_ro_flags = compat_ro_flags; } void __stdcall SetCsumType(uint16_t csum_type) { def_csum_type = csum_type; } BOOL __stdcall GetFilesystemInformation(uint32_t unk1, uint32_t unk2, void* unk3) { // STUB - undocumented return true; } BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) { if (dwReason == DLL_PROCESS_ATTACH) module = (HMODULE)hModule; return true; } ================================================ FILE: src/ubtrfs/ubtrfs.def ================================================ EXPORTS ChkdskEx PRIVATE FormatEx PRIVATE GetFilesystemInformation PRIVATE SetSizes PRIVATE SetIncompatFlags PRIVATE SetCsumType PRIVATE SetCompatROFlags PRIVATE ================================================ FILE: src/ubtrfs/ubtrfs.rc.in ================================================ // Microsoft Visual C++ generated resource script. // #include "@CMAKE_CURRENT_SOURCE_DIR@/src/ubtrfs/resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United Kingdom) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK #pragma code_page(1252) #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x2L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "080904b0" BEGIN VALUE "FileDescription", "Btrfs utility DLL" VALUE "FileVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" VALUE "InternalName", "ubtrfs" VALUE "LegalCopyright", "Copyright (c) Mark Harmstone 2016-24" VALUE "OriginalFilename", "ubtrfs.dll" VALUE "ProductName", "WinBtrfs" VALUE "ProductVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x809, 1200 END END #endif // English (United Kingdom) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: src/volume.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include #include #include #include #include #define IOCTL_VOLUME_IS_DYNAMIC CTL_CODE(IOCTL_VOLUME_BASE, 18, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_VOLUME_POST_ONLINE CTL_CODE(IOCTL_VOLUME_BASE, 25, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) extern PDRIVER_OBJECT drvobj; extern PDEVICE_OBJECT master_devobj; extern PDEVICE_OBJECT busobj; extern ERESOURCE pdo_list_lock; extern LIST_ENTRY pdo_list; extern UNICODE_STRING registry_path; extern tIoUnregisterPlugPlayNotificationEx fIoUnregisterPlugPlayNotificationEx; NTSTATUS vol_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { volume_device_extension* vde = DeviceObject->DeviceExtension; TRACE("(%p, %p)\n", DeviceObject, Irp); if (vde->removing) return STATUS_DEVICE_NOT_READY; Irp->IoStatus.Information = FILE_OPENED; InterlockedIncrement(&vde->open_count); return STATUS_SUCCESS; } void free_vol(volume_device_extension* vde) { PDEVICE_OBJECT pdo; vde->dead = true; if (vde->mounted_device) { device_extension* Vcb = vde->mounted_device->DeviceExtension; Vcb->vde = NULL; } if (vde->name.Buffer) ExFreePool(vde->name.Buffer); ExDeleteResourceLite(&vde->pdode->child_lock); if (vde->pdo->AttachedDevice) IoDetachDevice(vde->pdo); while (!IsListEmpty(&vde->pdode->children)) { volume_child* vc = CONTAINING_RECORD(RemoveHeadList(&vde->pdode->children), volume_child, list_entry); if (vc->notification_entry) { if (fIoUnregisterPlugPlayNotificationEx) fIoUnregisterPlugPlayNotificationEx(vc->notification_entry); else IoUnregisterPlugPlayNotification(vc->notification_entry); } if (vc->pnp_name.Buffer) ExFreePool(vc->pnp_name.Buffer); ExFreePool(vc); } if (no_pnp) ExFreePool(vde->pdode); pdo = vde->pdo; IoDeleteDevice(vde->device); if (!no_pnp) IoDeleteDevice(pdo); } NTSTATUS vol_close(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { volume_device_extension* vde = DeviceObject->DeviceExtension; pdo_device_extension* pdode = vde->pdode; TRACE("(%p, %p)\n", DeviceObject, Irp); Irp->IoStatus.Information = 0; if (vde->dead) return STATUS_SUCCESS; ExAcquireResourceExclusiveLite(&pdo_list_lock, true); if (vde->dead) { ExReleaseResourceLite(&pdo_list_lock); return STATUS_SUCCESS; } ExAcquireResourceSharedLite(&pdode->child_lock, true); if (InterlockedDecrement(&vde->open_count) == 0 && vde->removing) { ExReleaseResourceLite(&pdode->child_lock); free_vol(vde); } else ExReleaseResourceLite(&pdode->child_lock); ExReleaseResourceLite(&pdo_list_lock); return STATUS_SUCCESS; } typedef struct { IO_STATUS_BLOCK iosb; KEVENT Event; } vol_read_context; _Function_class_(IO_COMPLETION_ROUTINE) static NTSTATUS __stdcall vol_read_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) { vol_read_context* context = conptr; UNUSED(DeviceObject); context->iosb = Irp->IoStatus; KeSetEvent(&context->Event, 0, false); return STATUS_MORE_PROCESSING_REQUIRED; } NTSTATUS vol_read(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { volume_device_extension* vde = DeviceObject->DeviceExtension; pdo_device_extension* pdode = vde->pdode; volume_child* vc; NTSTATUS Status; PIRP Irp2; vol_read_context context; PIO_STACK_LOCATION IrpSp, IrpSp2; TRACE("(%p, %p)\n", DeviceObject, Irp); ExAcquireResourceSharedLite(&pdode->child_lock, true); if (IsListEmpty(&pdode->children)) { ExReleaseResourceLite(&pdode->child_lock); Status = STATUS_INVALID_DEVICE_REQUEST; goto end; } vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry); // We can't use IoSkipCurrentIrpStackLocation as the device isn't in our stack Irp2 = IoAllocateIrp(vc->devobj->StackSize, false); if (!Irp2) { ERR("IoAllocateIrp failed\n"); ExReleaseResourceLite(&pdode->child_lock); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } IrpSp = IoGetCurrentIrpStackLocation(Irp); IrpSp2 = IoGetNextIrpStackLocation(Irp2); IrpSp2->MajorFunction = IRP_MJ_READ; IrpSp2->FileObject = vc->fileobj; if (vc->devobj->Flags & DO_BUFFERED_IO) { Irp2->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPool, IrpSp->Parameters.Read.Length, ALLOC_TAG); if (!Irp2->AssociatedIrp.SystemBuffer) { ERR("out of memory\n"); ExReleaseResourceLite(&pdode->child_lock); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } Irp2->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION; Irp2->UserBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); } else if (vc->devobj->Flags & DO_DIRECT_IO) Irp2->MdlAddress = Irp->MdlAddress; else Irp2->UserBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); IrpSp2->Parameters.Read.Length = IrpSp->Parameters.Read.Length; IrpSp2->Parameters.Read.ByteOffset.QuadPart = IrpSp->Parameters.Read.ByteOffset.QuadPart; KeInitializeEvent(&context.Event, NotificationEvent, false); Irp2->UserIosb = &context.iosb; IoSetCompletionRoutine(Irp2, vol_read_completion, &context, true, true, true); Status = IoCallDriver(vc->devobj, Irp2); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL); Status = context.iosb.Status; } ExReleaseResourceLite(&pdode->child_lock); Irp->IoStatus.Information = context.iosb.Information; end: Irp->IoStatus.Status = Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); return Status; } NTSTATUS vol_write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { volume_device_extension* vde = DeviceObject->DeviceExtension; pdo_device_extension* pdode = vde->pdode; volume_child* vc; NTSTATUS Status; PIRP Irp2; vol_read_context context; PIO_STACK_LOCATION IrpSp, IrpSp2; TRACE("(%p, %p)\n", DeviceObject, Irp); ExAcquireResourceSharedLite(&pdode->child_lock, true); if (IsListEmpty(&pdode->children)) { ExReleaseResourceLite(&pdode->child_lock); Status = STATUS_INVALID_DEVICE_REQUEST; goto end; } vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry); if (vc->list_entry.Flink != &pdode->children) { // more than once device ExReleaseResourceLite(&pdode->child_lock); Status = STATUS_ACCESS_DENIED; goto end; } // We can't use IoSkipCurrentIrpStackLocation as the device isn't in our stack Irp2 = IoAllocateIrp(vc->devobj->StackSize, false); if (!Irp2) { ERR("IoAllocateIrp failed\n"); ExReleaseResourceLite(&pdode->child_lock); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } IrpSp = IoGetCurrentIrpStackLocation(Irp); IrpSp2 = IoGetNextIrpStackLocation(Irp2); IrpSp2->MajorFunction = IRP_MJ_WRITE; IrpSp2->FileObject = vc->fileobj; if (vc->devobj->Flags & DO_BUFFERED_IO) { Irp2->AssociatedIrp.SystemBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); Irp2->Flags |= IRP_BUFFERED_IO; Irp2->UserBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); } else if (vc->devobj->Flags & DO_DIRECT_IO) Irp2->MdlAddress = Irp->MdlAddress; else Irp2->UserBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); IrpSp2->Parameters.Write.Length = IrpSp->Parameters.Write.Length; IrpSp2->Parameters.Write.ByteOffset.QuadPart = IrpSp->Parameters.Write.ByteOffset.QuadPart; KeInitializeEvent(&context.Event, NotificationEvent, false); Irp2->UserIosb = &context.iosb; IoSetCompletionRoutine(Irp2, vol_read_completion, &context, true, true, true); Status = IoCallDriver(vc->devobj, Irp2); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL); Status = context.iosb.Status; } ExReleaseResourceLite(&pdode->child_lock); Irp->IoStatus.Information = context.iosb.Information; end: Irp->IoStatus.Status = Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); return Status; } static NTSTATUS vol_query_device_name(volume_device_extension* vde, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PMOUNTDEV_NAME name; if (IrpSp->FileObject && IrpSp->FileObject->FsContext) return STATUS_INVALID_PARAMETER; if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(MOUNTDEV_NAME)) { Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME); return STATUS_BUFFER_TOO_SMALL; } name = Irp->AssociatedIrp.SystemBuffer; name->NameLength = vde->name.Length; if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < offsetof(MOUNTDEV_NAME, Name[0]) + name->NameLength) { Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME); return STATUS_BUFFER_OVERFLOW; } RtlCopyMemory(name->Name, vde->name.Buffer, vde->name.Length); Irp->IoStatus.Information = offsetof(MOUNTDEV_NAME, Name[0]) + name->NameLength; return STATUS_SUCCESS; } static NTSTATUS vol_query_unique_id(volume_device_extension* vde, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); MOUNTDEV_UNIQUE_ID* mduid; pdo_device_extension* pdode; if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(MOUNTDEV_UNIQUE_ID)) { Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID); return STATUS_BUFFER_TOO_SMALL; } mduid = Irp->AssociatedIrp.SystemBuffer; mduid->UniqueIdLength = sizeof(BTRFS_UUID); if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < offsetof(MOUNTDEV_UNIQUE_ID, UniqueId[0]) + mduid->UniqueIdLength) { Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID); return STATUS_BUFFER_OVERFLOW; } if (!vde->pdo) return STATUS_INVALID_PARAMETER; pdode = vde->pdode; RtlCopyMemory(mduid->UniqueId, &pdode->uuid, sizeof(BTRFS_UUID)); Irp->IoStatus.Information = offsetof(MOUNTDEV_UNIQUE_ID, UniqueId[0]) + mduid->UniqueIdLength; return STATUS_SUCCESS; } static NTSTATUS vol_is_dynamic(PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); uint8_t* buf; if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength == 0 || !Irp->AssociatedIrp.SystemBuffer) return STATUS_INVALID_PARAMETER; buf = (uint8_t*)Irp->AssociatedIrp.SystemBuffer; *buf = 1; Irp->IoStatus.Information = 1; return STATUS_SUCCESS; } static NTSTATUS vol_check_verify(volume_device_extension* vde) { pdo_device_extension* pdode = vde->pdode; NTSTATUS Status; LIST_ENTRY* le; ExAcquireResourceSharedLite(&pdode->child_lock, true); le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); Status = dev_ioctl(vc->devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, NULL, 0, false, NULL); if (!NT_SUCCESS(Status)) goto end; le = le->Flink; } Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&pdode->child_lock); return Status; } static NTSTATUS vol_get_disk_extents(volume_device_extension* vde, PIRP Irp) { pdo_device_extension* pdode = vde->pdode; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); LIST_ENTRY* le; ULONG num_extents = 0, i, max_extents = 1; NTSTATUS Status; VOLUME_DISK_EXTENTS *ext, *ext3; if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(VOLUME_DISK_EXTENTS)) return STATUS_BUFFER_TOO_SMALL; ExAcquireResourceSharedLite(&pdode->child_lock, true); le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); VOLUME_DISK_EXTENTS ext2; Status = dev_ioctl(vc->devobj, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0, &ext2, sizeof(VOLUME_DISK_EXTENTS), false, NULL); if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) { ERR("IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS returned %08lx\n", Status); goto end; } num_extents += ext2.NumberOfDiskExtents; if (ext2.NumberOfDiskExtents > max_extents) max_extents = ext2.NumberOfDiskExtents; le = le->Flink; } ext = Irp->AssociatedIrp.SystemBuffer; if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < offsetof(VOLUME_DISK_EXTENTS, Extents[0]) + (num_extents * sizeof(DISK_EXTENT))) { Irp->IoStatus.Information = offsetof(VOLUME_DISK_EXTENTS, Extents[0]); ext->NumberOfDiskExtents = num_extents; Status = STATUS_BUFFER_OVERFLOW; goto end; } ext3 = ExAllocatePoolWithTag(PagedPool, offsetof(VOLUME_DISK_EXTENTS, Extents[0]) + (max_extents * sizeof(DISK_EXTENT)), ALLOC_TAG); if (!ext3) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } i = 0; ext->NumberOfDiskExtents = 0; le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); Status = dev_ioctl(vc->devobj, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0, ext3, (ULONG)offsetof(VOLUME_DISK_EXTENTS, Extents[0]) + (max_extents * sizeof(DISK_EXTENT)), false, NULL); if (!NT_SUCCESS(Status)) { ERR("IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS returned %08lx\n", Status); ExFreePool(ext3); goto end; } if (i + ext3->NumberOfDiskExtents > num_extents) { Irp->IoStatus.Information = offsetof(VOLUME_DISK_EXTENTS, Extents[0]); ext->NumberOfDiskExtents = i + ext3->NumberOfDiskExtents; Status = STATUS_BUFFER_OVERFLOW; ExFreePool(ext3); goto end; } RtlCopyMemory(&ext->Extents[i], ext3->Extents, sizeof(DISK_EXTENT) * ext3->NumberOfDiskExtents); i += ext3->NumberOfDiskExtents; le = le->Flink; } ExFreePool(ext3); Status = STATUS_SUCCESS; ext->NumberOfDiskExtents = i; Irp->IoStatus.Information = offsetof(VOLUME_DISK_EXTENTS, Extents[0]) + (i * sizeof(DISK_EXTENT)); end: ExReleaseResourceLite(&pdode->child_lock); return Status; } static NTSTATUS vol_is_writable(volume_device_extension* vde) { pdo_device_extension* pdode = vde->pdode; NTSTATUS Status; LIST_ENTRY* le; bool writable = false; ExAcquireResourceSharedLite(&pdode->child_lock, true); le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); Status = dev_ioctl(vc->devobj, IOCTL_DISK_IS_WRITABLE, NULL, 0, NULL, 0, true, NULL); if (NT_SUCCESS(Status)) { writable = true; break; } else if (Status != STATUS_MEDIA_WRITE_PROTECTED) goto end; le = le->Flink; } Status = writable ? STATUS_SUCCESS : STATUS_MEDIA_WRITE_PROTECTED; end: ExReleaseResourceLite(&pdode->child_lock); return STATUS_SUCCESS; } static NTSTATUS vol_get_length(volume_device_extension* vde, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); pdo_device_extension* pdode = vde->pdode; GET_LENGTH_INFORMATION* gli; LIST_ENTRY* le; if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(GET_LENGTH_INFORMATION)) return STATUS_BUFFER_TOO_SMALL; gli = (GET_LENGTH_INFORMATION*)Irp->AssociatedIrp.SystemBuffer; gli->Length.QuadPart = 0; ExAcquireResourceSharedLite(&pdode->child_lock, true); le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); gli->Length.QuadPart += vc->size; le = le->Flink; } ExReleaseResourceLite(&pdode->child_lock); Irp->IoStatus.Information = sizeof(GET_LENGTH_INFORMATION); return STATUS_SUCCESS; } static NTSTATUS vol_get_drive_geometry(PDEVICE_OBJECT DeviceObject, PIRP Irp) { volume_device_extension* vde = DeviceObject->DeviceExtension; pdo_device_extension* pdode = vde->pdode; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); DISK_GEOMETRY* geom; uint64_t length; LIST_ENTRY* le; if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(DISK_GEOMETRY)) return STATUS_BUFFER_TOO_SMALL; length = 0; ExAcquireResourceSharedLite(&pdode->child_lock, true); le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); length += vc->size; le = le->Flink; } ExReleaseResourceLite(&pdode->child_lock); geom = (DISK_GEOMETRY*)Irp->AssociatedIrp.SystemBuffer; geom->BytesPerSector = DeviceObject->SectorSize == 0 ? 0x200 : DeviceObject->SectorSize; geom->SectorsPerTrack = 0x3f; geom->TracksPerCylinder = 0xff; geom->Cylinders.QuadPart = length / (UInt32x32To64(geom->TracksPerCylinder, geom->SectorsPerTrack) * geom->BytesPerSector); geom->MediaType = DeviceObject->Characteristics & FILE_REMOVABLE_MEDIA ? RemovableMedia : FixedMedia; Irp->IoStatus.Information = sizeof(DISK_GEOMETRY); return STATUS_SUCCESS; } static NTSTATUS vol_get_gpt_attributes(PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); VOLUME_GET_GPT_ATTRIBUTES_INFORMATION* vggai; if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(VOLUME_GET_GPT_ATTRIBUTES_INFORMATION)) return STATUS_BUFFER_TOO_SMALL; vggai = (VOLUME_GET_GPT_ATTRIBUTES_INFORMATION*)Irp->AssociatedIrp.SystemBuffer; vggai->GptAttributes = 0; Irp->IoStatus.Information = sizeof(VOLUME_GET_GPT_ATTRIBUTES_INFORMATION); return STATUS_SUCCESS; } static NTSTATUS vol_get_device_number(volume_device_extension* vde, PIRP Irp) { pdo_device_extension* pdode = vde->pdode; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); volume_child* vc; STORAGE_DEVICE_NUMBER* sdn; // If only one device, return its disk number. This is needed for ejection to work. if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_DEVICE_NUMBER)) return STATUS_BUFFER_TOO_SMALL; ExAcquireResourceSharedLite(&pdode->child_lock, true); if (IsListEmpty(&pdode->children) || pdode->num_children > 1) { ExReleaseResourceLite(&pdode->child_lock); return STATUS_INVALID_DEVICE_REQUEST; } vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry); if (vc->disk_num == 0xffffffff) { ExReleaseResourceLite(&pdode->child_lock); return STATUS_INVALID_DEVICE_REQUEST; } sdn = (STORAGE_DEVICE_NUMBER*)Irp->AssociatedIrp.SystemBuffer; sdn->DeviceType = FILE_DEVICE_DISK; sdn->DeviceNumber = vc->disk_num; sdn->PartitionNumber = vc->part_num; ExReleaseResourceLite(&pdode->child_lock); Irp->IoStatus.Information = sizeof(STORAGE_DEVICE_NUMBER); return STATUS_SUCCESS; } _Function_class_(IO_COMPLETION_ROUTINE) static NTSTATUS __stdcall vol_ioctl_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) { KEVENT* event = conptr; UNUSED(DeviceObject); UNUSED(Irp); KeSetEvent(event, 0, false); return STATUS_MORE_PROCESSING_REQUIRED; } static NTSTATUS vol_ioctl_passthrough(volume_device_extension* vde, PIRP Irp) { NTSTATUS Status; volume_child* vc; PIRP Irp2; PIO_STACK_LOCATION IrpSp, IrpSp2; KEVENT Event; pdo_device_extension* pdode = vde->pdode; TRACE("(%p, %p)\n", vde, Irp); ExAcquireResourceSharedLite(&pdode->child_lock, true); if (IsListEmpty(&pdode->children)) { ExReleaseResourceLite(&pdode->child_lock); return STATUS_INVALID_DEVICE_REQUEST; } vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry); if (vc->list_entry.Flink != &pdode->children) { // more than one device ExReleaseResourceLite(&pdode->child_lock); return STATUS_INVALID_DEVICE_REQUEST; } Irp2 = IoAllocateIrp(vc->devobj->StackSize, false); if (!Irp2) { ERR("IoAllocateIrp failed\n"); ExReleaseResourceLite(&pdode->child_lock); return STATUS_INSUFFICIENT_RESOURCES; } IrpSp = IoGetCurrentIrpStackLocation(Irp); IrpSp2 = IoGetNextIrpStackLocation(Irp2); IrpSp2->MajorFunction = IrpSp->MajorFunction; IrpSp2->MinorFunction = IrpSp->MinorFunction; IrpSp2->FileObject = vc->fileobj; IrpSp2->Parameters.DeviceIoControl.OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength; IrpSp2->Parameters.DeviceIoControl.InputBufferLength = IrpSp->Parameters.DeviceIoControl.InputBufferLength; IrpSp2->Parameters.DeviceIoControl.IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode; IrpSp2->Parameters.DeviceIoControl.Type3InputBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer; Irp2->AssociatedIrp.SystemBuffer = Irp->AssociatedIrp.SystemBuffer; Irp2->MdlAddress = Irp->MdlAddress; Irp2->UserBuffer = Irp->UserBuffer; Irp2->Flags = Irp->Flags; KeInitializeEvent(&Event, NotificationEvent, false); IoSetCompletionRoutine(Irp2, vol_ioctl_completion, &Event, true, true, true); Status = IoCallDriver(vc->devobj, Irp2); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&Event, Executive, KernelMode, false, NULL); Status = Irp2->IoStatus.Status; } Irp->IoStatus.Status = Irp2->IoStatus.Status; Irp->IoStatus.Information = Irp2->IoStatus.Information; ExReleaseResourceLite(&pdode->child_lock); IoFreeIrp(Irp2); return Status; } static NTSTATUS vol_query_stable_guid(volume_device_extension* vde, PIRP Irp) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); MOUNTDEV_STABLE_GUID* mdsg; pdo_device_extension* pdode; if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(MOUNTDEV_STABLE_GUID)) { Irp->IoStatus.Information = sizeof(MOUNTDEV_STABLE_GUID); return STATUS_BUFFER_TOO_SMALL; } mdsg = Irp->AssociatedIrp.SystemBuffer; if (!vde->pdo) return STATUS_INVALID_PARAMETER; pdode = vde->pdode; RtlCopyMemory(&mdsg->StableGuid, &pdode->uuid, sizeof(BTRFS_UUID)); Irp->IoStatus.Information = sizeof(MOUNTDEV_STABLE_GUID); return STATUS_SUCCESS; } NTSTATUS vol_device_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { volume_device_extension* vde = DeviceObject->DeviceExtension; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); TRACE("(%p, %p)\n", DeviceObject, Irp); Irp->IoStatus.Information = 0; switch (IrpSp->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_MOUNTDEV_QUERY_DEVICE_NAME: return vol_query_device_name(vde, Irp); case IOCTL_MOUNTDEV_QUERY_UNIQUE_ID: return vol_query_unique_id(vde, Irp); case IOCTL_STORAGE_GET_DEVICE_NUMBER: return vol_get_device_number(vde, Irp); case IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME: TRACE("unhandled control code IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME\n"); break; case IOCTL_MOUNTDEV_QUERY_STABLE_GUID: return vol_query_stable_guid(vde, Irp); case IOCTL_MOUNTDEV_LINK_CREATED: TRACE("unhandled control code IOCTL_MOUNTDEV_LINK_CREATED\n"); break; case IOCTL_VOLUME_GET_GPT_ATTRIBUTES: return vol_get_gpt_attributes(Irp); case IOCTL_VOLUME_IS_DYNAMIC: return vol_is_dynamic(Irp); case IOCTL_VOLUME_ONLINE: Irp->IoStatus.Information = 0; return STATUS_SUCCESS; case IOCTL_VOLUME_POST_ONLINE: Irp->IoStatus.Information = 0; return STATUS_SUCCESS; case IOCTL_DISK_GET_DRIVE_GEOMETRY: return vol_get_drive_geometry(DeviceObject, Irp); case IOCTL_DISK_IS_WRITABLE: return vol_is_writable(vde); case IOCTL_DISK_GET_LENGTH_INFO: return vol_get_length(vde, Irp); case IOCTL_STORAGE_CHECK_VERIFY: case IOCTL_DISK_CHECK_VERIFY: return vol_check_verify(vde); case IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS: return vol_get_disk_extents(vde, Irp); default: { // pass ioctl through if only one child device NTSTATUS Status = vol_ioctl_passthrough(vde, Irp); #ifdef _DEBUG ULONG code = IrpSp->Parameters.DeviceIoControl.IoControlCode; if (NT_SUCCESS(Status)) TRACE("passing through ioctl %lx (returning %08lx)\n", code, Status); else WARN("passing through ioctl %lx (returning %08lx)\n", code, Status); #endif return Status; } } return STATUS_INVALID_DEVICE_REQUEST; } NTSTATUS mountmgr_add_drive_letter(PDEVICE_OBJECT mountmgr, PUNICODE_STRING devpath) { NTSTATUS Status; ULONG mmdltsize; MOUNTMGR_DRIVE_LETTER_TARGET* mmdlt; MOUNTMGR_DRIVE_LETTER_INFORMATION mmdli; mmdltsize = (ULONG)offsetof(MOUNTMGR_DRIVE_LETTER_TARGET, DeviceName[0]) + devpath->Length; mmdlt = ExAllocatePoolWithTag(NonPagedPool, mmdltsize, ALLOC_TAG); if (!mmdlt) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } mmdlt->DeviceNameLength = devpath->Length; RtlCopyMemory(&mmdlt->DeviceName, devpath->Buffer, devpath->Length); TRACE("mmdlt = %.*S\n", (int)(mmdlt->DeviceNameLength / sizeof(WCHAR)), mmdlt->DeviceName); Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_NEXT_DRIVE_LETTER, mmdlt, mmdltsize, &mmdli, sizeof(MOUNTMGR_DRIVE_LETTER_INFORMATION), false, NULL); if (!NT_SUCCESS(Status)) ERR("IOCTL_MOUNTMGR_NEXT_DRIVE_LETTER returned %08lx\n", Status); else TRACE("DriveLetterWasAssigned = %u, CurrentDriveLetter = %c\n", mmdli.DriveLetterWasAssigned, mmdli.CurrentDriveLetter); ExFreePool(mmdlt); return Status; } _Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE) NTSTATUS __stdcall pnp_removal(PVOID NotificationStructure, PVOID Context) { TARGET_DEVICE_REMOVAL_NOTIFICATION* tdrn = (TARGET_DEVICE_REMOVAL_NOTIFICATION*)NotificationStructure; pdo_device_extension* pdode = (pdo_device_extension*)Context; if (RtlCompareMemory(&tdrn->Event, &GUID_TARGET_DEVICE_QUERY_REMOVE, sizeof(GUID)) == sizeof(GUID)) { TRACE("GUID_TARGET_DEVICE_QUERY_REMOVE\n"); if (pdode->vde && pdode->vde->mounted_device) pnp_query_remove_device(pdode->vde->mounted_device, NULL); } return STATUS_SUCCESS; } static bool allow_degraded_mount(BTRFS_UUID* uuid) { HANDLE h; NTSTATUS Status; OBJECT_ATTRIBUTES oa; UNICODE_STRING path, adus; uint32_t degraded = mount_allow_degraded; ULONG i, j, kvfilen, retlen; KEY_VALUE_FULL_INFORMATION* kvfi; path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR)); path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG); if (!path.Buffer) { ERR("out of memory\n"); return false; } RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length); i = registry_path.Length / sizeof(WCHAR); path.Buffer[i] = '\\'; i++; for (j = 0; j < 16; j++) { path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4); path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF); i += 2; if (j == 3 || j == 5 || j == 7 || j == 9) { path.Buffer[i] = '-'; i++; } } InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); kvfilen = (ULONG)offsetof(KEY_VALUE_FULL_INFORMATION, Name[0]) + (255 * sizeof(WCHAR)); kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); ExFreePool(path.Buffer); return false; } Status = ZwOpenKey(&h, KEY_QUERY_VALUE, &oa); if (Status == STATUS_OBJECT_NAME_NOT_FOUND) goto end; else if (!NT_SUCCESS(Status)) { ERR("ZwOpenKey returned %08lx\n", Status); goto end; } adus.Buffer = L"AllowDegraded"; adus.Length = adus.MaximumLength = sizeof(adus.Buffer) - sizeof(WCHAR); if (NT_SUCCESS(ZwQueryValueKey(h, &adus, KeyValueFullInformation, kvfi, kvfilen, &retlen))) { if (kvfi->Type == REG_DWORD && kvfi->DataLength >= sizeof(uint32_t)) { uint32_t* val = (uint32_t*)((uint8_t*)kvfi + kvfi->DataOffset); degraded = *val; } } ZwClose(h); end: ExFreePool(kvfi); ExFreePool(path.Buffer); return degraded; } typedef struct { LIST_ENTRY list_entry; UNICODE_STRING name; NTSTATUS Status; BTRFS_UUID uuid; } drive_letter_removal; static void drive_letter_callback2(pdo_device_extension* pdode, PDEVICE_OBJECT mountmgr) { LIST_ENTRY* le; LIST_ENTRY dlrlist; InitializeListHead(&dlrlist); ExAcquireResourceExclusiveLite(&pdode->child_lock, true); le = pdode->children.Flink; while (le != &pdode->children) { drive_letter_removal* dlr; volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); dlr = ExAllocatePoolWithTag(PagedPool, sizeof(drive_letter_removal), ALLOC_TAG); if (!dlr) { ERR("out of memory\n"); while (!IsListEmpty(&dlrlist)) { dlr = CONTAINING_RECORD(RemoveHeadList(&dlrlist), drive_letter_removal, list_entry); ExFreePool(dlr->name.Buffer); ExFreePool(dlr); } ExReleaseResourceLite(&pdode->child_lock); return; } dlr->name.Length = dlr->name.MaximumLength = vc->pnp_name.Length + (3 * sizeof(WCHAR)); dlr->name.Buffer = ExAllocatePoolWithTag(PagedPool, dlr->name.Length, ALLOC_TAG); if (!dlr->name.Buffer) { ERR("out of memory\n"); ExFreePool(dlr); while (!IsListEmpty(&dlrlist)) { dlr = CONTAINING_RECORD(RemoveHeadList(&dlrlist), drive_letter_removal, list_entry); ExFreePool(dlr->name.Buffer); ExFreePool(dlr); } ExReleaseResourceLite(&pdode->child_lock); return; } RtlCopyMemory(dlr->name.Buffer, L"\\??", 3 * sizeof(WCHAR)); RtlCopyMemory(&dlr->name.Buffer[3], vc->pnp_name.Buffer, vc->pnp_name.Length); dlr->uuid = vc->uuid; InsertTailList(&dlrlist, &dlr->list_entry); le = le->Flink; } ExReleaseResourceLite(&pdode->child_lock); le = dlrlist.Flink; while (le != &dlrlist) { drive_letter_removal* dlr = CONTAINING_RECORD(le, drive_letter_removal, list_entry); dlr->Status = remove_drive_letter(mountmgr, &dlr->name); if (!NT_SUCCESS(dlr->Status) && dlr->Status != STATUS_NOT_FOUND) WARN("remove_drive_letter returned %08lx\n", dlr->Status); le = le->Flink; } // set vc->had_drive_letter ExAcquireResourceExclusiveLite(&pdode->child_lock, true); while (!IsListEmpty(&dlrlist)) { drive_letter_removal* dlr = CONTAINING_RECORD(RemoveHeadList(&dlrlist), drive_letter_removal, list_entry); le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry); if (RtlCompareMemory(&vc->uuid, &dlr->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { vc->had_drive_letter = NT_SUCCESS(dlr->Status); break; } le = le->Flink; } ExFreePool(dlr->name.Buffer); ExFreePool(dlr); } ExReleaseResourceLite(&pdode->child_lock); } _Function_class_(IO_WORKITEM_ROUTINE) static void __stdcall drive_letter_callback(pdo_device_extension* pdode) { NTSTATUS Status; UNICODE_STRING mmdevpath; PDEVICE_OBJECT mountmgr; PFILE_OBJECT mountmgrfo; RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME); Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &mountmgrfo, &mountmgr); if (!NT_SUCCESS(Status)) { ERR("IoGetDeviceObjectPointer returned %08lx\n", Status); return; } drive_letter_callback2(pdode, mountmgr); ObDereferenceObject(mountmgrfo); } void add_volume_device(superblock* sb, PUNICODE_STRING devpath, uint64_t length, ULONG disk_num, ULONG part_num) { NTSTATUS Status; LIST_ENTRY* le; PDEVICE_OBJECT DeviceObject; volume_child* vc; PFILE_OBJECT FileObject; UNICODE_STRING devpath2; bool inserted = false, new_pdo = false; pdo_device_extension* pdode = NULL; PDEVICE_OBJECT pdo = NULL; bool process_drive_letters = false; if (devpath->Length == 0) return; ExAcquireResourceExclusiveLite(&pdo_list_lock, true); le = pdo_list.Flink; while (le != &pdo_list) { pdo_device_extension* pdode2 = CONTAINING_RECORD(le, pdo_device_extension, list_entry); if (RtlCompareMemory(&pdode2->uuid, &sb->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { pdode = pdode2; break; } le = le->Flink; } Status = IoGetDeviceObjectPointer(devpath, FILE_READ_ATTRIBUTES, &FileObject, &DeviceObject); if (!NT_SUCCESS(Status)) { ERR("IoGetDeviceObjectPointer returned %08lx\n", Status); ExReleaseResourceLite(&pdo_list_lock); return; } if (!pdode) { if (no_pnp) { Status = IoReportDetectedDevice(drvobj, InterfaceTypeUndefined, 0xFFFFFFFF, 0xFFFFFFFF, NULL, NULL, 0, &pdo); if (!NT_SUCCESS(Status)) { ERR("IoReportDetectedDevice returned %08lx\n", Status); ExReleaseResourceLite(&pdo_list_lock); return; } pdode = ExAllocatePoolWithTag(NonPagedPool, sizeof(pdo_device_extension), ALLOC_TAG); if (!pdode) { ERR("out of memory\n"); ExReleaseResourceLite(&pdo_list_lock); return; } } else { Status = IoCreateDevice(drvobj, sizeof(pdo_device_extension), NULL, FILE_DEVICE_DISK, FILE_AUTOGENERATED_DEVICE_NAME | FILE_DEVICE_SECURE_OPEN, false, &pdo); if (!NT_SUCCESS(Status)) { ERR("IoCreateDevice returned %08lx\n", Status); ExReleaseResourceLite(&pdo_list_lock); goto fail; } pdo->Flags |= DO_BUS_ENUMERATED_DEVICE; pdode = pdo->DeviceExtension; } RtlZeroMemory(pdode, sizeof(pdo_device_extension)); pdode->type = VCB_TYPE_PDO; pdode->pdo = pdo; pdode->uuid = sb->uuid; ExInitializeResourceLite(&pdode->child_lock); InitializeListHead(&pdode->children); pdode->num_children = sb->num_devices; pdode->children_loaded = 0; pdo->Flags &= ~DO_DEVICE_INITIALIZING; pdo->SectorSize = (USHORT)sb->sector_size; ExAcquireResourceExclusiveLite(&pdode->child_lock, true); new_pdo = true; } else { ExAcquireResourceExclusiveLite(&pdode->child_lock, true); ExConvertExclusiveToSharedLite(&pdo_list_lock); le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry); if (RtlCompareMemory(&vc2->uuid, &sb->dev_item.device_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { // duplicate, ignore ExReleaseResourceLite(&pdode->child_lock); ExReleaseResourceLite(&pdo_list_lock); goto fail; } le = le->Flink; } } vc = ExAllocatePoolWithTag(PagedPool, sizeof(volume_child), ALLOC_TAG); if (!vc) { ERR("out of memory\n"); ExReleaseResourceLite(&pdode->child_lock); ExReleaseResourceLite(&pdo_list_lock); goto fail; } vc->uuid = sb->dev_item.device_uuid; vc->devid = sb->dev_item.dev_id; vc->generation = sb->generation; vc->notification_entry = NULL; vc->boot_volume = false; Status = IoRegisterPlugPlayNotification(EventCategoryTargetDeviceChange, 0, FileObject, drvobj, pnp_removal, pdode, &vc->notification_entry); if (!NT_SUCCESS(Status)) WARN("IoRegisterPlugPlayNotification returned %08lx\n", Status); vc->devobj = DeviceObject; vc->fileobj = FileObject; devpath2 = *devpath; // The PNP path sometimes begins \\?\ and sometimes \??\. We need to remove this prefix // so we can compare properly if the device is removed. if (devpath->Length > 4 * sizeof(WCHAR) && devpath->Buffer[0] == '\\' && (devpath->Buffer[1] == '\\' || devpath->Buffer[1] == '?') && devpath->Buffer[2] == '?' && devpath->Buffer[3] == '\\') { devpath2.Buffer = &devpath2.Buffer[3]; devpath2.Length -= 3 * sizeof(WCHAR); devpath2.MaximumLength -= 3 * sizeof(WCHAR); } vc->pnp_name.Length = vc->pnp_name.MaximumLength = devpath2.Length; vc->pnp_name.Buffer = ExAllocatePoolWithTag(PagedPool, devpath2.Length, ALLOC_TAG); if (vc->pnp_name.Buffer) RtlCopyMemory(vc->pnp_name.Buffer, devpath2.Buffer, devpath2.Length); else { ERR("out of memory\n"); vc->pnp_name.Length = vc->pnp_name.MaximumLength = 0; } vc->size = length; vc->seeding = sb->flags & BTRFS_SUPERBLOCK_FLAGS_SEEDING ? true : false; vc->disk_num = disk_num; vc->part_num = part_num; vc->had_drive_letter = false; le = pdode->children.Flink; while (le != &pdode->children) { volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry); if (vc2->generation < vc->generation) { if (le == pdode->children.Flink) pdode->num_children = sb->num_devices; InsertHeadList(vc2->list_entry.Blink, &vc->list_entry); inserted = true; break; } le = le->Flink; } if (!inserted) InsertTailList(&pdode->children, &vc->list_entry); pdode->children_loaded++; if (pdode->vde && pdode->vde->mounted_device) { device_extension* Vcb = pdode->vde->mounted_device->DeviceExtension; ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true); le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (!dev->devobj && RtlCompareMemory(&dev->devitem.device_uuid, &sb->dev_item.device_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { dev->devobj = DeviceObject; dev->disk_num = disk_num; dev->part_num = part_num; init_device(Vcb, dev, false); break; } le = le->Flink; } ExReleaseResourceLite(&Vcb->tree_lock); } if (DeviceObject->Characteristics & FILE_REMOVABLE_MEDIA) { pdode->removable = true; if (pdode->vde && pdode->vde->device) pdode->vde->device->Characteristics |= FILE_REMOVABLE_MEDIA; } if (pdode->num_children == pdode->children_loaded || (pdode->children_loaded == 1 && allow_degraded_mount(&sb->uuid))) { if ((!new_pdo || !no_pnp) && pdode->vde) { Status = IoSetDeviceInterfaceState(&pdode->vde->bus_name, true); if (!NT_SUCCESS(Status)) WARN("IoSetDeviceInterfaceState returned %08lx\n", Status); } process_drive_letters = true; } ExReleaseResourceLite(&pdode->child_lock); if (new_pdo) InsertTailList(&pdo_list, &pdode->list_entry); ExReleaseResourceLite(&pdo_list_lock); if (process_drive_letters) drive_letter_callback(pdode); if (new_pdo) { if (RtlCompareMemory(&sb->uuid, &boot_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) boot_add_device(pdo); else if (no_pnp) AddDevice(drvobj, pdo); else { bus_device_extension* bde = busobj->DeviceExtension; IoInvalidateDeviceRelations(bde->buspdo, BusRelations); } } return; fail: ObDereferenceObject(FileObject); } ================================================ FILE: src/worker-thread.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" typedef struct { device_extension* Vcb; PIRP Irp; WORK_QUEUE_ITEM item; } job_info; NTSTATUS do_read_job(PIRP Irp) { NTSTATUS Status; ULONG bytes_read; bool top_level = is_top_level(Irp); PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; fcb* fcb = FileObject->FsContext; bool acquired_fcb_lock = false; Irp->IoStatus.Information = 0; if (!ExIsResourceAcquiredSharedLite(fcb->Header.Resource)) { ExAcquireResourceSharedLite(fcb->Header.Resource, true); acquired_fcb_lock = true; } try { Status = do_read(Irp, true, &bytes_read); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (acquired_fcb_lock) ExReleaseResourceLite(fcb->Header.Resource); if (!NT_SUCCESS(Status)) ERR("do_read returned %08lx\n", Status); Irp->IoStatus.Status = Status; TRACE("read %Iu bytes\n", Irp->IoStatus.Information); IoCompleteRequest(Irp, IO_NO_INCREMENT); if (top_level) IoSetTopLevelIrp(NULL); TRACE("returning %08lx\n", Status); return Status; } NTSTATUS do_write_job(device_extension* Vcb, PIRP Irp) { bool top_level = is_top_level(Irp); NTSTATUS Status; try { Status = write_file(Vcb, Irp, true, true); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) ERR("write_file returned %08lx\n", Status); Irp->IoStatus.Status = Status; TRACE("wrote %Iu bytes\n", Irp->IoStatus.Information); IoCompleteRequest(Irp, IO_NO_INCREMENT); if (top_level) IoSetTopLevelIrp(NULL); TRACE("returning %08lx\n", Status); return Status; } _Function_class_(WORKER_THREAD_ROUTINE) static void __stdcall do_job(void* context) { job_info* ji = context; PIO_STACK_LOCATION IrpSp = ji->Irp ? IoGetCurrentIrpStackLocation(ji->Irp) : NULL; if (IrpSp->MajorFunction == IRP_MJ_READ) { do_read_job(ji->Irp); } else if (IrpSp->MajorFunction == IRP_MJ_WRITE) { do_write_job(ji->Vcb, ji->Irp); } ExFreePool(ji); } bool add_thread_job(device_extension* Vcb, PIRP Irp) { job_info* ji; ji = ExAllocatePoolWithTag(NonPagedPool, sizeof(job_info), ALLOC_TAG); if (!ji) { ERR("out of memory\n"); return false; } ji->Vcb = Vcb; ji->Irp = Irp; if (!Irp->MdlAddress) { PMDL Mdl; LOCK_OPERATION op; ULONG len; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); if (IrpSp->MajorFunction == IRP_MJ_READ) { op = IoWriteAccess; len = IrpSp->Parameters.Read.Length; } else if (IrpSp->MajorFunction == IRP_MJ_WRITE) { op = IoReadAccess; len = IrpSp->Parameters.Write.Length; } else { ERR("unexpected major function %u\n", IrpSp->MajorFunction); ExFreePool(ji); return false; } Mdl = IoAllocateMdl(Irp->UserBuffer, len, false, false, Irp); if (!Mdl) { ERR("out of memory\n"); ExFreePool(ji); return false; } try { MmProbeAndLockPages(Mdl, Irp->RequestorMode, op); } except(EXCEPTION_EXECUTE_HANDLER) { ERR("MmProbeAndLockPages raised status %08lx\n", GetExceptionCode()); IoFreeMdl(Mdl); Irp->MdlAddress = NULL; ExFreePool(ji); return false; } } ExInitializeWorkItem(&ji->item, do_job, ji); ExQueueWorkItem(&ji->item, DelayedWorkQueue); return true; } ================================================ FILE: src/write.c ================================================ /* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" typedef struct { uint64_t start; uint64_t end; uint8_t* data; PMDL mdl; uint64_t irp_offset; } write_stripe; _Function_class_(IO_COMPLETION_ROUTINE) static NTSTATUS __stdcall write_data_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr); static void remove_fcb_extent(fcb* fcb, extent* ext, LIST_ENTRY* rollback) __attribute__((nonnull(1, 2, 3))); extern tPsUpdateDiskCounters fPsUpdateDiskCounters; extern tCcCopyWriteEx fCcCopyWriteEx; extern tFsRtlUpdateDiskCounters fFsRtlUpdateDiskCounters; extern bool diskacc; __attribute__((nonnull(1, 2, 4))) bool find_data_address_in_chunk(device_extension* Vcb, chunk* c, uint64_t length, uint64_t* address) { LIST_ENTRY* le; space* s; TRACE("(%p, %I64x, %I64x, %p)\n", Vcb, c->offset, length, address); if (length > c->chunk_item->size - c->used) return false; if (!c->cache_loaded) { NTSTATUS Status = load_cache_chunk(Vcb, c, NULL); if (!NT_SUCCESS(Status)) { ERR("load_cache_chunk returned %08lx\n", Status); return false; } } if (IsListEmpty(&c->space_size)) return false; le = c->space_size.Flink; while (le != &c->space_size) { s = CONTAINING_RECORD(le, space, list_entry_size); if (s->size == length) { *address = s->address; return true; } else if (s->size < length) { if (le == c->space_size.Flink) return false; s = CONTAINING_RECORD(le->Blink, space, list_entry_size); *address = s->address; return true; } le = le->Flink; } s = CONTAINING_RECORD(c->space_size.Blink, space, list_entry_size); if (s->size > length) { *address = s->address; return true; } return false; } __attribute__((nonnull(1))) chunk* get_chunk_from_address(device_extension* Vcb, uint64_t address) { LIST_ENTRY* le2; ExAcquireResourceSharedLite(&Vcb->chunk_lock, true); le2 = Vcb->chunks.Flink; while (le2 != &Vcb->chunks) { chunk* c = CONTAINING_RECORD(le2, chunk, list_entry); if (address >= c->offset && address < c->offset + c->chunk_item->size) { ExReleaseResourceLite(&Vcb->chunk_lock); return c; } le2 = le2->Flink; } ExReleaseResourceLite(&Vcb->chunk_lock); return NULL; } typedef struct { space* dh; device* device; } stripe; __attribute__((nonnull(1))) static uint64_t find_new_chunk_address(device_extension* Vcb, uint64_t size) { uint64_t lastaddr; LIST_ENTRY* le; lastaddr = 0xc00000; le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c = CONTAINING_RECORD(le, chunk, list_entry); if (c->offset >= lastaddr + size) return lastaddr; lastaddr = c->offset + c->chunk_item->size; le = le->Flink; } return lastaddr; } __attribute__((nonnull(1,2))) static bool find_new_dup_stripes(device_extension* Vcb, stripe* stripes, uint64_t max_stripe_size, bool full_size) { uint64_t devusage = 0xffffffffffffffff; space *devdh1 = NULL, *devdh2 = NULL; LIST_ENTRY* le; device* dev2 = NULL; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (!dev->readonly && !dev->reloc && dev->devobj) { uint64_t usage = (dev->devitem.bytes_used * 4096) / dev->devitem.num_bytes; // favour devices which have been used the least if (usage < devusage) { if (!IsListEmpty(&dev->space)) { LIST_ENTRY* le2; space *dh1 = NULL, *dh2 = NULL; le2 = dev->space.Flink; while (le2 != &dev->space) { space* dh = CONTAINING_RECORD(le2, space, list_entry); if (dh->size >= max_stripe_size && (!dh1 || !dh2 || dh->size < dh1->size)) { dh2 = dh1; dh1 = dh; } le2 = le2->Flink; } if (dh1 && (dh2 || dh1->size >= 2 * max_stripe_size)) { dev2 = dev; devusage = usage; devdh1 = dh1; devdh2 = dh2 ? dh2 : dh1; } } } } le = le->Flink; } if (!devdh1) { uint64_t size = 0; // Can't find hole of at least max_stripe_size; look for the largest one we can find if (full_size) return false; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); if (!dev->readonly && !dev->reloc) { if (!IsListEmpty(&dev->space)) { LIST_ENTRY* le2; space *dh1 = NULL, *dh2 = NULL; le2 = dev->space.Flink; while (le2 != &dev->space) { space* dh = CONTAINING_RECORD(le2, space, list_entry); if (!dh1 || !dh2 || dh->size < dh1->size) { dh2 = dh1; dh1 = dh; } le2 = le2->Flink; } if (dh1) { uint64_t devsize; if (dh2) devsize = max(dh1->size / 2, min(dh1->size, dh2->size)); else devsize = dh1->size / 2; if (devsize > size) { dev2 = dev; devdh1 = dh1; if (dh2 && min(dh1->size, dh2->size) > dh1->size / 2) devdh2 = dh2; else devdh2 = dh1; size = devsize; } } } } le = le->Flink; } if (!devdh1) return false; } stripes[0].device = stripes[1].device = dev2; stripes[0].dh = devdh1; stripes[1].dh = devdh2; return true; } __attribute__((nonnull(1,2))) static bool find_new_stripe(device_extension* Vcb, stripe* stripes, uint16_t i, uint64_t max_stripe_size, bool allow_missing, bool full_size) { uint64_t k, devusage = 0xffffffffffffffff; space* devdh = NULL; LIST_ENTRY* le; device* dev2 = NULL; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); uint64_t usage; bool skip = false; if (dev->readonly || dev->reloc || (!dev->devobj && !allow_missing)) { le = le->Flink; continue; } // skip this device if it already has a stripe if (i > 0) { for (k = 0; k < i; k++) { if (stripes[k].device == dev) { skip = true; break; } } } if (!skip) { usage = (dev->devitem.bytes_used * 4096) / dev->devitem.num_bytes; // favour devices which have been used the least if (usage < devusage) { if (!IsListEmpty(&dev->space)) { LIST_ENTRY* le2; le2 = dev->space.Flink; while (le2 != &dev->space) { space* dh = CONTAINING_RECORD(le2, space, list_entry); if ((dev2 != dev && dh->size >= max_stripe_size) || (dev2 == dev && dh->size >= max_stripe_size && dh->size < devdh->size) ) { devdh = dh; dev2 = dev; devusage = usage; } le2 = le2->Flink; } } } } le = le->Flink; } if (!devdh) { // Can't find hole of at least max_stripe_size; look for the largest one we can find if (full_size) return false; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); bool skip = false; if (dev->readonly || dev->reloc || (!dev->devobj && !allow_missing)) { le = le->Flink; continue; } // skip this device if it already has a stripe if (i > 0) { for (k = 0; k < i; k++) { if (stripes[k].device == dev) { skip = true; break; } } } if (!skip) { if (!IsListEmpty(&dev->space)) { LIST_ENTRY* le2; le2 = dev->space.Flink; while (le2 != &dev->space) { space* dh = CONTAINING_RECORD(le2, space, list_entry); if (!devdh || devdh->size < dh->size) { devdh = dh; dev2 = dev; } le2 = le2->Flink; } } } le = le->Flink; } if (!devdh) return false; } stripes[i].dh = devdh; stripes[i].device = dev2; return true; } __attribute__((nonnull(1,3))) NTSTATUS alloc_chunk(device_extension* Vcb, uint64_t flags, chunk** pc, bool full_size) { NTSTATUS Status; uint64_t max_stripe_size, max_chunk_size, stripe_size, stripe_length, factor; uint64_t total_size = 0, logaddr; uint16_t i, type, num_stripes, sub_stripes, max_stripes, min_stripes, allowed_missing; stripe* stripes = NULL; uint16_t cisize; CHUNK_ITEM_STRIPE* cis; chunk* c = NULL; space* s = NULL; LIST_ENTRY* le; le = Vcb->devices.Flink; while (le != &Vcb->devices) { device* dev = CONTAINING_RECORD(le, device, list_entry); total_size += dev->devitem.num_bytes; le = le->Flink; } TRACE("total_size = %I64x\n", total_size); // We purposely check for DATA first - mixed blocks have the same size // as DATA ones. if (flags & BLOCK_FLAG_DATA) { max_stripe_size = 0x40000000; // 1 GB max_chunk_size = 10 * max_stripe_size; } else if (flags & BLOCK_FLAG_METADATA) { if (total_size > 0xC80000000) // 50 GB max_stripe_size = 0x40000000; // 1 GB else max_stripe_size = 0x10000000; // 256 MB max_chunk_size = max_stripe_size; } else if (flags & BLOCK_FLAG_SYSTEM) { max_stripe_size = 0x2000000; // 32 MB max_chunk_size = 2 * max_stripe_size; } else { ERR("unknown chunk type\n"); return STATUS_INTERNAL_ERROR; } if (flags & BLOCK_FLAG_DUPLICATE) { min_stripes = 2; max_stripes = 2; sub_stripes = 0; type = BLOCK_FLAG_DUPLICATE; allowed_missing = 0; } else if (flags & BLOCK_FLAG_RAID0) { min_stripes = 2; max_stripes = (uint16_t)min(0xffff, Vcb->superblock.num_devices); sub_stripes = 0; type = BLOCK_FLAG_RAID0; allowed_missing = 0; } else if (flags & BLOCK_FLAG_RAID1) { min_stripes = 2; max_stripes = 2; sub_stripes = 1; type = BLOCK_FLAG_RAID1; allowed_missing = 1; } else if (flags & BLOCK_FLAG_RAID10) { min_stripes = 4; max_stripes = (uint16_t)min(0xffff, Vcb->superblock.num_devices); sub_stripes = 2; type = BLOCK_FLAG_RAID10; allowed_missing = 1; } else if (flags & BLOCK_FLAG_RAID5) { min_stripes = 3; max_stripes = (uint16_t)min(0xffff, Vcb->superblock.num_devices); sub_stripes = 1; type = BLOCK_FLAG_RAID5; allowed_missing = 1; } else if (flags & BLOCK_FLAG_RAID6) { min_stripes = 4; max_stripes = 257; sub_stripes = 1; type = BLOCK_FLAG_RAID6; allowed_missing = 2; } else if (flags & BLOCK_FLAG_RAID1C3) { min_stripes = 3; max_stripes = 3; sub_stripes = 1; type = BLOCK_FLAG_RAID1C3; allowed_missing = 2; } else if (flags & BLOCK_FLAG_RAID1C4) { min_stripes = 4; max_stripes = 4; sub_stripes = 1; type = BLOCK_FLAG_RAID1C4; allowed_missing = 3; } else { // SINGLE min_stripes = 1; max_stripes = 1; sub_stripes = 1; type = 0; allowed_missing = 0; } if (max_chunk_size > total_size / 10) { // cap at 10% max_chunk_size = total_size / 10; max_stripe_size = max_chunk_size / min_stripes; } if (max_stripe_size > total_size / (10 * min_stripes)) max_stripe_size = total_size / (10 * min_stripes); TRACE("would allocate a new chunk of %I64x bytes and stripe %I64x\n", max_chunk_size, max_stripe_size); stripes = ExAllocatePoolWithTag(PagedPool, sizeof(stripe) * max_stripes, ALLOC_TAG); if (!stripes) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } num_stripes = 0; if (type == BLOCK_FLAG_DUPLICATE) { if (!find_new_dup_stripes(Vcb, stripes, max_stripe_size, full_size)) { Status = STATUS_DISK_FULL; goto end; } else num_stripes = max_stripes; } else { for (i = 0; i < max_stripes; i++) { if (!find_new_stripe(Vcb, stripes, i, max_stripe_size, false, full_size)) break; else num_stripes++; } } if (num_stripes < min_stripes && Vcb->options.allow_degraded && allowed_missing > 0) { uint16_t added_missing = 0; for (i = num_stripes; i < max_stripes; i++) { if (!find_new_stripe(Vcb, stripes, i, max_stripe_size, true, full_size)) break; else { added_missing++; if (added_missing >= allowed_missing) break; } } num_stripes += added_missing; } // for RAID10, round down to an even number of stripes if (type == BLOCK_FLAG_RAID10 && (num_stripes % sub_stripes) != 0) { num_stripes -= num_stripes % sub_stripes; } if (num_stripes < min_stripes) { WARN("found %u stripes, needed at least %u\n", num_stripes, min_stripes); Status = STATUS_DISK_FULL; goto end; } c = ExAllocatePoolWithTag(NonPagedPool, sizeof(chunk), ALLOC_TAG); if (!c) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } c->devices = NULL; cisize = sizeof(CHUNK_ITEM) + (num_stripes * sizeof(CHUNK_ITEM_STRIPE)); c->chunk_item = ExAllocatePoolWithTag(NonPagedPool, cisize, ALLOC_TAG); if (!c->chunk_item) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } stripe_length = 0x10000; // FIXME? BTRFS_STRIPE_LEN in kernel if (type == BLOCK_FLAG_DUPLICATE && stripes[1].dh == stripes[0].dh) stripe_size = min(stripes[0].dh->size / 2, max_stripe_size); else { stripe_size = max_stripe_size; for (i = 0; i < num_stripes; i++) { if (stripes[i].dh->size < stripe_size) stripe_size = stripes[i].dh->size; } } if (type == BLOCK_FLAG_RAID0) factor = num_stripes; else if (type == BLOCK_FLAG_RAID10) factor = num_stripes / sub_stripes; else if (type == BLOCK_FLAG_RAID5) factor = num_stripes - 1; else if (type == BLOCK_FLAG_RAID6) factor = num_stripes - 2; else factor = 1; // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4 if (stripe_size * factor > max_chunk_size) stripe_size = max_chunk_size / factor; if (stripe_size % stripe_length > 0) stripe_size -= stripe_size % stripe_length; if (stripe_size == 0) { ERR("not enough free space found (stripe_size == 0)\n"); Status = STATUS_DISK_FULL; goto end; } c->chunk_item->size = stripe_size * factor; c->chunk_item->root_id = Vcb->extent_root->id; c->chunk_item->stripe_length = stripe_length; c->chunk_item->type = flags; c->chunk_item->opt_io_alignment = (uint32_t)c->chunk_item->stripe_length; c->chunk_item->opt_io_width = (uint32_t)c->chunk_item->stripe_length; c->chunk_item->sector_size = stripes[0].device->devitem.minimal_io_size; c->chunk_item->num_stripes = num_stripes; c->chunk_item->sub_stripes = sub_stripes; c->devices = ExAllocatePoolWithTag(NonPagedPool, sizeof(device*) * num_stripes, ALLOC_TAG); if (!c->devices) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; for (i = 0; i < num_stripes; i++) { cis[i].dev_id = stripes[i].device->devitem.dev_id; if (type == BLOCK_FLAG_DUPLICATE && i == 1 && stripes[i].dh == stripes[0].dh) cis[i].offset = stripes[0].dh->address + stripe_size; else cis[i].offset = stripes[i].dh->address; cis[i].dev_uuid = stripes[i].device->devitem.device_uuid; c->devices[i] = stripes[i].device; } logaddr = find_new_chunk_address(Vcb, c->chunk_item->size); Vcb->superblock.chunk_root_generation = Vcb->superblock.generation; c->size = cisize; c->offset = logaddr; c->used = c->oldused = 0; c->cache = c->old_cache = NULL; c->readonly = false; c->reloc = false; c->last_alloc_set = false; c->last_stripe = 0; c->cache_loaded = true; c->changed = false; c->space_changed = false; c->balance_num = 0; InitializeListHead(&c->space); InitializeListHead(&c->space_size); InitializeListHead(&c->deleting); InitializeListHead(&c->changed_extents); InitializeListHead(&c->range_locks); ExInitializeResourceLite(&c->range_locks_lock); KeInitializeEvent(&c->range_locks_event, NotificationEvent, false); InitializeListHead(&c->partial_stripes); ExInitializeResourceLite(&c->partial_stripes_lock); ExInitializeResourceLite(&c->lock); ExInitializeResourceLite(&c->changed_extents_lock); s = ExAllocatePoolWithTag(NonPagedPool, sizeof(space), ALLOC_TAG); if (!s) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } s->address = c->offset; s->size = c->chunk_item->size; InsertTailList(&c->space, &s->list_entry); InsertTailList(&c->space_size, &s->list_entry_size); protect_superblocks(c); for (i = 0; i < num_stripes; i++) { stripes[i].device->devitem.bytes_used += stripe_size; space_list_subtract2(&stripes[i].device->space, NULL, cis[i].offset, stripe_size, NULL, NULL); } Status = STATUS_SUCCESS; if (flags & BLOCK_FLAG_RAID5 || flags & BLOCK_FLAG_RAID6) Vcb->superblock.incompat_flags |= BTRFS_INCOMPAT_FLAGS_RAID56; end: if (stripes) ExFreePool(stripes); if (!NT_SUCCESS(Status)) { if (c) { if (c->devices) ExFreePool(c->devices); if (c->chunk_item) ExFreePool(c->chunk_item); ExFreePool(c); } if (s) ExFreePool(s); } else { bool done = false; le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { chunk* c2 = CONTAINING_RECORD(le, chunk, list_entry); if (c2->offset > c->offset) { InsertHeadList(le->Blink, &c->list_entry); done = true; break; } le = le->Flink; } if (!done) InsertTailList(&Vcb->chunks, &c->list_entry); c->created = true; c->changed = true; c->space_changed = true; c->list_entry_balance.Flink = NULL; *pc = c; } return Status; } __attribute__((nonnull(1,3,5,8))) static 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, _In_ uint32_t length, _In_ write_stripe* stripes, _In_ PIRP Irp, _In_ uint64_t irp_offset, _In_ write_data_context* wtc) { uint64_t startoff, endoff; uint16_t startoffstripe, endoffstripe, stripenum; uint64_t pos, *stripeoff; uint32_t i; bool file_write = Irp && Irp->MdlAddress && (Irp->MdlAddress->ByteOffset == 0); PMDL master_mdl; PFN_NUMBER* pfns; stripeoff = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t) * c->chunk_item->num_stripes, ALLOC_TAG); if (!stripeoff) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } get_raid0_offset(address - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &startoff, &startoffstripe); get_raid0_offset(address + length - c->offset - 1, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &endoff, &endoffstripe); if (file_write) { master_mdl = Irp->MdlAddress; pfns = (PFN_NUMBER*)(Irp->MdlAddress + 1); pfns = &pfns[irp_offset >> PAGE_SHIFT]; } else if (((ULONG_PTR)data % PAGE_SIZE) != 0) { wtc->scratch = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG); if (!wtc->scratch) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(wtc->scratch, data, length); master_mdl = IoAllocateMdl(wtc->scratch, length, false, false, NULL); if (!master_mdl) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } MmBuildMdlForNonPagedPool(master_mdl); wtc->mdl = master_mdl; pfns = (PFN_NUMBER*)(master_mdl + 1); } else { NTSTATUS Status = STATUS_SUCCESS; master_mdl = IoAllocateMdl(data, length, false, false, NULL); if (!master_mdl) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } try { MmProbeAndLockPages(master_mdl, KernelMode, IoReadAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); IoFreeMdl(master_mdl); return Status; } wtc->mdl = master_mdl; pfns = (PFN_NUMBER*)(master_mdl + 1); } for (i = 0; i < c->chunk_item->num_stripes; i++) { if (startoffstripe > i) stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; else if (startoffstripe == i) stripes[i].start = startoff; else stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length); if (endoffstripe > i) stripes[i].end = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; else if (endoffstripe == i) stripes[i].end = endoff + 1; else stripes[i].end = endoff - (endoff % c->chunk_item->stripe_length); if (stripes[i].start != stripes[i].end) { stripes[i].mdl = IoAllocateMdl(NULL, (ULONG)(stripes[i].end - stripes[i].start), false, false, NULL); if (!stripes[i].mdl) { ERR("IoAllocateMdl failed\n"); ExFreePool(stripeoff); return STATUS_INSUFFICIENT_RESOURCES; } } } pos = 0; RtlZeroMemory(stripeoff, sizeof(uint64_t) * c->chunk_item->num_stripes); stripenum = startoffstripe; while (pos < length) { PFN_NUMBER* stripe_pfns = (PFN_NUMBER*)(stripes[stripenum].mdl + 1); if (pos == 0) { uint32_t writelen = (uint32_t)min(stripes[stripenum].end - stripes[stripenum].start, c->chunk_item->stripe_length - (stripes[stripenum].start % c->chunk_item->stripe_length)); RtlCopyMemory(stripe_pfns, pfns, writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); stripeoff[stripenum] += writelen; pos += writelen; } else if (length - pos < c->chunk_item->stripe_length) { RtlCopyMemory(&stripe_pfns[stripeoff[stripenum] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)((length - pos) * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); break; } else { RtlCopyMemory(&stripe_pfns[stripeoff[stripenum] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); stripeoff[stripenum] += c->chunk_item->stripe_length; pos += c->chunk_item->stripe_length; } stripenum = (stripenum + 1) % c->chunk_item->num_stripes; } ExFreePool(stripeoff); return STATUS_SUCCESS; } __attribute__((nonnull(1,3,5,8))) static 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, _In_ uint64_t address, _In_reads_bytes_(length) void* data, _In_ uint32_t length, _In_ write_stripe* stripes, _In_ PIRP Irp, _In_ uint64_t irp_offset, _In_ write_data_context* wtc) { uint64_t startoff, endoff; uint16_t startoffstripe, endoffstripe, stripenum; uint64_t pos, *stripeoff; uint32_t i; bool file_write = Irp && Irp->MdlAddress && (Irp->MdlAddress->ByteOffset == 0); PMDL master_mdl; PFN_NUMBER* pfns; get_raid0_offset(address - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes / c->chunk_item->sub_stripes, &startoff, &startoffstripe); 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); stripenum = startoffstripe; startoffstripe *= c->chunk_item->sub_stripes; endoffstripe *= c->chunk_item->sub_stripes; if (file_write) { master_mdl = Irp->MdlAddress; pfns = (PFN_NUMBER*)(Irp->MdlAddress + 1); pfns = &pfns[irp_offset >> PAGE_SHIFT]; } else if (((ULONG_PTR)data % PAGE_SIZE) != 0) { wtc->scratch = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG); if (!wtc->scratch) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(wtc->scratch, data, length); master_mdl = IoAllocateMdl(wtc->scratch, length, false, false, NULL); if (!master_mdl) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } MmBuildMdlForNonPagedPool(master_mdl); wtc->mdl = master_mdl; pfns = (PFN_NUMBER*)(master_mdl + 1); } else { NTSTATUS Status = STATUS_SUCCESS; master_mdl = IoAllocateMdl(data, length, false, false, NULL); if (!master_mdl) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } try { MmProbeAndLockPages(master_mdl, KernelMode, IoReadAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); IoFreeMdl(master_mdl); return Status; } wtc->mdl = master_mdl; pfns = (PFN_NUMBER*)(master_mdl + 1); } for (i = 0; i < c->chunk_item->num_stripes; i += c->chunk_item->sub_stripes) { uint16_t j; if (startoffstripe > i) stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; else if (startoffstripe == i) stripes[i].start = startoff; else stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length); if (endoffstripe > i) stripes[i].end = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; else if (endoffstripe == i) stripes[i].end = endoff + 1; else stripes[i].end = endoff - (endoff % c->chunk_item->stripe_length); stripes[i].mdl = IoAllocateMdl(NULL, (ULONG)(stripes[i].end - stripes[i].start), false, false, NULL); if (!stripes[i].mdl) { ERR("IoAllocateMdl failed\n"); return STATUS_INSUFFICIENT_RESOURCES; } for (j = 1; j < c->chunk_item->sub_stripes; j++) { stripes[i+j].start = stripes[i].start; stripes[i+j].end = stripes[i].end; stripes[i+j].data = stripes[i].data; stripes[i+j].mdl = stripes[i].mdl; } } pos = 0; stripeoff = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t) * c->chunk_item->num_stripes / c->chunk_item->sub_stripes, ALLOC_TAG); if (!stripeoff) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(stripeoff, sizeof(uint64_t) * c->chunk_item->num_stripes / c->chunk_item->sub_stripes); while (pos < length) { PFN_NUMBER* stripe_pfns = (PFN_NUMBER*)(stripes[stripenum * c->chunk_item->sub_stripes].mdl + 1); if (pos == 0) { uint32_t writelen = (uint32_t)min(stripes[stripenum * c->chunk_item->sub_stripes].end - stripes[stripenum * c->chunk_item->sub_stripes].start, c->chunk_item->stripe_length - (stripes[stripenum * c->chunk_item->sub_stripes].start % c->chunk_item->stripe_length)); RtlCopyMemory(stripe_pfns, pfns, writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); stripeoff[stripenum] += writelen; pos += writelen; } else if (length - pos < c->chunk_item->stripe_length) { RtlCopyMemory(&stripe_pfns[stripeoff[stripenum] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)((length - pos) * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); break; } else { RtlCopyMemory(&stripe_pfns[stripeoff[stripenum] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); stripeoff[stripenum] += c->chunk_item->stripe_length; pos += c->chunk_item->stripe_length; } stripenum = (stripenum + 1) % (c->chunk_item->num_stripes / c->chunk_item->sub_stripes); } ExFreePool(stripeoff); return STATUS_SUCCESS; } __attribute__((nonnull(1,2,5))) static NTSTATUS add_partial_stripe(device_extension* Vcb, chunk* c, uint64_t address, uint32_t length, void* data) { NTSTATUS Status; LIST_ENTRY* le; partial_stripe* ps; uint64_t stripe_addr; uint16_t num_data_stripes; num_data_stripes = c->chunk_item->num_stripes - (c->chunk_item->type & BLOCK_FLAG_RAID5 ? 1 : 2); stripe_addr = address - ((address - c->offset) % (num_data_stripes * c->chunk_item->stripe_length)); ExAcquireResourceExclusiveLite(&c->partial_stripes_lock, true); le = c->partial_stripes.Flink; while (le != &c->partial_stripes) { ps = CONTAINING_RECORD(le, partial_stripe, list_entry); if (ps->address == stripe_addr) { // update existing entry RtlCopyMemory(ps->data + address - stripe_addr, data, length); RtlClearBits(&ps->bmp, (ULONG)((address - stripe_addr) >> Vcb->sector_shift), length >> Vcb->sector_shift); // if now filled, flush if (RtlAreBitsClear(&ps->bmp, 0, (ULONG)((num_data_stripes * c->chunk_item->stripe_length) >> Vcb->sector_shift))) { Status = flush_partial_stripe(Vcb, c, ps); if (!NT_SUCCESS(Status)) { ERR("flush_partial_stripe returned %08lx\n", Status); goto end; } RemoveEntryList(&ps->list_entry); if (ps->bmparr) ExFreePool(ps->bmparr); ExFreePool(ps); } Status = STATUS_SUCCESS; goto end; } else if (ps->address > stripe_addr) break; le = le->Flink; } // add new entry ps = ExAllocatePoolWithTag(NonPagedPool, offsetof(partial_stripe, data[0]) + (ULONG)(num_data_stripes * c->chunk_item->stripe_length), ALLOC_TAG); if (!ps) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } ps->bmplen = (ULONG)(num_data_stripes * c->chunk_item->stripe_length) >> Vcb->sector_shift; ps->address = stripe_addr; ps->bmparr = ExAllocatePoolWithTag(NonPagedPool, (size_t)sector_align(((ps->bmplen / 8) + 1), sizeof(ULONG)), ALLOC_TAG); if (!ps->bmparr) { ERR("out of memory\n"); ExFreePool(ps); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlInitializeBitMap(&ps->bmp, ps->bmparr, ps->bmplen); RtlSetAllBits(&ps->bmp); RtlCopyMemory(ps->data + address - stripe_addr, data, length); RtlClearBits(&ps->bmp, (ULONG)((address - stripe_addr) >> Vcb->sector_shift), length >> Vcb->sector_shift); InsertHeadList(le->Blink, &ps->list_entry); Status = STATUS_SUCCESS; end: ExReleaseResourceLite(&c->partial_stripes_lock); return Status; } typedef struct { PMDL mdl; PFN_NUMBER* pfns; } log_stripe; __attribute__((nonnull(1,2,4,6,10))) static NTSTATUS prepare_raid5_write(device_extension* Vcb, chunk* c, uint64_t address, void* data, uint32_t length, write_stripe* stripes, PIRP Irp, uint64_t irp_offset, ULONG priority, write_data_context* wtc) { uint64_t startoff, endoff, parity_start, parity_end; uint16_t startoffstripe, endoffstripe, parity, num_data_stripes = c->chunk_item->num_stripes - 1; uint64_t pos, parity_pos, *stripeoff = NULL; uint32_t i; bool file_write = Irp && Irp->MdlAddress && (Irp->MdlAddress->ByteOffset == 0); PMDL master_mdl; NTSTATUS Status; PFN_NUMBER *pfns, *parity_pfns; log_stripe* log_stripes = NULL; if ((address + length - c->offset) % (num_data_stripes * c->chunk_item->stripe_length) > 0) { uint64_t delta = (address + length - c->offset) % (num_data_stripes * c->chunk_item->stripe_length); delta = min(length, delta); Status = add_partial_stripe(Vcb, c, address + length - delta, (uint32_t)delta, (uint8_t*)data + length - delta); if (!NT_SUCCESS(Status)) { ERR("add_partial_stripe returned %08lx\n", Status); goto exit; } length -= (uint32_t)delta; } if (length > 0 && (address - c->offset) % (num_data_stripes * c->chunk_item->stripe_length) > 0) { uint64_t delta = (num_data_stripes * c->chunk_item->stripe_length) - ((address - c->offset) % (num_data_stripes * c->chunk_item->stripe_length)); Status = add_partial_stripe(Vcb, c, address, (uint32_t)delta, data); if (!NT_SUCCESS(Status)) { ERR("add_partial_stripe returned %08lx\n", Status); goto exit; } address += delta; length -= (uint32_t)delta; irp_offset += delta; data = (uint8_t*)data + delta; } if (length == 0) { Status = STATUS_SUCCESS; goto exit; } get_raid0_offset(address - c->offset, c->chunk_item->stripe_length, num_data_stripes, &startoff, &startoffstripe); get_raid0_offset(address + length - c->offset - 1, c->chunk_item->stripe_length, num_data_stripes, &endoff, &endoffstripe); pos = 0; while (pos < length) { parity = (((address - c->offset + pos) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes; if (pos == 0) { uint16_t stripe = (parity + startoffstripe + 1) % c->chunk_item->num_stripes; ULONG skip, writelen; i = startoffstripe; while (stripe != parity) { if (i == startoffstripe) { writelen = (ULONG)min(length, c->chunk_item->stripe_length - (startoff % c->chunk_item->stripe_length)); stripes[stripe].start = startoff; stripes[stripe].end = startoff + writelen; pos += writelen; if (pos == length) break; } else { writelen = (ULONG)min(length - pos, c->chunk_item->stripe_length); stripes[stripe].start = startoff - (startoff % c->chunk_item->stripe_length); stripes[stripe].end = stripes[stripe].start + writelen; pos += writelen; if (pos == length) break; } i++; stripe = (stripe + 1) % c->chunk_item->num_stripes; } if (pos == length) break; for (i = 0; i < startoffstripe; i++) { stripe = (parity + i + 1) % c->chunk_item->num_stripes; stripes[stripe].start = stripes[stripe].end = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; } stripes[parity].start = stripes[parity].end = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; if (length - pos > c->chunk_item->num_stripes * num_data_stripes * c->chunk_item->stripe_length) { skip = (ULONG)(((length - pos) / (c->chunk_item->num_stripes * num_data_stripes * c->chunk_item->stripe_length)) - 1); for (i = 0; i < c->chunk_item->num_stripes; i++) { stripes[i].end += skip * c->chunk_item->num_stripes * c->chunk_item->stripe_length; } pos += skip * num_data_stripes * c->chunk_item->num_stripes * c->chunk_item->stripe_length; } } else if (length - pos >= c->chunk_item->stripe_length * num_data_stripes) { for (i = 0; i < c->chunk_item->num_stripes; i++) { stripes[i].end += c->chunk_item->stripe_length; } pos += c->chunk_item->stripe_length * num_data_stripes; } else { uint16_t stripe = (parity + 1) % c->chunk_item->num_stripes; i = 0; while (stripe != parity) { if (endoffstripe == i) { stripes[stripe].end = endoff + 1; break; } else if (endoffstripe > i) stripes[stripe].end = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; i++; stripe = (stripe + 1) % c->chunk_item->num_stripes; } break; } } parity_start = 0xffffffffffffffff; parity_end = 0; for (i = 0; i < c->chunk_item->num_stripes; i++) { if (stripes[i].start != 0 || stripes[i].end != 0) { parity_start = min(stripes[i].start, parity_start); parity_end = max(stripes[i].end, parity_end); } } if (parity_end == parity_start) { Status = STATUS_SUCCESS; goto exit; } parity = (((address - c->offset) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes; stripes[parity].start = parity_start; parity = (((address - c->offset + length - 1) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes; stripes[parity].end = parity_end; log_stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(log_stripe) * num_data_stripes, ALLOC_TAG); if (!log_stripes) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlZeroMemory(log_stripes, sizeof(log_stripe) * num_data_stripes); for (i = 0; i < num_data_stripes; i++) { log_stripes[i].mdl = IoAllocateMdl(NULL, (ULONG)(parity_end - parity_start), false, false, NULL); if (!log_stripes[i].mdl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } log_stripes[i].mdl->MdlFlags |= MDL_PARTIAL; log_stripes[i].pfns = (PFN_NUMBER*)(log_stripes[i].mdl + 1); } wtc->parity1 = ExAllocatePoolWithTag(NonPagedPool, (ULONG)(parity_end - parity_start), ALLOC_TAG); if (!wtc->parity1) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } wtc->parity1_mdl = IoAllocateMdl(wtc->parity1, (ULONG)(parity_end - parity_start), false, false, NULL); if (!wtc->parity1_mdl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } MmBuildMdlForNonPagedPool(wtc->parity1_mdl); if (file_write) master_mdl = Irp->MdlAddress; else if (((ULONG_PTR)data % PAGE_SIZE) != 0) { wtc->scratch = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG); if (!wtc->scratch) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlCopyMemory(wtc->scratch, data, length); master_mdl = IoAllocateMdl(wtc->scratch, length, false, false, NULL); if (!master_mdl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } MmBuildMdlForNonPagedPool(master_mdl); wtc->mdl = master_mdl; } else { master_mdl = IoAllocateMdl(data, length, false, false, NULL); if (!master_mdl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Status = STATUS_SUCCESS; try { MmProbeAndLockPages(master_mdl, KernelMode, IoReadAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); IoFreeMdl(master_mdl); return Status; } wtc->mdl = master_mdl; } pfns = (PFN_NUMBER*)(master_mdl + 1); parity_pfns = (PFN_NUMBER*)(wtc->parity1_mdl + 1); if (file_write) pfns = &pfns[irp_offset >> PAGE_SHIFT]; for (i = 0; i < c->chunk_item->num_stripes; i++) { if (stripes[i].start != stripes[i].end) { stripes[i].mdl = IoAllocateMdl((uint8_t*)MmGetMdlVirtualAddress(master_mdl) + irp_offset, (ULONG)(stripes[i].end - stripes[i].start), false, false, NULL); if (!stripes[i].mdl) { ERR("IoAllocateMdl failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } } stripeoff = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t) * c->chunk_item->num_stripes, ALLOC_TAG); if (!stripeoff) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlZeroMemory(stripeoff, sizeof(uint64_t) * c->chunk_item->num_stripes); pos = 0; parity_pos = 0; while (pos < length) { PFN_NUMBER* stripe_pfns; parity = (((address - c->offset + pos) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes; if (pos == 0) { uint16_t stripe = (parity + startoffstripe + 1) % c->chunk_item->num_stripes; uint32_t writelen = (uint32_t)min(length - pos, min(stripes[stripe].end - stripes[stripe].start, c->chunk_item->stripe_length - (stripes[stripe].start % c->chunk_item->stripe_length))); uint32_t maxwritelen = writelen; stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1); RtlCopyMemory(stripe_pfns, pfns, writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); RtlCopyMemory(log_stripes[startoffstripe].pfns, pfns, writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); log_stripes[startoffstripe].pfns += writelen >> PAGE_SHIFT; stripeoff[stripe] = writelen; pos += writelen; stripe = (stripe + 1) % c->chunk_item->num_stripes; i = startoffstripe + 1; while (stripe != parity) { stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1); writelen = (uint32_t)min(length - pos, min(stripes[stripe].end - stripes[stripe].start, c->chunk_item->stripe_length)); if (writelen == 0) break; if (writelen > maxwritelen) maxwritelen = writelen; RtlCopyMemory(stripe_pfns, &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); RtlCopyMemory(log_stripes[i].pfns, &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); log_stripes[i].pfns += writelen >> PAGE_SHIFT; stripeoff[stripe] = writelen; pos += writelen; stripe = (stripe + 1) % c->chunk_item->num_stripes; i++; } stripe_pfns = (PFN_NUMBER*)(stripes[parity].mdl + 1); RtlCopyMemory(stripe_pfns, parity_pfns, maxwritelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); stripeoff[parity] = maxwritelen; parity_pos = maxwritelen; } else if (length - pos >= c->chunk_item->stripe_length * num_data_stripes) { uint16_t stripe = (parity + 1) % c->chunk_item->num_stripes; i = 0; while (stripe != parity) { stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1); RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); RtlCopyMemory(log_stripes[i].pfns, &pfns[pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); log_stripes[i].pfns += c->chunk_item->stripe_length >> PAGE_SHIFT; stripeoff[stripe] += c->chunk_item->stripe_length; pos += c->chunk_item->stripe_length; stripe = (stripe + 1) % c->chunk_item->num_stripes; i++; } stripe_pfns = (PFN_NUMBER*)(stripes[parity].mdl + 1); RtlCopyMemory(&stripe_pfns[stripeoff[parity] >> PAGE_SHIFT], &parity_pfns[parity_pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); stripeoff[parity] += c->chunk_item->stripe_length; parity_pos += c->chunk_item->stripe_length; } else { uint16_t stripe = (parity + 1) % c->chunk_item->num_stripes; uint32_t writelen, maxwritelen = 0; i = 0; while (pos < length) { stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1); writelen = (uint32_t)min(length - pos, min(stripes[stripe].end - stripes[stripe].start, c->chunk_item->stripe_length)); if (writelen == 0) break; if (writelen > maxwritelen) maxwritelen = writelen; RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); RtlCopyMemory(log_stripes[i].pfns, &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); log_stripes[i].pfns += writelen >> PAGE_SHIFT; stripeoff[stripe] += writelen; pos += writelen; stripe = (stripe + 1) % c->chunk_item->num_stripes; i++; } stripe_pfns = (PFN_NUMBER*)(stripes[parity].mdl + 1); RtlCopyMemory(&stripe_pfns[stripeoff[parity] >> PAGE_SHIFT], &parity_pfns[parity_pos >> PAGE_SHIFT], maxwritelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); } } for (i = 0; i < num_data_stripes; i++) { uint8_t* ss = MmGetSystemAddressForMdlSafe(log_stripes[i].mdl, priority); if (i == 0) RtlCopyMemory(wtc->parity1, ss, (uint32_t)(parity_end - parity_start)); else do_xor(wtc->parity1, ss, (uint32_t)(parity_end - parity_start)); } Status = STATUS_SUCCESS; exit: if (log_stripes) { for (i = 0; i < num_data_stripes; i++) { if (log_stripes[i].mdl) IoFreeMdl(log_stripes[i].mdl); } ExFreePool(log_stripes); } if (stripeoff) ExFreePool(stripeoff); return Status; } __attribute__((nonnull(1,2,4,6,10))) static NTSTATUS prepare_raid6_write(device_extension* Vcb, chunk* c, uint64_t address, void* data, uint32_t length, write_stripe* stripes, PIRP Irp, uint64_t irp_offset, ULONG priority, write_data_context* wtc) { uint64_t startoff, endoff, parity_start, parity_end; uint16_t startoffstripe, endoffstripe, parity1, num_data_stripes = c->chunk_item->num_stripes - 2; uint64_t pos, parity_pos, *stripeoff = NULL; uint32_t i; bool file_write = Irp && Irp->MdlAddress && (Irp->MdlAddress->ByteOffset == 0); PMDL master_mdl; NTSTATUS Status; PFN_NUMBER *pfns, *parity1_pfns, *parity2_pfns; log_stripe* log_stripes = NULL; if ((address + length - c->offset) % (num_data_stripes * c->chunk_item->stripe_length) > 0) { uint64_t delta = (address + length - c->offset) % (num_data_stripes * c->chunk_item->stripe_length); delta = min(length, delta); Status = add_partial_stripe(Vcb, c, address + length - delta, (uint32_t)delta, (uint8_t*)data + length - delta); if (!NT_SUCCESS(Status)) { ERR("add_partial_stripe returned %08lx\n", Status); goto exit; } length -= (uint32_t)delta; } if (length > 0 && (address - c->offset) % (num_data_stripes * c->chunk_item->stripe_length) > 0) { uint64_t delta = (num_data_stripes * c->chunk_item->stripe_length) - ((address - c->offset) % (num_data_stripes * c->chunk_item->stripe_length)); Status = add_partial_stripe(Vcb, c, address, (uint32_t)delta, data); if (!NT_SUCCESS(Status)) { ERR("add_partial_stripe returned %08lx\n", Status); goto exit; } address += delta; length -= (uint32_t)delta; irp_offset += delta; data = (uint8_t*)data + delta; } if (length == 0) { Status = STATUS_SUCCESS; goto exit; } get_raid0_offset(address - c->offset, c->chunk_item->stripe_length, num_data_stripes, &startoff, &startoffstripe); get_raid0_offset(address + length - c->offset - 1, c->chunk_item->stripe_length, num_data_stripes, &endoff, &endoffstripe); pos = 0; while (pos < length) { parity1 = (((address - c->offset + pos) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes; if (pos == 0) { uint16_t stripe = (parity1 + startoffstripe + 2) % c->chunk_item->num_stripes; uint16_t parity2 = (parity1 + 1) % c->chunk_item->num_stripes; ULONG skip, writelen; i = startoffstripe; while (stripe != parity1) { if (i == startoffstripe) { writelen = (ULONG)min(length, c->chunk_item->stripe_length - (startoff % c->chunk_item->stripe_length)); stripes[stripe].start = startoff; stripes[stripe].end = startoff + writelen; pos += writelen; if (pos == length) break; } else { writelen = (ULONG)min(length - pos, c->chunk_item->stripe_length); stripes[stripe].start = startoff - (startoff % c->chunk_item->stripe_length); stripes[stripe].end = stripes[stripe].start + writelen; pos += writelen; if (pos == length) break; } i++; stripe = (stripe + 1) % c->chunk_item->num_stripes; } if (pos == length) break; for (i = 0; i < startoffstripe; i++) { stripe = (parity1 + i + 2) % c->chunk_item->num_stripes; stripes[stripe].start = stripes[stripe].end = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; } stripes[parity1].start = stripes[parity1].end = stripes[parity2].start = stripes[parity2].end = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; if (length - pos > c->chunk_item->num_stripes * num_data_stripes * c->chunk_item->stripe_length) { skip = (ULONG)(((length - pos) / (c->chunk_item->num_stripes * num_data_stripes * c->chunk_item->stripe_length)) - 1); for (i = 0; i < c->chunk_item->num_stripes; i++) { stripes[i].end += skip * c->chunk_item->num_stripes * c->chunk_item->stripe_length; } pos += skip * num_data_stripes * c->chunk_item->num_stripes * c->chunk_item->stripe_length; } } else if (length - pos >= c->chunk_item->stripe_length * num_data_stripes) { for (i = 0; i < c->chunk_item->num_stripes; i++) { stripes[i].end += c->chunk_item->stripe_length; } pos += c->chunk_item->stripe_length * num_data_stripes; } else { uint16_t stripe = (parity1 + 2) % c->chunk_item->num_stripes; i = 0; while (stripe != parity1) { if (endoffstripe == i) { stripes[stripe].end = endoff + 1; break; } else if (endoffstripe > i) stripes[stripe].end = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length; i++; stripe = (stripe + 1) % c->chunk_item->num_stripes; } break; } } parity_start = 0xffffffffffffffff; parity_end = 0; for (i = 0; i < c->chunk_item->num_stripes; i++) { if (stripes[i].start != 0 || stripes[i].end != 0) { parity_start = min(stripes[i].start, parity_start); parity_end = max(stripes[i].end, parity_end); } } if (parity_end == parity_start) { Status = STATUS_SUCCESS; goto exit; } parity1 = (((address - c->offset) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes; stripes[parity1].start = stripes[(parity1 + 1) % c->chunk_item->num_stripes].start = parity_start; parity1 = (((address - c->offset + length - 1) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes; stripes[parity1].end = stripes[(parity1 + 1) % c->chunk_item->num_stripes].end = parity_end; log_stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(log_stripe) * num_data_stripes, ALLOC_TAG); if (!log_stripes) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlZeroMemory(log_stripes, sizeof(log_stripe) * num_data_stripes); for (i = 0; i < num_data_stripes; i++) { log_stripes[i].mdl = IoAllocateMdl(NULL, (ULONG)(parity_end - parity_start), false, false, NULL); if (!log_stripes[i].mdl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } log_stripes[i].mdl->MdlFlags |= MDL_PARTIAL; log_stripes[i].pfns = (PFN_NUMBER*)(log_stripes[i].mdl + 1); } wtc->parity1 = ExAllocatePoolWithTag(NonPagedPool, (ULONG)(parity_end - parity_start), ALLOC_TAG); if (!wtc->parity1) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } wtc->parity2 = ExAllocatePoolWithTag(NonPagedPool, (ULONG)(parity_end - parity_start), ALLOC_TAG); if (!wtc->parity2) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } wtc->parity1_mdl = IoAllocateMdl(wtc->parity1, (ULONG)(parity_end - parity_start), false, false, NULL); if (!wtc->parity1_mdl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } MmBuildMdlForNonPagedPool(wtc->parity1_mdl); wtc->parity2_mdl = IoAllocateMdl(wtc->parity2, (ULONG)(parity_end - parity_start), false, false, NULL); if (!wtc->parity2_mdl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } MmBuildMdlForNonPagedPool(wtc->parity2_mdl); if (file_write) master_mdl = Irp->MdlAddress; else if (((ULONG_PTR)data % PAGE_SIZE) != 0) { wtc->scratch = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG); if (!wtc->scratch) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlCopyMemory(wtc->scratch, data, length); master_mdl = IoAllocateMdl(wtc->scratch, length, false, false, NULL); if (!master_mdl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } MmBuildMdlForNonPagedPool(master_mdl); wtc->mdl = master_mdl; } else { master_mdl = IoAllocateMdl(data, length, false, false, NULL); if (!master_mdl) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Status = STATUS_SUCCESS; try { MmProbeAndLockPages(master_mdl, KernelMode, IoReadAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); IoFreeMdl(master_mdl); goto exit; } wtc->mdl = master_mdl; } pfns = (PFN_NUMBER*)(master_mdl + 1); parity1_pfns = (PFN_NUMBER*)(wtc->parity1_mdl + 1); parity2_pfns = (PFN_NUMBER*)(wtc->parity2_mdl + 1); if (file_write) pfns = &pfns[irp_offset >> PAGE_SHIFT]; for (i = 0; i < c->chunk_item->num_stripes; i++) { if (stripes[i].start != stripes[i].end) { stripes[i].mdl = IoAllocateMdl((uint8_t*)MmGetMdlVirtualAddress(master_mdl) + irp_offset, (ULONG)(stripes[i].end - stripes[i].start), false, false, NULL); if (!stripes[i].mdl) { ERR("IoAllocateMdl failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } } stripeoff = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t) * c->chunk_item->num_stripes, ALLOC_TAG); if (!stripeoff) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } RtlZeroMemory(stripeoff, sizeof(uint64_t) * c->chunk_item->num_stripes); pos = 0; parity_pos = 0; while (pos < length) { PFN_NUMBER* stripe_pfns; parity1 = (((address - c->offset + pos) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes; if (pos == 0) { uint16_t stripe = (parity1 + startoffstripe + 2) % c->chunk_item->num_stripes, parity2; uint32_t writelen = (uint32_t)min(length - pos, min(stripes[stripe].end - stripes[stripe].start, c->chunk_item->stripe_length - (stripes[stripe].start % c->chunk_item->stripe_length))); uint32_t maxwritelen = writelen; stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1); RtlCopyMemory(stripe_pfns, pfns, writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); RtlCopyMemory(log_stripes[startoffstripe].pfns, pfns, writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); log_stripes[startoffstripe].pfns += writelen >> PAGE_SHIFT; stripeoff[stripe] = writelen; pos += writelen; stripe = (stripe + 1) % c->chunk_item->num_stripes; i = startoffstripe + 1; while (stripe != parity1) { stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1); writelen = (uint32_t)min(length - pos, min(stripes[stripe].end - stripes[stripe].start, c->chunk_item->stripe_length)); if (writelen == 0) break; if (writelen > maxwritelen) maxwritelen = writelen; RtlCopyMemory(stripe_pfns, &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); RtlCopyMemory(log_stripes[i].pfns, &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); log_stripes[i].pfns += writelen >> PAGE_SHIFT; stripeoff[stripe] = writelen; pos += writelen; stripe = (stripe + 1) % c->chunk_item->num_stripes; i++; } stripe_pfns = (PFN_NUMBER*)(stripes[parity1].mdl + 1); RtlCopyMemory(stripe_pfns, parity1_pfns, maxwritelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); stripeoff[parity1] = maxwritelen; parity2 = (parity1 + 1) % c->chunk_item->num_stripes; stripe_pfns = (PFN_NUMBER*)(stripes[parity2].mdl + 1); RtlCopyMemory(stripe_pfns, parity2_pfns, maxwritelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); stripeoff[parity2] = maxwritelen; parity_pos = maxwritelen; } else if (length - pos >= c->chunk_item->stripe_length * num_data_stripes) { uint16_t stripe = (parity1 + 2) % c->chunk_item->num_stripes, parity2; i = 0; while (stripe != parity1) { stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1); RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); RtlCopyMemory(log_stripes[i].pfns, &pfns[pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); log_stripes[i].pfns += c->chunk_item->stripe_length >> PAGE_SHIFT; stripeoff[stripe] += c->chunk_item->stripe_length; pos += c->chunk_item->stripe_length; stripe = (stripe + 1) % c->chunk_item->num_stripes; i++; } stripe_pfns = (PFN_NUMBER*)(stripes[parity1].mdl + 1); RtlCopyMemory(&stripe_pfns[stripeoff[parity1] >> PAGE_SHIFT], &parity1_pfns[parity_pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); stripeoff[parity1] += c->chunk_item->stripe_length; parity2 = (parity1 + 1) % c->chunk_item->num_stripes; stripe_pfns = (PFN_NUMBER*)(stripes[parity2].mdl + 1); RtlCopyMemory(&stripe_pfns[stripeoff[parity2] >> PAGE_SHIFT], &parity2_pfns[parity_pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT)); stripeoff[parity2] += c->chunk_item->stripe_length; parity_pos += c->chunk_item->stripe_length; } else { uint16_t stripe = (parity1 + 2) % c->chunk_item->num_stripes, parity2; uint32_t writelen, maxwritelen = 0; i = 0; while (pos < length) { stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1); writelen = (uint32_t)min(length - pos, min(stripes[stripe].end - stripes[stripe].start, c->chunk_item->stripe_length)); if (writelen == 0) break; if (writelen > maxwritelen) maxwritelen = writelen; RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); RtlCopyMemory(log_stripes[i].pfns, &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); log_stripes[i].pfns += writelen >> PAGE_SHIFT; stripeoff[stripe] += writelen; pos += writelen; stripe = (stripe + 1) % c->chunk_item->num_stripes; i++; } stripe_pfns = (PFN_NUMBER*)(stripes[parity1].mdl + 1); RtlCopyMemory(&stripe_pfns[stripeoff[parity1] >> PAGE_SHIFT], &parity1_pfns[parity_pos >> PAGE_SHIFT], maxwritelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); parity2 = (parity1 + 1) % c->chunk_item->num_stripes; stripe_pfns = (PFN_NUMBER*)(stripes[parity2].mdl + 1); RtlCopyMemory(&stripe_pfns[stripeoff[parity2] >> PAGE_SHIFT], &parity2_pfns[parity_pos >> PAGE_SHIFT], maxwritelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT); } } for (i = 0; i < num_data_stripes; i++) { uint8_t* ss = MmGetSystemAddressForMdlSafe(log_stripes[c->chunk_item->num_stripes - 3 - i].mdl, priority); if (i == 0) { RtlCopyMemory(wtc->parity1, ss, (ULONG)(parity_end - parity_start)); RtlCopyMemory(wtc->parity2, ss, (ULONG)(parity_end - parity_start)); } else { do_xor(wtc->parity1, ss, (uint32_t)(parity_end - parity_start)); galois_double(wtc->parity2, (uint32_t)(parity_end - parity_start)); do_xor(wtc->parity2, ss, (uint32_t)(parity_end - parity_start)); } } Status = STATUS_SUCCESS; exit: if (log_stripes) { for (i = 0; i < num_data_stripes; i++) { if (log_stripes[i].mdl) IoFreeMdl(log_stripes[i].mdl); } ExFreePool(log_stripes); } if (stripeoff) ExFreePool(stripeoff); return Status; } __attribute__((nonnull(1,3,5))) NTSTATUS 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, _In_opt_ PIRP Irp, _In_opt_ chunk* c, _In_ bool file_write, _In_ uint64_t irp_offset, _In_ ULONG priority) { NTSTATUS Status; uint32_t i; CHUNK_ITEM_STRIPE* cis; write_stripe* stripes = NULL; uint64_t total_writing = 0; ULONG allowed_missing, missing; TRACE("(%p, %I64x, %p, %x)\n", Vcb, address, data, length); if (!c) { c = get_chunk_from_address(Vcb, address); if (!c) { ERR("could not get chunk for address %I64x\n", address); return STATUS_INTERNAL_ERROR; } } stripes = ExAllocatePoolWithTag(PagedPool, sizeof(write_stripe) * c->chunk_item->num_stripes, ALLOC_TAG); if (!stripes) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(stripes, sizeof(write_stripe) * c->chunk_item->num_stripes); cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1]; if (c->chunk_item->type & BLOCK_FLAG_RAID0) { Status = prepare_raid0_write(c, address, data, length, stripes, file_write ? Irp : NULL, irp_offset, wtc); if (!NT_SUCCESS(Status)) { ERR("prepare_raid0_write returned %08lx\n", Status); goto prepare_failed; } allowed_missing = 0; } else if (c->chunk_item->type & BLOCK_FLAG_RAID10) { Status = prepare_raid10_write(c, address, data, length, stripes, file_write ? Irp : NULL, irp_offset, wtc); if (!NT_SUCCESS(Status)) { ERR("prepare_raid10_write returned %08lx\n", Status); goto prepare_failed; } allowed_missing = 1; } else if (c->chunk_item->type & BLOCK_FLAG_RAID5) { Status = prepare_raid5_write(Vcb, c, address, data, length, stripes, file_write ? Irp : NULL, irp_offset, priority, wtc); if (!NT_SUCCESS(Status)) { ERR("prepare_raid5_write returned %08lx\n", Status); goto prepare_failed; } allowed_missing = 1; } else if (c->chunk_item->type & BLOCK_FLAG_RAID6) { Status = prepare_raid6_write(Vcb, c, address, data, length, stripes, file_write ? Irp : NULL, irp_offset, priority, wtc); if (!NT_SUCCESS(Status)) { ERR("prepare_raid6_write returned %08lx\n", Status); goto prepare_failed; } allowed_missing = 2; } else { // write same data to every location - SINGLE, DUP, RAID1, RAID1C3, RAID1C4 for (i = 0; i < c->chunk_item->num_stripes; i++) { stripes[i].start = address - c->offset; stripes[i].end = stripes[i].start + length; stripes[i].data = data; stripes[i].irp_offset = irp_offset; if (c->devices[i]->devobj) { if (file_write) { uint8_t* va; ULONG writelen = (ULONG)(stripes[i].end - stripes[i].start); va = (uint8_t*)MmGetMdlVirtualAddress(Irp->MdlAddress) + stripes[i].irp_offset; stripes[i].mdl = IoAllocateMdl(va, writelen, false, false, NULL); if (!stripes[i].mdl) { ERR("IoAllocateMdl failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto prepare_failed; } IoBuildPartialMdl(Irp->MdlAddress, stripes[i].mdl, va, writelen); } else { stripes[i].mdl = IoAllocateMdl(stripes[i].data, (ULONG)(stripes[i].end - stripes[i].start), false, false, NULL); if (!stripes[i].mdl) { ERR("IoAllocateMdl failed\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto prepare_failed; } Status = STATUS_SUCCESS; try { MmProbeAndLockPages(stripes[i].mdl, KernelMode, IoReadAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); IoFreeMdl(stripes[i].mdl); stripes[i].mdl = NULL; goto prepare_failed; } } } } allowed_missing = c->chunk_item->num_stripes - 1; } missing = 0; for (i = 0; i < c->chunk_item->num_stripes; i++) { if (!c->devices[i]->devobj) missing++; } if (missing > allowed_missing) { ERR("cannot write as %lu missing devices (maximum %lu)\n", missing, allowed_missing); Status = STATUS_DEVICE_NOT_READY; goto prepare_failed; } for (i = 0; i < c->chunk_item->num_stripes; i++) { write_data_stripe* stripe; PIO_STACK_LOCATION IrpSp; stripe = ExAllocatePoolWithTag(NonPagedPool, sizeof(write_data_stripe), ALLOC_TAG); if (!stripe) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } if (stripes[i].start == stripes[i].end || !c->devices[i]->devobj) { stripe->status = WriteDataStatus_Ignore; stripe->Irp = NULL; stripe->buf = stripes[i].data; stripe->mdl = NULL; } else { stripe->context = (struct _write_data_context*)wtc; stripe->buf = stripes[i].data; stripe->device = c->devices[i]; RtlZeroMemory(&stripe->iosb, sizeof(IO_STATUS_BLOCK)); stripe->status = WriteDataStatus_Pending; stripe->mdl = stripes[i].mdl; if (!Irp) { stripe->Irp = IoAllocateIrp(stripe->device->devobj->StackSize, false); if (!stripe->Irp) { ERR("IoAllocateIrp failed\n"); ExFreePool(stripe); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } } else { stripe->Irp = IoMakeAssociatedIrp(Irp, stripe->device->devobj->StackSize); if (!stripe->Irp) { ERR("IoMakeAssociatedIrp failed\n"); ExFreePool(stripe); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } } IrpSp = IoGetNextIrpStackLocation(stripe->Irp); IrpSp->MajorFunction = IRP_MJ_WRITE; IrpSp->FileObject = stripe->device->fileobj; if (stripe->device->devobj->Flags & DO_BUFFERED_IO) { stripe->Irp->AssociatedIrp.SystemBuffer = MmGetSystemAddressForMdlSafe(stripes[i].mdl, priority); stripe->Irp->Flags = IRP_BUFFERED_IO; } else if (stripe->device->devobj->Flags & DO_DIRECT_IO) stripe->Irp->MdlAddress = stripe->mdl; else stripe->Irp->UserBuffer = MmGetSystemAddressForMdlSafe(stripes[i].mdl, priority); #ifdef DEBUG_PARANOID if (stripes[i].end < stripes[i].start) { ERR("trying to write stripe with negative length (%I64x < %I64x)\n", stripes[i].end, stripes[i].start); int3; } #endif IrpSp->Parameters.Write.Length = (ULONG)(stripes[i].end - stripes[i].start); IrpSp->Parameters.Write.ByteOffset.QuadPart = stripes[i].start + cis[i].offset; total_writing += IrpSp->Parameters.Write.Length; stripe->Irp->UserIosb = &stripe->iosb; wtc->stripes_left++; IoSetCompletionRoutine(stripe->Irp, write_data_completion, stripe, true, true, true); } InsertTailList(&wtc->stripes, &stripe->list_entry); } if (diskacc) fFsRtlUpdateDiskCounters(0, total_writing); Status = STATUS_SUCCESS; end: if (stripes) ExFreePool(stripes); if (!NT_SUCCESS(Status)) free_write_data_stripes(wtc); return Status; prepare_failed: for (i = 0; i < c->chunk_item->num_stripes; i++) { if (stripes[i].mdl && (i == 0 || stripes[i].mdl != stripes[i-1].mdl)) { if (stripes[i].mdl->MdlFlags & MDL_PAGES_LOCKED) MmUnlockPages(stripes[i].mdl); IoFreeMdl(stripes[i].mdl); } } if (wtc->parity1_mdl) { if (wtc->parity1_mdl->MdlFlags & MDL_PAGES_LOCKED) MmUnlockPages(wtc->parity1_mdl); IoFreeMdl(wtc->parity1_mdl); wtc->parity1_mdl = NULL; } if (wtc->parity2_mdl) { if (wtc->parity2_mdl->MdlFlags & MDL_PAGES_LOCKED) MmUnlockPages(wtc->parity2_mdl); IoFreeMdl(wtc->parity2_mdl); wtc->parity2_mdl = NULL; } if (wtc->mdl) { if (wtc->mdl->MdlFlags & MDL_PAGES_LOCKED) MmUnlockPages(wtc->mdl); IoFreeMdl(wtc->mdl); wtc->mdl = NULL; } if (wtc->parity1) { ExFreePool(wtc->parity1); wtc->parity1 = NULL; } if (wtc->parity2) { ExFreePool(wtc->parity2); wtc->parity2 = NULL; } if (wtc->scratch) { ExFreePool(wtc->scratch); wtc->scratch = NULL; } ExFreePool(stripes); return Status; } __attribute__((nonnull(1,4,5))) void get_raid56_lock_range(chunk* c, uint64_t address, uint64_t length, uint64_t* lockaddr, uint64_t* locklen) { uint64_t startoff, endoff; uint16_t startoffstripe, endoffstripe, datastripes; datastripes = c->chunk_item->num_stripes - (c->chunk_item->type & BLOCK_FLAG_RAID5 ? 1 : 2); get_raid0_offset(address - c->offset, c->chunk_item->stripe_length, datastripes, &startoff, &startoffstripe); get_raid0_offset(address + length - c->offset - 1, c->chunk_item->stripe_length, datastripes, &endoff, &endoffstripe); startoff -= startoff % c->chunk_item->stripe_length; endoff = sector_align(endoff, c->chunk_item->stripe_length); *lockaddr = c->offset + (startoff * datastripes); *locklen = (endoff - startoff) * datastripes; } __attribute__((nonnull(1,3))) NTSTATUS 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) { write_data_context wtc; NTSTATUS Status; uint64_t lockaddr, locklen; KeInitializeEvent(&wtc.Event, NotificationEvent, false); InitializeListHead(&wtc.stripes); wtc.stripes_left = 0; wtc.parity1 = wtc.parity2 = wtc.scratch = NULL; wtc.mdl = wtc.parity1_mdl = wtc.parity2_mdl = NULL; if (!c) { c = get_chunk_from_address(Vcb, address); if (!c) { ERR("could not get chunk for address %I64x\n", address); return STATUS_INTERNAL_ERROR; } } if (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6) { get_raid56_lock_range(c, address, length, &lockaddr, &locklen); chunk_lock_range(Vcb, c, lockaddr, locklen); } try { Status = write_data(Vcb, address, data, length, &wtc, Irp, c, file_write, irp_offset, priority); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("write_data returned %08lx\n", Status); if (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6) chunk_unlock_range(Vcb, c, lockaddr, locklen); free_write_data_stripes(&wtc); return Status; } if (wtc.stripes.Flink != &wtc.stripes) { // launch writes and wait LIST_ENTRY* le = wtc.stripes.Flink; bool no_wait = true; while (le != &wtc.stripes) { write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry); if (stripe->status != WriteDataStatus_Ignore) { IoCallDriver(stripe->device->devobj, stripe->Irp); no_wait = false; } le = le->Flink; } if (!no_wait) KeWaitForSingleObject(&wtc.Event, Executive, KernelMode, false, NULL); le = wtc.stripes.Flink; while (le != &wtc.stripes) { write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry); if (stripe->status != WriteDataStatus_Ignore && !NT_SUCCESS(stripe->iosb.Status)) { Status = stripe->iosb.Status; log_device_error(Vcb, stripe->device, BTRFS_DEV_STAT_WRITE_ERRORS); break; } le = le->Flink; } free_write_data_stripes(&wtc); } if (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6) chunk_unlock_range(Vcb, c, lockaddr, locklen); return Status; } __attribute__((nonnull(2,3))) _Function_class_(IO_COMPLETION_ROUTINE) static NTSTATUS __stdcall write_data_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) { write_data_stripe* stripe = conptr; write_data_context* context = (write_data_context*)stripe->context; LIST_ENTRY* le; UNUSED(DeviceObject); // FIXME - we need a lock here if (stripe->status == WriteDataStatus_Cancelling) { stripe->status = WriteDataStatus_Cancelled; goto end; } stripe->iosb = Irp->IoStatus; if (NT_SUCCESS(Irp->IoStatus.Status)) { stripe->status = WriteDataStatus_Success; } else { le = context->stripes.Flink; stripe->status = WriteDataStatus_Error; while (le != &context->stripes) { write_data_stripe* s2 = CONTAINING_RECORD(le, write_data_stripe, list_entry); if (s2->status == WriteDataStatus_Pending) { s2->status = WriteDataStatus_Cancelling; IoCancelIrp(s2->Irp); } le = le->Flink; } } end: if (InterlockedDecrement(&context->stripes_left) == 0) KeSetEvent(&context->Event, 0, false); return STATUS_MORE_PROCESSING_REQUIRED; } __attribute__((nonnull(1))) void free_write_data_stripes(write_data_context* wtc) { LIST_ENTRY* le; PMDL last_mdl = NULL; if (wtc->parity1_mdl) { if (wtc->parity1_mdl->MdlFlags & MDL_PAGES_LOCKED) MmUnlockPages(wtc->parity1_mdl); IoFreeMdl(wtc->parity1_mdl); } if (wtc->parity2_mdl) { if (wtc->parity2_mdl->MdlFlags & MDL_PAGES_LOCKED) MmUnlockPages(wtc->parity2_mdl); IoFreeMdl(wtc->parity2_mdl); } if (wtc->mdl) { if (wtc->mdl->MdlFlags & MDL_PAGES_LOCKED) MmUnlockPages(wtc->mdl); IoFreeMdl(wtc->mdl); } if (wtc->parity1) ExFreePool(wtc->parity1); if (wtc->parity2) ExFreePool(wtc->parity2); if (wtc->scratch) ExFreePool(wtc->scratch); le = wtc->stripes.Flink; while (le != &wtc->stripes) { write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry); if (stripe->mdl && stripe->mdl != last_mdl) { if (stripe->mdl->MdlFlags & MDL_PAGES_LOCKED) MmUnlockPages(stripe->mdl); IoFreeMdl(stripe->mdl); } last_mdl = stripe->mdl; if (stripe->Irp) IoFreeIrp(stripe->Irp); le = le->Flink; } while (!IsListEmpty(&wtc->stripes)) { write_data_stripe* stripe = CONTAINING_RECORD(RemoveHeadList(&wtc->stripes), write_data_stripe, list_entry); ExFreePool(stripe); } } __attribute__((nonnull(1,2,3))) void add_extent(_In_ fcb* fcb, _In_ LIST_ENTRY* prevextle, _In_ __drv_aliasesMem extent* newext) { LIST_ENTRY* le = prevextle->Flink; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (ext->offset >= newext->offset) { InsertHeadList(ext->list_entry.Blink, &newext->list_entry); return; } le = le->Flink; } InsertTailList(&fcb->extents, &newext->list_entry); } __attribute__((nonnull(1,2,6))) NTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, uint64_t start_data, uint64_t end_data, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; LIST_ENTRY* le; le = fcb->extents.Flink; while (le != &fcb->extents) { LIST_ENTRY* le2 = le->Flink; extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (!ext->ignore) { EXTENT_DATA* ed = &ext->extent_data; uint64_t len; if (ed->type == EXTENT_TYPE_INLINE) len = ed->decoded_size; else len = ((EXTENT_DATA2*)ed->data)->num_bytes; if (ext->offset < end_data && ext->offset + len > start_data) { if (ed->type == EXTENT_TYPE_INLINE) { if (start_data <= ext->offset && end_data >= ext->offset + len) { // remove all remove_fcb_extent(fcb, ext, rollback); fcb->inode_item.st_blocks -= len; fcb->inode_item_changed = true; } else { ERR("trying to split inline extent\n"); #ifdef DEBUG_PARANOID int3; #endif return STATUS_INTERNAL_ERROR; } } else { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; if (start_data <= ext->offset && end_data >= ext->offset + len) { // remove all if (ed2->size != 0) { chunk* c; fcb->inode_item.st_blocks -= len; fcb->inode_item_changed = true; c = get_chunk_from_address(Vcb, ed2->address); if (!c) { ERR("get_chunk_from_address(%I64x) failed\n", ed2->address); } else { Status = update_changed_extent_ref(Vcb, c, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, -1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp); if (!NT_SUCCESS(Status)) { ERR("update_changed_extent_ref returned %08lx\n", Status); goto end; } } } remove_fcb_extent(fcb, ext, rollback); } else if (start_data <= ext->offset && end_data < ext->offset + len) { // remove beginning EXTENT_DATA2* ned2; extent* newext; if (ed2->size != 0) { fcb->inode_item.st_blocks -= end_data - ext->offset; fcb->inode_item_changed = true; } newext = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG); if (!newext) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } ned2 = (EXTENT_DATA2*)newext->extent_data.data; newext->extent_data.generation = Vcb->superblock.generation; newext->extent_data.decoded_size = ed->decoded_size; newext->extent_data.compression = ed->compression; newext->extent_data.encryption = ed->encryption; newext->extent_data.encoding = ed->encoding; newext->extent_data.type = ed->type; ned2->address = ed2->address; ned2->size = ed2->size; ned2->offset = ed2->offset + (end_data - ext->offset); ned2->num_bytes = ed2->num_bytes - (end_data - ext->offset); newext->offset = end_data; newext->datalen = sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2); newext->unique = ext->unique; newext->ignore = false; newext->inserted = true; if (ext->csum) { if (ed->compression == BTRFS_COMPRESSION_NONE) { newext->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ned2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG); if (!newext->csum) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(newext); goto end; } RtlCopyMemory(newext->csum, (uint8_t*)ext->csum + (((end_data - ext->offset) * Vcb->csum_size) >> Vcb->sector_shift), (ULONG)((ned2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift)); } else { newext->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG); if (!newext->csum) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(newext); goto end; } RtlCopyMemory(newext->csum, ext->csum, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift)); } } else newext->csum = NULL; add_extent(fcb, &ext->list_entry, newext); remove_fcb_extent(fcb, ext, rollback); } else if (start_data > ext->offset && end_data >= ext->offset + len) { // remove end EXTENT_DATA2* ned2; extent* newext; if (ed2->size != 0) { fcb->inode_item.st_blocks -= ext->offset + len - start_data; fcb->inode_item_changed = true; } newext = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG); if (!newext) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } ned2 = (EXTENT_DATA2*)newext->extent_data.data; newext->extent_data.generation = Vcb->superblock.generation; newext->extent_data.decoded_size = ed->decoded_size; newext->extent_data.compression = ed->compression; newext->extent_data.encryption = ed->encryption; newext->extent_data.encoding = ed->encoding; newext->extent_data.type = ed->type; ned2->address = ed2->address; ned2->size = ed2->size; ned2->offset = ed2->offset; ned2->num_bytes = start_data - ext->offset; newext->offset = ext->offset; newext->datalen = sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2); newext->unique = ext->unique; newext->ignore = false; newext->inserted = true; if (ext->csum) { if (ed->compression == BTRFS_COMPRESSION_NONE) { newext->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ned2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG); if (!newext->csum) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(newext); goto end; } RtlCopyMemory(newext->csum, ext->csum, (ULONG)((ned2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift)); } else { newext->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG); if (!newext->csum) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(newext); goto end; } RtlCopyMemory(newext->csum, ext->csum, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift)); } } else newext->csum = NULL; InsertHeadList(&ext->list_entry, &newext->list_entry); remove_fcb_extent(fcb, ext, rollback); } else if (start_data > ext->offset && end_data < ext->offset + len) { // remove middle EXTENT_DATA2 *neda2, *nedb2; extent *newext1, *newext2; if (ed2->size != 0) { chunk* c; fcb->inode_item.st_blocks -= end_data - start_data; fcb->inode_item_changed = true; c = get_chunk_from_address(Vcb, ed2->address); if (!c) { ERR("get_chunk_from_address(%I64x) failed\n", ed2->address); } else { Status = update_changed_extent_ref(Vcb, c, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, 1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp); if (!NT_SUCCESS(Status)) { ERR("update_changed_extent_ref returned %08lx\n", Status); goto end; } } } newext1 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG); if (!newext1) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } newext2 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG); if (!newext2) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(newext1); goto end; } neda2 = (EXTENT_DATA2*)newext1->extent_data.data; newext1->extent_data.generation = Vcb->superblock.generation; newext1->extent_data.decoded_size = ed->decoded_size; newext1->extent_data.compression = ed->compression; newext1->extent_data.encryption = ed->encryption; newext1->extent_data.encoding = ed->encoding; newext1->extent_data.type = ed->type; neda2->address = ed2->address; neda2->size = ed2->size; neda2->offset = ed2->offset; neda2->num_bytes = start_data - ext->offset; nedb2 = (EXTENT_DATA2*)newext2->extent_data.data; newext2->extent_data.generation = Vcb->superblock.generation; newext2->extent_data.decoded_size = ed->decoded_size; newext2->extent_data.compression = ed->compression; newext2->extent_data.encryption = ed->encryption; newext2->extent_data.encoding = ed->encoding; newext2->extent_data.type = ed->type; nedb2->address = ed2->address; nedb2->size = ed2->size; nedb2->offset = ed2->offset + (end_data - ext->offset); nedb2->num_bytes = ext->offset + len - end_data; newext1->offset = ext->offset; newext1->datalen = sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2); newext1->unique = ext->unique; newext1->ignore = false; newext1->inserted = true; newext2->offset = end_data; newext2->datalen = sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2); newext2->unique = ext->unique; newext2->ignore = false; newext2->inserted = true; if (ext->csum) { if (ed->compression == BTRFS_COMPRESSION_NONE) { newext1->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((neda2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG); if (!newext1->csum) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(newext1); ExFreePool(newext2); goto end; } newext2->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((nedb2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG); if (!newext2->csum) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(newext1->csum); ExFreePool(newext1); ExFreePool(newext2); goto end; } RtlCopyMemory(newext1->csum, ext->csum, (ULONG)((neda2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift)); RtlCopyMemory(newext2->csum, (uint8_t*)ext->csum + (((end_data - ext->offset) * Vcb->csum_size) >> Vcb->sector_shift), (ULONG)((nedb2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift)); } else { newext1->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG); if (!newext1->csum) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(newext1); ExFreePool(newext2); goto end; } newext2->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG); if (!newext2->csum) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; ExFreePool(newext1->csum); ExFreePool(newext1); ExFreePool(newext2); goto end; } RtlCopyMemory(newext1->csum, ext->csum, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift)); RtlCopyMemory(newext2->csum, ext->csum, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift)); } } else { newext1->csum = NULL; newext2->csum = NULL; } InsertHeadList(&ext->list_entry, &newext1->list_entry); add_extent(fcb, &newext1->list_entry, newext2); remove_fcb_extent(fcb, ext, rollback); } } } } le = le2; } Status = STATUS_SUCCESS; end: fcb->extents_changed = true; mark_fcb_dirty(fcb); return Status; } __attribute__((nonnull(1,2,3))) static void add_insert_extent_rollback(LIST_ENTRY* rollback, fcb* fcb, extent* ext) { rollback_extent* re; re = ExAllocatePoolWithTag(NonPagedPool, sizeof(rollback_extent), ALLOC_TAG); if (!re) { ERR("out of memory\n"); return; } re->fcb = fcb; re->ext = ext; add_rollback(rollback, ROLLBACK_INSERT_EXTENT, re); } #ifdef _MSC_VER #pragma warning(push) #pragma warning(suppress: 28194) #endif __attribute__((nonnull(1,3,7))) NTSTATUS add_extent_to_fcb(_In_ fcb* fcb, _In_ uint64_t offset, _In_reads_bytes_(edsize) EXTENT_DATA* ed, _In_ uint16_t edsize, _In_ bool unique, _In_opt_ _When_(return >= 0, __drv_aliasesMem) void* csum, _In_ LIST_ENTRY* rollback) { extent* ext; LIST_ENTRY* le; ext = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + edsize, ALLOC_TAG); if (!ext) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ext->offset = offset; ext->datalen = edsize; ext->unique = unique; ext->ignore = false; ext->inserted = true; ext->csum = csum; RtlCopyMemory(&ext->extent_data, ed, edsize); le = fcb->extents.Flink; while (le != &fcb->extents) { extent* oldext = CONTAINING_RECORD(le, extent, list_entry); if (oldext->offset >= offset) { InsertHeadList(le->Blink, &ext->list_entry); goto end; } le = le->Flink; } InsertTailList(&fcb->extents, &ext->list_entry); end: add_insert_extent_rollback(rollback, fcb, ext); return STATUS_SUCCESS; } #ifdef _MSC_VER #pragma warning(pop) #endif __attribute__((nonnull(1, 2, 3))) static void remove_fcb_extent(fcb* fcb, extent* ext, LIST_ENTRY* rollback) { if (!ext->ignore) { rollback_extent* re; ext->ignore = true; re = ExAllocatePoolWithTag(NonPagedPool, sizeof(rollback_extent), ALLOC_TAG); if (!re) { ERR("out of memory\n"); return; } re->fcb = fcb; re->ext = ext; add_rollback(rollback, ROLLBACK_DELETE_EXTENT, re); } } _Requires_lock_held_(c->lock) _When_(return != 0, _Releases_lock_(c->lock)) __attribute__((nonnull(1,2,3,9))) bool 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, _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) { uint64_t address; NTSTATUS Status; EXTENT_DATA* ed; EXTENT_DATA2* ed2; uint16_t edsize = (uint16_t)(offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)); void* csum = NULL; 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); if (!find_data_address_in_chunk(Vcb, c, length, &address)) return false; // add extent data to inode ed = ExAllocatePoolWithTag(PagedPool, edsize, ALLOC_TAG); if (!ed) { ERR("out of memory\n"); return false; } ed->generation = Vcb->superblock.generation; ed->decoded_size = decoded_size; ed->compression = compression; ed->encryption = BTRFS_ENCRYPTION_NONE; ed->encoding = BTRFS_ENCODING_NONE; ed->type = prealloc ? EXTENT_TYPE_PREALLOC : EXTENT_TYPE_REGULAR; ed2 = (EXTENT_DATA2*)ed->data; ed2->address = address; ed2->size = length; ed2->offset = 0; ed2->num_bytes = decoded_size; if (!prealloc && data && !(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) { ULONG sl = (ULONG)(length >> Vcb->sector_shift); csum = ExAllocatePoolWithTag(PagedPool, sl * Vcb->csum_size, ALLOC_TAG); if (!csum) { ERR("out of memory\n"); ExFreePool(ed); return false; } do_calc_job(Vcb, data, sl, csum); } Status = add_extent_to_fcb(fcb, start_data, ed, edsize, true, csum, rollback); if (!NT_SUCCESS(Status)) { ERR("add_extent_to_fcb returned %08lx\n", Status); if (csum) ExFreePool(csum); ExFreePool(ed); return false; } ExFreePool(ed); c->used += length; space_list_subtract(c, address, length, rollback); fcb->inode_item.st_blocks += decoded_size; fcb->extents_changed = true; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); ExAcquireResourceExclusiveLite(&c->changed_extents_lock, true); add_changed_extent_ref(c, address, length, fcb->subvol->id, fcb->inode, start_data, 1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM); ExReleaseResourceLite(&c->changed_extents_lock); release_chunk_lock(c, Vcb); if (data) { Status = write_data_complete(Vcb, address, data, (uint32_t)length, Irp, NULL, file_write, irp_offset, fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? HighPagePriority : NormalPagePriority); if (!NT_SUCCESS(Status)) ERR("write_data_complete returned %08lx\n", Status); } return true; } __attribute__((nonnull(1,2,5,7,10))) static bool try_extend_data(device_extension* Vcb, fcb* fcb, uint64_t start_data, uint64_t length, void* data, PIRP Irp, uint64_t* written, bool file_write, uint64_t irp_offset, LIST_ENTRY* rollback) { bool success = false; EXTENT_DATA* ed; EXTENT_DATA2* ed2; chunk* c; LIST_ENTRY* le; extent* ext = NULL; le = fcb->extents.Flink; while (le != &fcb->extents) { extent* nextext = CONTAINING_RECORD(le, extent, list_entry); if (!nextext->ignore) { if (nextext->offset == start_data) { ext = nextext; break; } else if (nextext->offset > start_data) break; ext = nextext; } le = le->Flink; } if (!ext) return false; ed = &ext->extent_data; if (ed->type != EXTENT_TYPE_REGULAR && ed->type != EXTENT_TYPE_PREALLOC) { TRACE("not extending extent which is not regular or prealloc\n"); return false; } ed2 = (EXTENT_DATA2*)ed->data; if (ext->offset + ed2->num_bytes != start_data) { TRACE("last EXTENT_DATA does not run up to start_data (%I64x + %I64x != %I64x)\n", ext->offset, ed2->num_bytes, start_data); return false; } c = get_chunk_from_address(Vcb, ed2->address); if (c->reloc || c->readonly || c->chunk_item->type != Vcb->data_flags) return false; acquire_chunk_lock(c, Vcb); if (length > c->chunk_item->size - c->used) { release_chunk_lock(c, Vcb); return false; } if (!c->cache_loaded) { NTSTATUS Status = load_cache_chunk(Vcb, c, NULL); if (!NT_SUCCESS(Status)) { ERR("load_cache_chunk returned %08lx\n", Status); release_chunk_lock(c, Vcb); return false; } } le = c->space.Flink; while (le != &c->space) { space* s = CONTAINING_RECORD(le, space, list_entry); if (s->address == ed2->address + ed2->size) { uint64_t newlen = min(min(s->size, length), MAX_EXTENT_SIZE); success = insert_extent_chunk(Vcb, fcb, c, start_data, newlen, false, data, Irp, rollback, BTRFS_COMPRESSION_NONE, newlen, file_write, irp_offset); if (success) *written += newlen; else release_chunk_lock(c, Vcb); return success; } else if (s->address > ed2->address + ed2->size) break; le = le->Flink; } release_chunk_lock(c, Vcb); return false; } __attribute__((nonnull(1))) static NTSTATUS insert_chunk_fragmented(fcb* fcb, uint64_t start, uint64_t length, uint8_t* data, bool prealloc, LIST_ENTRY* rollback) { LIST_ENTRY* le; uint64_t flags = fcb->Vcb->data_flags; bool page_file = fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE; NTSTATUS Status; chunk* c; ExAcquireResourceSharedLite(&fcb->Vcb->chunk_lock, true); // first create as many chunks as we can do { Status = alloc_chunk(fcb->Vcb, flags, &c, false); } while (NT_SUCCESS(Status)); if (Status != STATUS_DISK_FULL) { ERR("alloc_chunk returned %08lx\n", Status); ExReleaseResourceLite(&fcb->Vcb->chunk_lock); return Status; } le = fcb->Vcb->chunks.Flink; while (le != &fcb->Vcb->chunks) { c = CONTAINING_RECORD(le, chunk, list_entry); if (!c->readonly && !c->reloc) { acquire_chunk_lock(c, fcb->Vcb); if (c->chunk_item->type == flags) { while (!IsListEmpty(&c->space_size) && length > 0) { space* s = CONTAINING_RECORD(c->space_size.Flink, space, list_entry_size); uint64_t extlen = min(length, s->size); if (insert_extent_chunk(fcb->Vcb, fcb, c, start, extlen, prealloc && !page_file, data, NULL, rollback, BTRFS_COMPRESSION_NONE, extlen, false, 0)) { start += extlen; length -= extlen; if (data) data += extlen; acquire_chunk_lock(c, fcb->Vcb); } } } release_chunk_lock(c, fcb->Vcb); if (length == 0) break; } le = le->Flink; } ExReleaseResourceLite(&fcb->Vcb->chunk_lock); return length == 0 ? STATUS_SUCCESS : STATUS_DISK_FULL; } __attribute__((nonnull(1,4))) static NTSTATUS insert_prealloc_extent(fcb* fcb, uint64_t start, uint64_t length, LIST_ENTRY* rollback) { LIST_ENTRY* le; chunk* c; uint64_t flags; NTSTATUS Status; bool page_file = fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE; flags = fcb->Vcb->data_flags; do { uint64_t extlen = min(MAX_EXTENT_SIZE, length); ExAcquireResourceSharedLite(&fcb->Vcb->chunk_lock, true); le = fcb->Vcb->chunks.Flink; while (le != &fcb->Vcb->chunks) { c = CONTAINING_RECORD(le, chunk, list_entry); if (!c->readonly && !c->reloc) { acquire_chunk_lock(c, fcb->Vcb); if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= extlen) { if (insert_extent_chunk(fcb->Vcb, fcb, c, start, extlen, !page_file, NULL, NULL, rollback, BTRFS_COMPRESSION_NONE, extlen, false, 0)) { ExReleaseResourceLite(&fcb->Vcb->chunk_lock); goto cont; } } release_chunk_lock(c, fcb->Vcb); } le = le->Flink; } ExReleaseResourceLite(&fcb->Vcb->chunk_lock); ExAcquireResourceExclusiveLite(&fcb->Vcb->chunk_lock, true); Status = alloc_chunk(fcb->Vcb, flags, &c, false); ExReleaseResourceLite(&fcb->Vcb->chunk_lock); if (!NT_SUCCESS(Status)) { ERR("alloc_chunk returned %08lx\n", Status); goto end; } acquire_chunk_lock(c, fcb->Vcb); if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= extlen) { if (insert_extent_chunk(fcb->Vcb, fcb, c, start, extlen, !page_file, NULL, NULL, rollback, BTRFS_COMPRESSION_NONE, extlen, false, 0)) goto cont; } release_chunk_lock(c, fcb->Vcb); Status = insert_chunk_fragmented(fcb, start, length, NULL, true, rollback); if (!NT_SUCCESS(Status)) ERR("insert_chunk_fragmented returned %08lx\n", Status); goto end; cont: length -= extlen; start += extlen; } while (length > 0); Status = STATUS_SUCCESS; end: return Status; } __attribute__((nonnull(1,2,5,9))) static NTSTATUS insert_extent(device_extension* Vcb, fcb* fcb, uint64_t start_data, uint64_t length, void* data, PIRP Irp, bool file_write, uint64_t irp_offset, LIST_ENTRY* rollback) { NTSTATUS Status; LIST_ENTRY* le; chunk* c; uint64_t flags, orig_length = length, written = 0; TRACE("(%p, (%I64x, %I64x), %I64x, %I64x, %p)\n", Vcb, fcb->subvol->id, fcb->inode, start_data, length, data); if (start_data > 0) { try_extend_data(Vcb, fcb, start_data, length, data, Irp, &written, file_write, irp_offset, rollback); if (written == length) return STATUS_SUCCESS; else if (written > 0) { start_data += written; irp_offset += written; length -= written; data = &((uint8_t*)data)[written]; } } flags = Vcb->data_flags; while (written < orig_length) { uint64_t newlen = min(length, MAX_EXTENT_SIZE); bool done = false; // Rather than necessarily writing the whole extent at once, we deal with it in blocks of 128 MB. // First, see if we can write the extent part to an existing chunk. ExAcquireResourceSharedLite(&Vcb->chunk_lock, true); le = Vcb->chunks.Flink; while (le != &Vcb->chunks) { c = CONTAINING_RECORD(le, chunk, list_entry); if (!c->readonly && !c->reloc) { acquire_chunk_lock(c, Vcb); if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= newlen && insert_extent_chunk(Vcb, fcb, c, start_data, newlen, false, data, Irp, rollback, BTRFS_COMPRESSION_NONE, newlen, file_write, irp_offset)) { written += newlen; if (written == orig_length) { ExReleaseResourceLite(&Vcb->chunk_lock); return STATUS_SUCCESS; } else { done = true; start_data += newlen; irp_offset += newlen; length -= newlen; data = &((uint8_t*)data)[newlen]; break; } } else release_chunk_lock(c, Vcb); } le = le->Flink; } ExReleaseResourceLite(&Vcb->chunk_lock); if (done) continue; // Otherwise, see if we can put it in a new chunk. ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true); Status = alloc_chunk(Vcb, flags, &c, false); ExReleaseResourceLite(&Vcb->chunk_lock); if (!NT_SUCCESS(Status)) { ERR("alloc_chunk returned %08lx\n", Status); return Status; } if (c) { acquire_chunk_lock(c, Vcb); if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= newlen && insert_extent_chunk(Vcb, fcb, c, start_data, newlen, false, data, Irp, rollback, BTRFS_COMPRESSION_NONE, newlen, file_write, irp_offset)) { written += newlen; if (written == orig_length) return STATUS_SUCCESS; else { done = true; start_data += newlen; irp_offset += newlen; length -= newlen; data = &((uint8_t*)data)[newlen]; } } else release_chunk_lock(c, Vcb); } if (!done) { Status = insert_chunk_fragmented(fcb, start_data, length, data, false, rollback); if (!NT_SUCCESS(Status)) ERR("insert_chunk_fragmented returned %08lx\n", Status); return Status; } } return STATUS_DISK_FULL; } __attribute__((nonnull(1,4))) NTSTATUS truncate_file(fcb* fcb, uint64_t end, PIRP Irp, LIST_ENTRY* rollback) { NTSTATUS Status; // FIXME - convert into inline extent if short enough if (end > 0 && fcb_is_inline(fcb)) { uint8_t* buf; bool make_inline = end <= fcb->Vcb->options.max_inline; buf = ExAllocatePoolWithTag(PagedPool, (ULONG)(make_inline ? (offsetof(EXTENT_DATA, data[0]) + end) : sector_align(end, fcb->Vcb->superblock.sector_size)), ALLOC_TAG); if (!buf) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } Status = read_file(fcb, make_inline ? (buf + offsetof(EXTENT_DATA, data[0])) : buf, 0, end, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("read_file returned %08lx\n", Status); ExFreePool(buf); return Status; } Status = excise_extents(fcb->Vcb, fcb, 0, fcb->inode_item.st_size, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); ExFreePool(buf); return Status; } if (!make_inline) { RtlZeroMemory(buf + end, (ULONG)(sector_align(end, fcb->Vcb->superblock.sector_size) - end)); Status = do_write_file(fcb, 0, sector_align(end, fcb->Vcb->superblock.sector_size), buf, Irp, false, 0, rollback); if (!NT_SUCCESS(Status)) { ERR("do_write_file returned %08lx\n", Status); ExFreePool(buf); return Status; } } else { EXTENT_DATA* ed = (EXTENT_DATA*)buf; ed->generation = fcb->Vcb->superblock.generation; ed->decoded_size = end; ed->compression = BTRFS_COMPRESSION_NONE; ed->encryption = BTRFS_ENCRYPTION_NONE; ed->encoding = BTRFS_ENCODING_NONE; ed->type = EXTENT_TYPE_INLINE; Status = add_extent_to_fcb(fcb, 0, ed, (uint16_t)(offsetof(EXTENT_DATA, data[0]) + end), false, NULL, rollback); if (!NT_SUCCESS(Status)) { ERR("add_extent_to_fcb returned %08lx\n", Status); ExFreePool(buf); return Status; } fcb->inode_item.st_blocks += end; fcb->inode_item.st_size = end; fcb->inode_item_changed = true; TRACE("setting st_size to %I64x\n", end); fcb->Header.AllocationSize.QuadPart = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size); fcb->Header.FileSize.QuadPart = fcb->inode_item.st_size; fcb->Header.ValidDataLength.QuadPart = fcb->inode_item.st_size; } ExFreePool(buf); return STATUS_SUCCESS; } Status = excise_extents(fcb->Vcb, fcb, sector_align(end, fcb->Vcb->superblock.sector_size), sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size), Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); return Status; } fcb->inode_item.st_size = end; fcb->inode_item_changed = true; TRACE("setting st_size to %I64x\n", end); fcb->Header.AllocationSize.QuadPart = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size); fcb->Header.FileSize.QuadPart = fcb->inode_item.st_size; fcb->Header.ValidDataLength.QuadPart = fcb->inode_item.st_size; // FIXME - inform cache manager of this TRACE("fcb %p FileSize = %I64x\n", fcb, fcb->Header.FileSize.QuadPart); return STATUS_SUCCESS; } __attribute__((nonnull(1,6))) NTSTATUS extend_file(fcb* fcb, file_ref* fileref, uint64_t end, bool prealloc, PIRP Irp, LIST_ENTRY* rollback) { uint64_t oldalloc, newalloc; bool cur_inline; NTSTATUS Status; TRACE("(%p, %p, %I64x, %u)\n", fcb, fileref, end, prealloc); if (fcb->ads) { if (end > 0xffff) return STATUS_DISK_FULL; return stream_set_end_of_file_information(fcb->Vcb, (uint16_t)end, fcb, fileref, false); } else { extent* ext = NULL; LIST_ENTRY* le; le = fcb->extents.Blink; while (le != &fcb->extents) { extent* ext2 = CONTAINING_RECORD(le, extent, list_entry); if (!ext2->ignore) { ext = ext2; break; } le = le->Blink; } oldalloc = 0; if (ext) { EXTENT_DATA* ed = &ext->extent_data; EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; oldalloc = ext->offset + (ed->type == EXTENT_TYPE_INLINE ? ed->decoded_size : ed2->num_bytes); cur_inline = ed->type == EXTENT_TYPE_INLINE; if (cur_inline && end > fcb->Vcb->options.max_inline) { uint64_t origlength, length; uint8_t* data; TRACE("giving inline file proper extents\n"); origlength = ed->decoded_size; cur_inline = false; length = sector_align(origlength, fcb->Vcb->superblock.sector_size); data = ExAllocatePoolWithTag(PagedPool, (ULONG)length, ALLOC_TAG); if (!data) { ERR("could not allocate %I64x bytes for data\n", length); return STATUS_INSUFFICIENT_RESOURCES; } Status = read_file(fcb, data, 0, origlength, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("read_file returned %08lx\n", Status); ExFreePool(data); return Status; } RtlZeroMemory(data + origlength, (ULONG)(length - origlength)); Status = excise_extents(fcb->Vcb, fcb, 0, fcb->inode_item.st_size, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); ExFreePool(data); return Status; } Status = do_write_file(fcb, 0, length, data, Irp, false, 0, rollback); if (!NT_SUCCESS(Status)) { ERR("do_write_file returned %08lx\n", Status); ExFreePool(data); return Status; } oldalloc = ext->offset + length; ExFreePool(data); } if (cur_inline) { uint16_t edsize; if (end > oldalloc) { edsize = (uint16_t)(offsetof(EXTENT_DATA, data[0]) + end - ext->offset); ed = ExAllocatePoolWithTag(PagedPool, edsize, ALLOC_TAG); if (!ed) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ed->generation = fcb->Vcb->superblock.generation; ed->decoded_size = end - ext->offset; ed->compression = BTRFS_COMPRESSION_NONE; ed->encryption = BTRFS_ENCRYPTION_NONE; ed->encoding = BTRFS_ENCODING_NONE; ed->type = EXTENT_TYPE_INLINE; Status = read_file(fcb, ed->data, ext->offset, oldalloc, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("read_file returned %08lx\n", Status); ExFreePool(ed); return Status; } RtlZeroMemory(ed->data + oldalloc - ext->offset, (ULONG)(end - oldalloc)); remove_fcb_extent(fcb, ext, rollback); Status = add_extent_to_fcb(fcb, ext->offset, ed, edsize, ext->unique, NULL, rollback); if (!NT_SUCCESS(Status)) { ERR("add_extent_to_fcb returned %08lx\n", Status); ExFreePool(ed); return Status; } ExFreePool(ed); fcb->extents_changed = true; mark_fcb_dirty(fcb); } TRACE("extending inline file (oldalloc = %I64x, end = %I64x)\n", oldalloc, end); fcb->inode_item.st_size = end; TRACE("setting st_size to %I64x\n", end); fcb->inode_item.st_blocks = end; fcb->Header.AllocationSize.QuadPart = fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end; } else { newalloc = sector_align(end, fcb->Vcb->superblock.sector_size); if (newalloc > oldalloc) { if (prealloc) { // FIXME - try and extend previous extent first Status = insert_prealloc_extent(fcb, oldalloc, newalloc - oldalloc, rollback); if (!NT_SUCCESS(Status) && Status != STATUS_DISK_FULL) { ERR("insert_prealloc_extent returned %08lx\n", Status); return Status; } } fcb->extents_changed = true; } fcb->inode_item.st_size = end; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); TRACE("setting st_size to %I64x\n", end); TRACE("newalloc = %I64x\n", newalloc); fcb->Header.AllocationSize.QuadPart = newalloc; fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end; } } else { if (end > fcb->Vcb->options.max_inline) { newalloc = sector_align(end, fcb->Vcb->superblock.sector_size); if (prealloc) { Status = insert_prealloc_extent(fcb, 0, newalloc, rollback); if (!NT_SUCCESS(Status) && Status != STATUS_DISK_FULL) { ERR("insert_prealloc_extent returned %08lx\n", Status); return Status; } } fcb->extents_changed = true; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); fcb->inode_item.st_size = end; TRACE("setting st_size to %I64x\n", end); TRACE("newalloc = %I64x\n", newalloc); fcb->Header.AllocationSize.QuadPart = newalloc; fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end; } else { EXTENT_DATA* ed; uint16_t edsize; edsize = (uint16_t)(offsetof(EXTENT_DATA, data[0]) + end); ed = ExAllocatePoolWithTag(PagedPool, edsize, ALLOC_TAG); if (!ed) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } ed->generation = fcb->Vcb->superblock.generation; ed->decoded_size = end; ed->compression = BTRFS_COMPRESSION_NONE; ed->encryption = BTRFS_ENCRYPTION_NONE; ed->encoding = BTRFS_ENCODING_NONE; ed->type = EXTENT_TYPE_INLINE; RtlZeroMemory(ed->data, (ULONG)end); Status = add_extent_to_fcb(fcb, 0, ed, edsize, false, NULL, rollback); if (!NT_SUCCESS(Status)) { ERR("add_extent_to_fcb returned %08lx\n", Status); ExFreePool(ed); return Status; } ExFreePool(ed); fcb->extents_changed = true; fcb->inode_item_changed = true; mark_fcb_dirty(fcb); fcb->inode_item.st_size = end; TRACE("setting st_size to %I64x\n", end); fcb->inode_item.st_blocks = end; fcb->Header.AllocationSize.QuadPart = fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end; } } } return STATUS_SUCCESS; } __attribute__((nonnull(1,2,5,6,11))) static NTSTATUS do_write_file_prealloc(fcb* fcb, extent* ext, uint64_t start_data, uint64_t end_data, void* data, uint64_t* written, PIRP Irp, bool file_write, uint64_t irp_offset, ULONG priority, LIST_ENTRY* rollback) { EXTENT_DATA* ed = &ext->extent_data; EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; NTSTATUS Status; chunk* c = NULL; if (start_data <= ext->offset && end_data >= ext->offset + ed2->num_bytes) { // replace all extent* newext; newext = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG); if (!newext) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(&newext->extent_data, &ext->extent_data, ext->datalen); newext->extent_data.type = EXTENT_TYPE_REGULAR; Status = write_data_complete(fcb->Vcb, ed2->address + ed2->offset, (uint8_t*)data + ext->offset - start_data, (uint32_t)ed2->num_bytes, Irp, NULL, file_write, irp_offset + ext->offset - start_data, priority); if (!NT_SUCCESS(Status)) { ERR("write_data_complete returned %08lx\n", Status); return Status; } if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) { ULONG sl = (ULONG)(ed2->num_bytes >> fcb->Vcb->sector_shift); void* csum = ExAllocatePoolWithTag(PagedPool, sl * fcb->Vcb->csum_size, ALLOC_TAG); if (!csum) { ERR("out of memory\n"); ExFreePool(newext); return STATUS_INSUFFICIENT_RESOURCES; } do_calc_job(fcb->Vcb, (uint8_t*)data + ext->offset - start_data, sl, csum); newext->csum = csum; } else newext->csum = NULL; *written = ed2->num_bytes; newext->offset = ext->offset; newext->datalen = ext->datalen; newext->unique = ext->unique; newext->ignore = false; newext->inserted = true; InsertHeadList(&ext->list_entry, &newext->list_entry); add_insert_extent_rollback(rollback, fcb, newext); remove_fcb_extent(fcb, ext, rollback); c = get_chunk_from_address(fcb->Vcb, ed2->address); } else if (start_data <= ext->offset && end_data < ext->offset + ed2->num_bytes) { // replace beginning EXTENT_DATA2* ned2; extent *newext1, *newext2; newext1 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG); if (!newext1) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } newext2 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG); if (!newext2) { ERR("out of memory\n"); ExFreePool(newext1); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(&newext1->extent_data, &ext->extent_data, ext->datalen); newext1->extent_data.type = EXTENT_TYPE_REGULAR; ned2 = (EXTENT_DATA2*)newext1->extent_data.data; ned2->num_bytes = end_data - ext->offset; RtlCopyMemory(&newext2->extent_data, &ext->extent_data, ext->datalen); ned2 = (EXTENT_DATA2*)newext2->extent_data.data; ned2->offset += end_data - ext->offset; ned2->num_bytes -= end_data - ext->offset; Status = write_data_complete(fcb->Vcb, ed2->address + ed2->offset, (uint8_t*)data + ext->offset - start_data, (uint32_t)(end_data - ext->offset), Irp, NULL, file_write, irp_offset + ext->offset - start_data, priority); if (!NT_SUCCESS(Status)) { ERR("write_data_complete returned %08lx\n", Status); ExFreePool(newext1); ExFreePool(newext2); return Status; } if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) { ULONG sl = (ULONG)((end_data - ext->offset) >> fcb->Vcb->sector_shift); void* csum = ExAllocatePoolWithTag(PagedPool, sl * fcb->Vcb->csum_size, ALLOC_TAG); if (!csum) { ERR("out of memory\n"); ExFreePool(newext1); ExFreePool(newext2); return STATUS_INSUFFICIENT_RESOURCES; } do_calc_job(fcb->Vcb, (uint8_t*)data + ext->offset - start_data, sl, csum); newext1->csum = csum; } else newext1->csum = NULL; *written = end_data - ext->offset; newext1->offset = ext->offset; newext1->datalen = ext->datalen; newext1->unique = ext->unique; newext1->ignore = false; newext1->inserted = true; InsertHeadList(&ext->list_entry, &newext1->list_entry); add_insert_extent_rollback(rollback, fcb, newext1); newext2->offset = end_data; newext2->datalen = ext->datalen; newext2->unique = ext->unique; newext2->ignore = false; newext2->inserted = true; newext2->csum = NULL; add_extent(fcb, &newext1->list_entry, newext2); add_insert_extent_rollback(rollback, fcb, newext2); c = get_chunk_from_address(fcb->Vcb, ed2->address); if (!c) ERR("get_chunk_from_address(%I64x) failed\n", ed2->address); else { Status = update_changed_extent_ref(fcb->Vcb, c, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, 1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp); if (!NT_SUCCESS(Status)) { ERR("update_changed_extent_ref returned %08lx\n", Status); return Status; } } remove_fcb_extent(fcb, ext, rollback); } else if (start_data > ext->offset && end_data >= ext->offset + ed2->num_bytes) { // replace end EXTENT_DATA2* ned2; extent *newext1, *newext2; newext1 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG); if (!newext1) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } newext2 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG); if (!newext2) { ERR("out of memory\n"); ExFreePool(newext1); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(&newext1->extent_data, &ext->extent_data, ext->datalen); ned2 = (EXTENT_DATA2*)newext1->extent_data.data; ned2->num_bytes = start_data - ext->offset; RtlCopyMemory(&newext2->extent_data, &ext->extent_data, ext->datalen); newext2->extent_data.type = EXTENT_TYPE_REGULAR; ned2 = (EXTENT_DATA2*)newext2->extent_data.data; ned2->offset += start_data - ext->offset; ned2->num_bytes = ext->offset + ed2->num_bytes - start_data; Status = write_data_complete(fcb->Vcb, ed2->address + ned2->offset, data, (uint32_t)ned2->num_bytes, Irp, NULL, file_write, irp_offset, priority); if (!NT_SUCCESS(Status)) { ERR("write_data_complete returned %08lx\n", Status); ExFreePool(newext1); ExFreePool(newext2); return Status; } if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) { ULONG sl = (ULONG)(ned2->num_bytes >> fcb->Vcb->sector_shift); void* csum = ExAllocatePoolWithTag(PagedPool, sl * fcb->Vcb->csum_size, ALLOC_TAG); if (!csum) { ERR("out of memory\n"); ExFreePool(newext1); ExFreePool(newext2); return STATUS_INSUFFICIENT_RESOURCES; } do_calc_job(fcb->Vcb, data, sl, csum); newext2->csum = csum; } else newext2->csum = NULL; *written = ned2->num_bytes; newext1->offset = ext->offset; newext1->datalen = ext->datalen; newext1->unique = ext->unique; newext1->ignore = false; newext1->inserted = true; newext1->csum = NULL; InsertHeadList(&ext->list_entry, &newext1->list_entry); add_insert_extent_rollback(rollback, fcb, newext1); newext2->offset = start_data; newext2->datalen = ext->datalen; newext2->unique = ext->unique; newext2->ignore = false; newext2->inserted = true; add_extent(fcb, &newext1->list_entry, newext2); add_insert_extent_rollback(rollback, fcb, newext2); c = get_chunk_from_address(fcb->Vcb, ed2->address); if (!c) ERR("get_chunk_from_address(%I64x) failed\n", ed2->address); else { Status = update_changed_extent_ref(fcb->Vcb, c, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, 1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp); if (!NT_SUCCESS(Status)) { ERR("update_changed_extent_ref returned %08lx\n", Status); return Status; } } remove_fcb_extent(fcb, ext, rollback); } else if (start_data > ext->offset && end_data < ext->offset + ed2->num_bytes) { // replace middle EXTENT_DATA2* ned2; extent *newext1, *newext2, *newext3; newext1 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG); if (!newext1) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } newext2 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG); if (!newext2) { ERR("out of memory\n"); ExFreePool(newext1); return STATUS_INSUFFICIENT_RESOURCES; } newext3 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG); if (!newext3) { ERR("out of memory\n"); ExFreePool(newext1); ExFreePool(newext2); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(&newext1->extent_data, &ext->extent_data, ext->datalen); RtlCopyMemory(&newext2->extent_data, &ext->extent_data, ext->datalen); RtlCopyMemory(&newext3->extent_data, &ext->extent_data, ext->datalen); ned2 = (EXTENT_DATA2*)newext1->extent_data.data; ned2->num_bytes = start_data - ext->offset; newext2->extent_data.type = EXTENT_TYPE_REGULAR; ned2 = (EXTENT_DATA2*)newext2->extent_data.data; ned2->offset += start_data - ext->offset; ned2->num_bytes = end_data - start_data; ned2 = (EXTENT_DATA2*)newext3->extent_data.data; ned2->offset += end_data - ext->offset; ned2->num_bytes -= end_data - ext->offset; ned2 = (EXTENT_DATA2*)newext2->extent_data.data; Status = write_data_complete(fcb->Vcb, ed2->address + ned2->offset, data, (uint32_t)(end_data - start_data), Irp, NULL, file_write, irp_offset, priority); if (!NT_SUCCESS(Status)) { ERR("write_data_complete returned %08lx\n", Status); ExFreePool(newext1); ExFreePool(newext2); ExFreePool(newext3); return Status; } if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) { ULONG sl = (ULONG)((end_data - start_data) >> fcb->Vcb->sector_shift); void* csum = ExAllocatePoolWithTag(PagedPool, sl * fcb->Vcb->csum_size, ALLOC_TAG); if (!csum) { ERR("out of memory\n"); ExFreePool(newext1); ExFreePool(newext2); ExFreePool(newext3); return STATUS_INSUFFICIENT_RESOURCES; } do_calc_job(fcb->Vcb, data, sl, csum); newext2->csum = csum; } else newext2->csum = NULL; *written = end_data - start_data; newext1->offset = ext->offset; newext1->datalen = ext->datalen; newext1->unique = ext->unique; newext1->ignore = false; newext1->inserted = true; newext1->csum = NULL; InsertHeadList(&ext->list_entry, &newext1->list_entry); add_insert_extent_rollback(rollback, fcb, newext1); newext2->offset = start_data; newext2->datalen = ext->datalen; newext2->unique = ext->unique; newext2->ignore = false; newext2->inserted = true; add_extent(fcb, &newext1->list_entry, newext2); add_insert_extent_rollback(rollback, fcb, newext2); newext3->offset = end_data; newext3->datalen = ext->datalen; newext3->unique = ext->unique; newext3->ignore = false; newext3->inserted = true; newext3->csum = NULL; add_extent(fcb, &newext2->list_entry, newext3); add_insert_extent_rollback(rollback, fcb, newext3); c = get_chunk_from_address(fcb->Vcb, ed2->address); if (!c) ERR("get_chunk_from_address(%I64x) failed\n", ed2->address); else { Status = update_changed_extent_ref(fcb->Vcb, c, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, 2, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp); if (!NT_SUCCESS(Status)) { ERR("update_changed_extent_ref returned %08lx\n", Status); return Status; } } remove_fcb_extent(fcb, ext, rollback); } if (c) c->changed = true; return STATUS_SUCCESS; } __attribute__((nonnull(1, 4))) NTSTATUS 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) { NTSTATUS Status; LIST_ENTRY *le, *le2; uint64_t written = 0, length = end_data - start; uint64_t last_cow_start; ULONG priority = fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? HighPagePriority : NormalPagePriority; #ifdef DEBUG_PARANOID uint64_t last_off; #endif bool extents_changed = false; last_cow_start = 0; le = fcb->extents.Flink; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); le2 = le->Flink; if (!ext->ignore) { EXTENT_DATA* ed = &ext->extent_data; uint64_t len; if (ed->type == EXTENT_TYPE_INLINE) len = ed->decoded_size; else len = ((EXTENT_DATA2*)ed->data)->num_bytes; if (ext->offset + len <= start) goto nextitem; if (ext->offset > start + written + length) break; if ((fcb->inode_item.flags & BTRFS_INODE_NODATACOW || ed->type == EXTENT_TYPE_PREALLOC) && ext->unique && ed->compression == BTRFS_COMPRESSION_NONE) { if (max(last_cow_start, start + written) < ext->offset) { uint64_t start_write = max(last_cow_start, start + written); extents_changed = true; Status = excise_extents(fcb->Vcb, fcb, start_write, ext->offset, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); return Status; } Status = insert_extent(fcb->Vcb, fcb, start_write, ext->offset - start_write, (uint8_t*)data + written, Irp, file_write, irp_offset + written, rollback); if (!NT_SUCCESS(Status)) { ERR("insert_extent returned %08lx\n", Status); return Status; } written += ext->offset - start_write; length -= ext->offset - start_write; if (length == 0) break; } if (ed->type == EXTENT_TYPE_REGULAR) { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data; uint64_t writeaddr = ed2->address + ed2->offset + start + written - ext->offset; uint64_t write_len = min(len, length); chunk* c; TRACE("doing non-COW write to %I64x\n", writeaddr); Status = write_data_complete(fcb->Vcb, writeaddr, (uint8_t*)data + written, (uint32_t)write_len, Irp, NULL, file_write, irp_offset + written, priority); if (!NT_SUCCESS(Status)) { ERR("write_data_complete returned %08lx\n", Status); return Status; } c = get_chunk_from_address(fcb->Vcb, writeaddr); if (c) c->changed = true; // This shouldn't ever get called - nocow files should always also be nosum. if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) { do_calc_job(fcb->Vcb, (uint8_t*)data + written, (uint32_t)(write_len >> fcb->Vcb->sector_shift), (uint8_t*)ext->csum + (((start + written - ext->offset) * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift)); ext->inserted = true; extents_changed = true; } written += write_len; length -= write_len; if (length == 0) break; } else if (ed->type == EXTENT_TYPE_PREALLOC) { uint64_t write_len; Status = do_write_file_prealloc(fcb, ext, start + written, end_data, (uint8_t*)data + written, &write_len, Irp, file_write, irp_offset + written, priority, rollback); if (!NT_SUCCESS(Status)) { ERR("do_write_file_prealloc returned %08lx\n", Status); return Status; } extents_changed = true; written += write_len; length -= write_len; if (length == 0) break; } last_cow_start = ext->offset + len; } } nextitem: le = le2; } if (length > 0) { uint64_t start_write = max(last_cow_start, start + written); extents_changed = true; Status = excise_extents(fcb->Vcb, fcb, start_write, end_data, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("excise_extents returned %08lx\n", Status); return Status; } Status = insert_extent(fcb->Vcb, fcb, start_write, end_data - start_write, (uint8_t*)data + written, Irp, file_write, irp_offset + written, rollback); if (!NT_SUCCESS(Status)) { ERR("insert_extent returned %08lx\n", Status); return Status; } } #ifdef DEBUG_PARANOID last_off = 0xffffffffffffffff; le = fcb->extents.Flink; while (le != &fcb->extents) { extent* ext = CONTAINING_RECORD(le, extent, list_entry); if (!ext->ignore) { if (ext->offset == last_off) { ERR("offset %I64x duplicated\n", ext->offset); int3; } else if (ext->offset < last_off && last_off != 0xffffffffffffffff) { ERR("offsets out of order\n"); int3; } last_off = ext->offset; } le = le->Flink; } #endif if (extents_changed) { fcb->extents_changed = true; mark_fcb_dirty(fcb); } return STATUS_SUCCESS; } __attribute__((nonnull(1,2,4,5,11))) NTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void* buf, ULONG* length, bool paging_io, bool no_cache, bool wait, bool deferred_write, bool write_irp, LIST_ENTRY* rollback) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; EXTENT_DATA* ed2; uint64_t off64, newlength, start_data, end_data; uint32_t bufhead; bool make_inline; INODE_ITEM* origii; bool changed_length = false; NTSTATUS Status; LARGE_INTEGER time; BTRFS_TIME now; fcb* fcb; ccb* ccb; file_ref* fileref; bool paging_lock = false, acquired_fcb_lock = false, acquired_tree_lock = false, pagefile; ULONG filter = 0; TRACE("(%p, %p, %I64x, %p, %lx, %u, %u)\n", Vcb, FileObject, offset.QuadPart, buf, *length, paging_io, no_cache); if (*length == 0) { TRACE("returning success for zero-length write\n"); return STATUS_SUCCESS; } if (!FileObject) { ERR("error - FileObject was NULL\n"); return STATUS_ACCESS_DENIED; } fcb = FileObject->FsContext; ccb = FileObject->FsContext2; fileref = ccb ? ccb->fileref : NULL; if (!fcb->ads && fcb->type != BTRFS_TYPE_FILE && fcb->type != BTRFS_TYPE_SYMLINK) { 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); return STATUS_INVALID_DEVICE_REQUEST; } if (offset.LowPart == FILE_WRITE_TO_END_OF_FILE && offset.HighPart == -1) offset = fcb->Header.FileSize; off64 = offset.QuadPart; TRACE("fcb->Header.Flags = %x\n", fcb->Header.Flags); if (!no_cache && !CcCanIWrite(FileObject, *length, wait, deferred_write)) return STATUS_PENDING; if (!wait && no_cache) return STATUS_PENDING; if (no_cache && !paging_io && FileObject->SectionObjectPointer->DataSectionObject) { IO_STATUS_BLOCK iosb; ExAcquireResourceExclusiveLite(fcb->Header.PagingIoResource, true); CcFlushCache(FileObject->SectionObjectPointer, &offset, *length, &iosb); if (!NT_SUCCESS(iosb.Status)) { ExReleaseResourceLite(fcb->Header.PagingIoResource); ERR("CcFlushCache returned %08lx\n", iosb.Status); return iosb.Status; } paging_lock = true; CcPurgeCacheSection(FileObject->SectionObjectPointer, &offset, *length, false); } if (paging_io) { if (!ExAcquireResourceSharedLite(fcb->Header.PagingIoResource, wait)) { Status = STATUS_PENDING; goto end; } else paging_lock = true; } pagefile = fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE && paging_io; if (!pagefile && !ExIsResourceAcquiredExclusiveLite(&Vcb->tree_lock)) { if (!ExAcquireResourceSharedLite(&Vcb->tree_lock, wait)) { Status = STATUS_PENDING; goto end; } else acquired_tree_lock = true; } if (pagefile) { if (!ExAcquireResourceSharedLite(fcb->Header.Resource, wait)) { Status = STATUS_PENDING; goto end; } else acquired_fcb_lock = true; } else if (!ExIsResourceAcquiredExclusiveLite(fcb->Header.Resource)) { if (!ExAcquireResourceExclusiveLite(fcb->Header.Resource, wait)) { Status = STATUS_PENDING; goto end; } else acquired_fcb_lock = true; } newlength = fcb->ads ? fcb->adsdata.Length : fcb->inode_item.st_size; if (fcb->deleted) newlength = 0; TRACE("newlength = %I64x\n", newlength); if (off64 + *length > newlength) { if (paging_io) { if (off64 >= newlength) { TRACE("paging IO tried to write beyond end of file (file size = %I64x, offset = %I64x, length = %lx)\n", newlength, off64, *length); TRACE("FileObject: AllocationSize = %I64x, FileSize = %I64x, ValidDataLength = %I64x\n", fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart); Irp->IoStatus.Information = 0; Status = STATUS_SUCCESS; goto end; } *length = (ULONG)(newlength - off64); } else { newlength = off64 + *length; changed_length = true; TRACE("extending length to %I64x\n", newlength); } } if (fcb->ads) make_inline = false; else make_inline = newlength <= fcb->Vcb->options.max_inline; if (changed_length) { if (newlength > (uint64_t)fcb->Header.AllocationSize.QuadPart) { if (!acquired_tree_lock) { // We need to acquire the tree lock if we don't have it already - // we can't give an inline file proper extents at the same time as we're // doing a flush. if (!ExAcquireResourceSharedLite(&Vcb->tree_lock, wait)) { Status = STATUS_PENDING; goto end; } else acquired_tree_lock = true; } Status = extend_file(fcb, fileref, newlength, false, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("extend_file returned %08lx\n", Status); goto end; } } else if (!fcb->ads) fcb->inode_item.st_size = newlength; fcb->Header.FileSize.QuadPart = newlength; fcb->Header.ValidDataLength.QuadPart = newlength; TRACE("AllocationSize = %I64x\n", fcb->Header.AllocationSize.QuadPart); TRACE("FileSize = %I64x\n", fcb->Header.FileSize.QuadPart); TRACE("ValidDataLength = %I64x\n", fcb->Header.ValidDataLength.QuadPart); } if (!no_cache) { Status = STATUS_SUCCESS; try { if (!FileObject->PrivateCacheMap || changed_length) { CC_FILE_SIZES ccfs; ccfs.AllocationSize = fcb->Header.AllocationSize; ccfs.FileSize = fcb->Header.FileSize; ccfs.ValidDataLength = fcb->Header.ValidDataLength; if (!FileObject->PrivateCacheMap) init_file_cache(FileObject, &ccfs); CcSetFileSizes(FileObject, &ccfs); } if (IrpSp->MinorFunction & IRP_MN_MDL) { CcPrepareMdlWrite(FileObject, &offset, *length, &Irp->MdlAddress, &Irp->IoStatus); Status = Irp->IoStatus.Status; goto end; } else { /* We have to wait in CcCopyWrite - if we return STATUS_PENDING and add this to the work queue, * it can result in CcFlushCache being called before the job has run. See ifstest ReadWriteTest. */ if (fCcCopyWriteEx) { TRACE("CcCopyWriteEx(%p, %I64x, %lx, %u, %p, %p)\n", FileObject, off64, *length, true, buf, Irp->Tail.Overlay.Thread); if (!fCcCopyWriteEx(FileObject, &offset, *length, true, buf, Irp->Tail.Overlay.Thread)) { Status = STATUS_PENDING; goto end; } TRACE("CcCopyWriteEx finished\n"); } else { TRACE("CcCopyWrite(%p, %I64x, %lx, %u, %p)\n", FileObject, off64, *length, true, buf); if (!CcCopyWrite(FileObject, &offset, *length, true, buf)) { Status = STATUS_PENDING; goto end; } TRACE("CcCopyWrite finished\n"); } Irp->IoStatus.Information = *length; } } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (changed_length) { queue_notification_fcb(fcb->ads ? fileref->parent : fileref, fcb->ads ? FILE_NOTIFY_CHANGE_STREAM_SIZE : FILE_NOTIFY_CHANGE_SIZE, fcb->ads ? FILE_ACTION_MODIFIED_STREAM : FILE_ACTION_MODIFIED, fcb->ads && fileref->dc ? &fileref->dc->name : NULL); } goto end; } if (fcb->ads) { if (changed_length) { char* data2; if (newlength > fcb->adsmaxlen) { ERR("error - xattr too long (%I64u > %lu)\n", newlength, fcb->adsmaxlen); Status = STATUS_DISK_FULL; goto end; } data2 = ExAllocatePoolWithTag(PagedPool, (ULONG)newlength, ALLOC_TAG); if (!data2) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } if (fcb->adsdata.Buffer) { RtlCopyMemory(data2, fcb->adsdata.Buffer, fcb->adsdata.Length); ExFreePool(fcb->adsdata.Buffer); } if (newlength > fcb->adsdata.Length) RtlZeroMemory(&data2[fcb->adsdata.Length], (ULONG)(newlength - fcb->adsdata.Length)); fcb->adsdata.Buffer = data2; fcb->adsdata.Length = fcb->adsdata.MaximumLength = (USHORT)newlength; fcb->Header.AllocationSize.QuadPart = newlength; fcb->Header.FileSize.QuadPart = newlength; fcb->Header.ValidDataLength.QuadPart = newlength; } if (*length > 0) RtlCopyMemory(&fcb->adsdata.Buffer[off64], buf, *length); fcb->Header.ValidDataLength.QuadPart = newlength; mark_fcb_dirty(fcb); if (fileref) mark_fileref_dirty(fileref); } else { bool compress = write_fcb_compressed(fcb), no_buf = false; uint8_t* data; if (make_inline) { start_data = 0; end_data = sector_align(newlength, fcb->Vcb->superblock.sector_size); bufhead = sizeof(EXTENT_DATA) - 1; } else if (compress) { start_data = off64 & ~(uint64_t)(COMPRESSED_EXTENT_SIZE - 1); end_data = min(sector_align(off64 + *length, COMPRESSED_EXTENT_SIZE), sector_align(newlength, fcb->Vcb->superblock.sector_size)); bufhead = 0; } else { start_data = off64 & ~(uint64_t)(fcb->Vcb->superblock.sector_size - 1); end_data = sector_align(off64 + *length, fcb->Vcb->superblock.sector_size); bufhead = 0; } if (fcb_is_inline(fcb)) end_data = max(end_data, sector_align(fcb->inode_item.st_size, Vcb->superblock.sector_size)); fcb->Header.ValidDataLength.QuadPart = newlength; TRACE("fcb %p FileSize = %I64x\n", fcb, fcb->Header.FileSize.QuadPart); if (!make_inline && !compress && off64 == start_data && off64 + *length == end_data) { data = buf; no_buf = true; } else { data = ExAllocatePoolWithTag(PagedPool, (ULONG)(end_data - start_data + bufhead), ALLOC_TAG); if (!data) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } RtlZeroMemory(data + bufhead, (ULONG)(end_data - start_data)); TRACE("start_data = %I64x\n", start_data); TRACE("end_data = %I64x\n", end_data); if (off64 > start_data || off64 + *length < end_data) { if (changed_length) { if (fcb->inode_item.st_size > start_data) Status = read_file(fcb, data + bufhead, start_data, fcb->inode_item.st_size - start_data, NULL, Irp); else Status = STATUS_SUCCESS; } else Status = read_file(fcb, data + bufhead, start_data, end_data - start_data, NULL, Irp); if (!NT_SUCCESS(Status)) { ERR("read_file returned %08lx\n", Status); ExFreePool(data); goto end; } } RtlCopyMemory(data + bufhead + off64 - start_data, buf, *length); } if (make_inline) { Status = excise_extents(fcb->Vcb, fcb, start_data, end_data, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("error - excise_extents returned %08lx\n", Status); ExFreePool(data); goto end; } ed2 = (EXTENT_DATA*)data; ed2->generation = fcb->Vcb->superblock.generation; ed2->decoded_size = newlength; ed2->compression = BTRFS_COMPRESSION_NONE; ed2->encryption = BTRFS_ENCRYPTION_NONE; ed2->encoding = BTRFS_ENCODING_NONE; ed2->type = EXTENT_TYPE_INLINE; Status = add_extent_to_fcb(fcb, 0, ed2, (uint16_t)(offsetof(EXTENT_DATA, data[0]) + newlength), false, NULL, rollback); if (!NT_SUCCESS(Status)) { ERR("add_extent_to_fcb returned %08lx\n", Status); ExFreePool(data); goto end; } fcb->inode_item.st_blocks += newlength; } else if (compress) { Status = write_compressed(fcb, start_data, end_data, data, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("write_compressed returned %08lx\n", Status); ExFreePool(data); goto end; } } else { if (write_irp && Irp->MdlAddress && no_buf) { bool locked = Irp->MdlAddress->MdlFlags & (MDL_PAGES_LOCKED | MDL_PARTIAL); if (!locked) { Status = STATUS_SUCCESS; try { MmProbeAndLockPages(Irp->MdlAddress, KernelMode, IoReadAccess); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!NT_SUCCESS(Status)) { ERR("MmProbeAndLockPages threw exception %08lx\n", Status); goto end; } } try { Status = do_write_file(fcb, start_data, end_data, data, Irp, true, 0, rollback); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if (!locked) MmUnlockPages(Irp->MdlAddress); } else { try { Status = do_write_file(fcb, start_data, end_data, data, Irp, false, 0, rollback); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } } if (!NT_SUCCESS(Status)) { ERR("do_write_file returned %08lx\n", Status); if (!no_buf) ExFreePool(data); goto end; } } if (!no_buf) ExFreePool(data); } KeQuerySystemTime(&time); win_time_to_unix(time, &now); if (!pagefile) { if (fcb->ads) { if (fileref && fileref->parent) origii = &fileref->parent->fcb->inode_item; else { ERR("no parent fcb found for stream\n"); Status = STATUS_INTERNAL_ERROR; goto end; } } else origii = &fcb->inode_item; origii->transid = Vcb->superblock.generation; origii->sequence++; if (!ccb->user_set_change_time) origii->st_ctime = now; if (!fcb->ads) { if (changed_length) { TRACE("setting st_size to %I64x\n", newlength); origii->st_size = newlength; filter |= FILE_NOTIFY_CHANGE_SIZE; } fcb->inode_item_changed = true; } else { fileref->parent->fcb->inode_item_changed = true; if (changed_length) filter |= FILE_NOTIFY_CHANGE_STREAM_SIZE; filter |= FILE_NOTIFY_CHANGE_STREAM_WRITE; } if (!ccb->user_set_write_time) { origii->st_mtime = now; filter |= FILE_NOTIFY_CHANGE_LAST_WRITE; } mark_fcb_dirty(fcb->ads ? fileref->parent->fcb : fcb); } if (changed_length) { CC_FILE_SIZES ccfs; ccfs.AllocationSize = fcb->Header.AllocationSize; ccfs.FileSize = fcb->Header.FileSize; ccfs.ValidDataLength = fcb->Header.ValidDataLength; try { CcSetFileSizes(FileObject, &ccfs); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); goto end; } } fcb->subvol->root_item.ctransid = Vcb->superblock.generation; fcb->subvol->root_item.ctime = now; Status = STATUS_SUCCESS; Irp->IoStatus.Information = *length; if (filter != 0) queue_notification_fcb(fcb->ads ? fileref->parent : fileref, filter, fcb->ads ? FILE_ACTION_MODIFIED_STREAM : FILE_ACTION_MODIFIED, fcb->ads && fileref->dc ? &fileref->dc->name : NULL); end: if (NT_SUCCESS(Status) && FileObject->Flags & FO_SYNCHRONOUS_IO && !paging_io) { TRACE("CurrentByteOffset was: %I64x\n", FileObject->CurrentByteOffset.QuadPart); FileObject->CurrentByteOffset.QuadPart = offset.QuadPart + (NT_SUCCESS(Status) ? *length : 0); TRACE("CurrentByteOffset now: %I64x\n", FileObject->CurrentByteOffset.QuadPart); } if (acquired_fcb_lock) ExReleaseResourceLite(fcb->Header.Resource); if (acquired_tree_lock) ExReleaseResourceLite(&Vcb->tree_lock); if (paging_lock) ExReleaseResourceLite(fcb->Header.PagingIoResource); return Status; } __attribute__((nonnull(1,2))) NTSTATUS write_file(device_extension* Vcb, PIRP Irp, bool wait, bool deferred_write) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); void* buf; NTSTATUS Status; LARGE_INTEGER offset = IrpSp->Parameters.Write.ByteOffset; PFILE_OBJECT FileObject = IrpSp->FileObject; fcb* fcb = FileObject ? FileObject->FsContext : NULL; LIST_ENTRY rollback; InitializeListHead(&rollback); TRACE("write\n"); Irp->IoStatus.Information = 0; TRACE("offset = %I64x\n", offset.QuadPart); TRACE("length = %lx\n", IrpSp->Parameters.Write.Length); if (!Irp->AssociatedIrp.SystemBuffer) { buf = map_user_buffer(Irp, fcb && fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? HighPagePriority : NormalPagePriority); if (Irp->MdlAddress && !buf) { ERR("MmGetSystemAddressForMdlSafe returned NULL\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto exit; } } else buf = Irp->AssociatedIrp.SystemBuffer; TRACE("buf = %p\n", buf); if (fcb && !(Irp->Flags & IRP_PAGING_IO) && !FsRtlCheckLockForWriteAccess(&fcb->lock, Irp)) { WARN("tried to write to locked region\n"); Status = STATUS_FILE_LOCK_CONFLICT; goto exit; } Status = write_file2(Vcb, Irp, offset, buf, &IrpSp->Parameters.Write.Length, Irp->Flags & IRP_PAGING_IO, Irp->Flags & IRP_NOCACHE, wait, deferred_write, true, &rollback); if (Status == STATUS_PENDING) goto exit; else if (!NT_SUCCESS(Status)) { ERR("write_file2 returned %08lx\n", Status); goto exit; } if (NT_SUCCESS(Status)) { if (diskacc && Status != STATUS_PENDING && Irp->Flags & IRP_NOCACHE) { PETHREAD thread = NULL; if (Irp->Tail.Overlay.Thread && !IoIsSystemThread(Irp->Tail.Overlay.Thread)) thread = Irp->Tail.Overlay.Thread; else if (!IoIsSystemThread(PsGetCurrentThread())) thread = PsGetCurrentThread(); else if (IoIsSystemThread(PsGetCurrentThread()) && IoGetTopLevelIrp() == Irp) thread = PsGetCurrentThread(); if (thread) fPsUpdateDiskCounters(PsGetThreadProcess(thread), 0, IrpSp->Parameters.Write.Length, 0, 1, 0); } } exit: if (NT_SUCCESS(Status)) clear_rollback(&rollback); else do_rollback(Vcb, &rollback); return Status; } _Dispatch_type_(IRP_MJ_WRITE) _Function_class_(DRIVER_DISPATCH) __attribute__((nonnull(1,2))) NTSTATUS __stdcall drv_write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS Status; bool top_level; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); device_extension* Vcb = DeviceObject->DeviceExtension; PFILE_OBJECT FileObject = IrpSp->FileObject; fcb* fcb = FileObject ? FileObject->FsContext : NULL; ccb* ccb = FileObject ? FileObject->FsContext2 : NULL; bool wait = FileObject ? IoIsOperationSynchronous(Irp) : true; FsRtlEnterFileSystem(); top_level = is_top_level(Irp); if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { Status = vol_write(DeviceObject, Irp); goto exit; } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { Status = STATUS_INVALID_PARAMETER; goto end; } if (!fcb) { ERR("fcb was NULL\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (!ccb) { ERR("ccb was NULL\n"); Status = STATUS_INVALID_PARAMETER; goto end; } if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_WRITE_DATA | FILE_APPEND_DATA))) { WARN("insufficient permissions\n"); Status = STATUS_ACCESS_DENIED; goto end; } if (fcb == Vcb->volume_fcb) { if (!Vcb->locked || Vcb->locked_fileobj != FileObject) { ERR("trying to write to volume when not locked, or locked with another FileObject\n"); Status = STATUS_ACCESS_DENIED; goto end; } TRACE("writing directly to volume\n"); IoSkipCurrentIrpStackLocation(Irp); Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp); goto exit; } if (is_subvol_readonly(fcb->subvol, Irp)) { Status = STATUS_ACCESS_DENIED; goto end; } if (Vcb->readonly) { Status = STATUS_MEDIA_WRITE_PROTECTED; goto end; } try { if (IrpSp->MinorFunction & IRP_MN_COMPLETE) { CcMdlWriteComplete(IrpSp->FileObject, &IrpSp->Parameters.Write.ByteOffset, Irp->MdlAddress); Irp->MdlAddress = NULL; Status = STATUS_SUCCESS; } else { if (!(Irp->Flags & IRP_PAGING_IO)) FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL); // Don't offload jobs when doing paging IO - otherwise this can lead to // deadlocks in CcCopyWrite. if (Irp->Flags & IRP_PAGING_IO) wait = true; Status = write_file(Vcb, Irp, wait, false); } } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } end: Irp->IoStatus.Status = Status; TRACE("wrote %Iu bytes\n", Irp->IoStatus.Information); if (Status != STATUS_PENDING) IoCompleteRequest(Irp, IO_NO_INCREMENT); else { IoMarkIrpPending(Irp); if (!add_thread_job(Vcb, Irp)) Status = do_write_job(Vcb, Irp); } exit: if (top_level) IoSetTopLevelIrp(NULL); TRACE("returning %08lx\n", Status); FsRtlExitFileSystem(); return Status; } ================================================ FILE: src/xor-gas.S ================================================ /* Copyright (c) Mark Harmstone 2020 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ .intel_syntax noprefix #ifdef __x86_64__ .global do_xor_sse2 /* void do_xor_sse2(uint8_t* buf1, uint8_t* buf2, uint32_t len); */ do_xor_sse2: /* rcx = buf1 * rdx = buf2 * r8d = len * rax = tmp1 * r9 = tmp2 * xmm0 = tmp3 * xmm1 = tmp4 */ mov rax, rcx and rax, 15 cmp rax, 0 jne stragglers2 mov rax, rdx and rax, 15 cmp rax, 0 jne stragglers2 do_xor_sse2_loop: cmp r8d, 16 jl stragglers2 movdqa xmm0, [rcx] movdqa xmm1, [rdx] pxor xmm0, xmm1 movdqa [rcx], xmm0 add rcx, 16 add rdx, 16 sub r8d, 16 jmp do_xor_sse2_loop stragglers2: cmp r8d, 8 jl stragglers mov rax, [rcx] mov r9, [rdx] xor rax, r9 mov [rcx], rax add rcx, 8 add rdx, 8 sub r8d, 8 jmp stragglers2 stragglers: cmp r8d, 0 je do_xor_sse2_end mov al, [rcx] mov r9b, [rdx] xor al, r9b mov [rcx], al inc rcx inc rdx dec r8d jmp stragglers do_xor_sse2_end: ret .global do_xor_avx2 /* void do_xor_avx2(uint8_t* buf1, uint8_t* buf2, uint32_t len); */ do_xor_avx2: /* rcx = buf1 * rdx = buf2 * r8d = len * rax = tmp1 * r9 = tmp2 * xmm0 = tmp3 * xmm1 = tmp4 */ mov rax, rcx and rax, 31 cmp rax, 0 jne stragglers4 mov rax, rdx and rax, 31 cmp rax, 0 jne stragglers4 do_xor_avx2_loop: cmp r8d, 32 jl stragglers4 vmovdqa ymm0, [rcx] vmovdqa ymm1, [rdx] vpxor ymm0, ymm0, ymm1 vmovdqa [rcx], ymm0 add rcx, 32 add rdx, 32 sub r8d, 32 jmp do_xor_avx2_loop stragglers4: cmp r8d, 8 jl stragglers3 mov rax, [rcx] mov r9, [rdx] xor rax, r9 mov [rcx], rax add rcx, 8 add rdx, 8 sub r8d, 8 jmp stragglers4 stragglers3: cmp r8d, 0 je do_xor_avx2_end mov al, [rcx] mov r9b, [rdx] xor al, r9b mov [rcx], al inc rcx inc rdx dec r8d jmp stragglers3 do_xor_avx2_end: ret #else .global _do_xor_sse2@12 /* void __stdcall do_xor_sse2(uint8_t* buf1, uint8_t* buf2, uint32_t len); */ _do_xor_sse2@12: /* edi = buf1 * edx = buf2 * esi = len * eax = tmp1 * ecx = tmp2 * xmm0 = tmp3 * xmm1 = tmp4 */ push ebp mov ebp, esp push esi push edi mov edi, [ebp+8] mov edx, [ebp+12] mov esi, [ebp+16] mov eax, edi and eax, 15 cmp eax, 0 jne stragglers2 mov eax, edx and eax, 15 cmp eax, 0 jne stragglers2 do_xor_sse2_loop: cmp esi, 16 jl stragglers2 movdqa xmm0, [edi] movdqa xmm1, [edx] pxor xmm0, xmm1 movdqa [edi], xmm0 add edi, 16 add edx, 16 sub esi, 16 jmp do_xor_sse2_loop stragglers2: cmp esi, 4 jl stragglers mov eax, [edi] mov ecx, [edx] xor eax, ecx mov [edi], eax add edi, 4 add edx, 4 sub esi, 4 jmp stragglers2 stragglers: cmp esi, 0 je do_xor_sse2_end mov al, [edi] mov cl, [edx] xor al, cl mov [edi], al inc edi inc edx dec esi jmp stragglers do_xor_sse2_end: pop edi pop esi pop ebp ret 12 .global _do_xor_avx2@12 /* void __stdcall do_xor_avx2(uint8_t* buf1, uint8_t* buf2, uint32_t len); */ _do_xor_avx2@12: /* edi = buf1 * edx = buf2 * esi = len * eax = tmp1 * ecx = tmp2 * xmm0 = tmp3 * xmm1 = tmp4 */ push ebp mov ebp, esp push esi push edi mov edi, [ebp+8] mov edx, [ebp+12] mov esi, [ebp+16] mov eax, edi and eax, 31 cmp eax, 0 jne stragglers4 mov eax, edx and eax, 31 cmp eax, 0 jne stragglers4 do_xor_avx2_loop: cmp esi, 32 jl stragglers4 vmovdqa ymm0, [edi] vmovdqa ymm1, [edx] vpxor ymm0, ymm0, ymm1 vmovdqa [edi], ymm0 add edi, 32 add edx, 32 sub esi, 32 jmp do_xor_avx2_loop stragglers4: cmp esi, 4 jl stragglers3 mov eax, [edi] mov ecx, [edx] xor eax, ecx mov [edi], eax add edi, 4 add edx, 4 sub esi, 4 jmp stragglers4 stragglers3: cmp esi, 0 je do_xor_avx2_end mov al, [edi] mov cl, [edx] xor al, cl mov [edi], al inc edi inc edx dec esi jmp stragglers3 do_xor_avx2_end: pop edi pop esi pop ebp ret 12 #endif ================================================ FILE: src/xor-masm.asm ================================================ ; Copyright (c) Mark Harmstone 2020 ; ; This file is part of WinBtrfs. ; ; WinBtrfs is free software: you can redistribute it and/or modify ; it under the terms of the GNU Lesser General Public Licence as published by ; the Free Software Foundation, either version 3 of the Licence, or ; (at your option) any later version. ; ; WinBtrfs is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU Lesser General Public Licence for more details. ; ; You should have received a copy of the GNU Lesser General Public Licence ; along with WinBtrfs. If not, see . IFDEF RAX ELSE .686P .xmm ENDIF _TEXT SEGMENT IFDEF RAX PUBLIC do_xor_sse2 ; void do_xor_sse2(uint8_t* buf1, uint8_t* buf2, uint32_t len); do_xor_sse2: ; rcx = buf1 ; rdx = buf2 ; r8d = len ; rax = tmp1 ; r9 = tmp2 ; xmm0 = tmp3 ; xmm1 = tmp4 mov rax, rcx and rax, 15 cmp rax, 0 jne stragglers2 mov rax, rdx and rax, 15 cmp rax, 0 jne stragglers2 do_xor_sse2_loop: cmp r8d, 16 jl stragglers2 movdqa xmm0, [rcx] movdqa xmm1, [rdx] pxor xmm0, xmm1 movdqa [rcx], xmm0 add rcx, 16 add rdx, 16 sub r8d, 16 jmp do_xor_sse2_loop stragglers2: cmp r8d, 8 jl stragglers mov rax, [rcx] mov r9, [rdx] xor rax, r9 mov [rcx], rax add rcx, 8 add rdx, 8 sub r8d, 8 jmp stragglers2 stragglers: cmp r8d, 0 je do_xor_sse2_end mov al, [rcx] mov r9b, [rdx] xor al, r9b mov [rcx], al inc rcx inc rdx dec r8d jmp stragglers do_xor_sse2_end: ret PUBLIC do_xor_avx2 ; void do_xor_avx2(uint8_t* buf1, uint8_t* buf2, uint32_t len); do_xor_avx2: ; rcx = buf1 ; rdx = buf2 ; r8d = len ; rax = tmp1 ; r9 = tmp2 ; xmm0 = tmp3 ; xmm1 = tmp4 mov rax, rcx and rax, 31 cmp rax, 0 jne stragglers4 mov rax, rdx and rax, 31 cmp rax, 0 jne stragglers4 do_xor_avx2_loop: cmp r8d, 32 jl stragglers4 vmovdqa ymm0, YMMWORD PTR[rcx] vmovdqa ymm1, YMMWORD PTR[rdx] vpxor ymm0, ymm0, ymm1 vmovdqa YMMWORD PTR[rcx], ymm0 add rcx, 32 add rdx, 32 sub r8d, 32 jmp do_xor_avx2_loop stragglers4: cmp r8d, 8 jl stragglers3 mov rax, [rcx] mov r9, [rdx] xor rax, r9 mov [rcx], rax add rcx, 8 add rdx, 8 sub r8d, 8 jmp stragglers4 stragglers3: cmp r8d, 0 je do_xor_avx2_end mov al, [rcx] mov r9b, [rdx] xor al, r9b mov [rcx], al inc rcx inc rdx dec r8d jmp stragglers3 do_xor_avx2_end: ret ELSE PUBLIC do_xor_sse2@12 ; void __stdcall do_xor_sse2(uint8_t* buf1, uint8_t* buf2, uint32_t len); do_xor_sse2@12: ; edi = buf1 ; edx = buf2 ; esi = len ; eax = tmp1 ; ecx = tmp2 ; xmm0 = tmp3 ; xmm1 = tmp4 push ebp mov ebp, esp push esi push edi mov edi, [ebp+8] mov edx, [ebp+12] mov esi, [ebp+16] mov eax, edi and eax, 15 cmp eax, 0 jne stragglers2 mov eax, edx and eax, 15 cmp eax, 0 jne stragglers2 do_xor_sse2_loop: cmp esi, 16 jl stragglers2 movdqa xmm0, [edi] movdqa xmm1, [edx] pxor xmm0, xmm1 movdqa [edi], xmm0 add edi, 16 add edx, 16 sub esi, 16 jmp do_xor_sse2_loop stragglers2: cmp esi, 4 jl stragglers mov eax, [edi] mov ecx, [edx] xor eax, ecx mov [edi], eax add edi, 4 add edx, 4 sub esi, 4 jmp stragglers2 stragglers: cmp esi, 0 je do_xor_sse2_end mov al, [edi] mov cl, [edx] xor al, cl mov [edi], al inc edi inc edx dec esi jmp stragglers do_xor_sse2_end: pop edi pop esi pop ebp ret 12 PUBLIC do_xor_avx2@12 ; void __stdcall do_xor_avx2(uint8_t* buf1, uint8_t* buf2, uint32_t len); do_xor_avx2@12: ; edi = buf1 ; edx = buf2 ; esi = len ; eax = tmp1 ; ecx = tmp2 ; xmm0 = tmp3 ; xmm1 = tmp4 push ebp mov ebp, esp push esi push edi mov edi, [ebp+8] mov edx, [ebp+12] mov esi, [ebp+16] mov eax, edi and eax, 31 cmp eax, 0 jne stragglers4 mov eax, edx and eax, 31 cmp eax, 0 jne stragglers4 do_xor_avx2_loop: cmp esi, 32 jl stragglers4 vmovdqa ymm0, YMMWORD PTR[edi] vmovdqa ymm1, YMMWORD PTR[edx] vpxor ymm0, ymm0, ymm1 vmovdqa YMMWORD PTR[edi], ymm0 add edi, 32 add edx, 32 sub esi, 32 jmp do_xor_avx2_loop stragglers4: cmp esi, 4 jl stragglers3 mov eax, [edi] mov ecx, [edx] xor eax, ecx mov [edi], eax add edi, 4 add edx, 4 sub esi, 4 jmp stragglers4 stragglers3: cmp esi, 0 je do_xor_avx2_end mov al, [edi] mov cl, [edx] xor al, cl mov [edi], al inc edi inc edx dec esi jmp stragglers3 do_xor_avx2_end: pop edi pop esi pop ebp ret 12 ENDIF _TEXT ENDS end ================================================ FILE: src/zstd-shim.h ================================================ #include #include static void* ZSTD_malloc(size_t size) { return NULL; } static void* ZSTD_calloc(size_t nmemb, size_t size) { return NULL; } static void ZSTD_free(void* ptr) { } #ifdef _MSC_VER #include #pragma intrinsic(_byteswap_uint64) #pragma intrinsic(_byteswap_ulong) #pragma intrinsic(_rotl) #pragma intrinsic(_rotl64) #endif