From e9625a20cf64bcb64c55018f0f9a8e14d746c7e1 Mon Sep 17 00:00:00 2001 From: jamersonpro Date: Wed, 20 Jan 2021 18:47:53 +0300 Subject: [PATCH] Initial version --- NtfsMarkBad.cpp | 215 ++ NtfsMarkBad_2005.sln | 26 + NtfsMarkBad_2005.vcproj | 842 ++++++ NtfsMarkBad_2019.sln | 31 + NtfsMarkBad_2019.vcxproj | 306 +++ NtfsMarkBad_2019.vcxproj.filters | 384 +++ README.md | 45 +- common.h | 82 + ifsutil/inc/bigint.hxx | 1268 +++++++++ ifsutil/inc/bpb.hxx | 136 + ifsutil/inc/dcache.hxx | 66 + ifsutil/inc/drive.hxx | 529 ++++ ifsutil/inc/ifssys.hxx | 58 + ifsutil/inc/intstack.hxx | 111 + ifsutil/inc/numset.hxx | 186 ++ ifsutil/inc/secrun.hxx | 255 ++ ifsutil/inc/supera.hxx | 134 + ifsutil/inc/untfs2.hxx | 424 +++ ifsutil/inc/volume.hxx | 132 + ifsutil/src/bigint.cxx | 142 + ifsutil/src/dcache.cxx | 169 ++ ifsutil/src/drive.cxx | 1847 +++++++++++++ ifsutil/src/ifssys.cxx | 343 +++ ifsutil/src/intstack.cxx | 231 ++ ifsutil/src/numset.cxx | 638 +++++ ifsutil/src/secrun.cxx | 196 ++ ifsutil/src/supera.cxx | 130 + ifsutil/src/volume.cxx | 182 ++ lib_x32/ntdll.lib | Bin 0 -> 523922 bytes lib_x64/ntdll.lib | Bin 0 -> 498012 bytes my_ntddk.h | 482 ++++ stdafx.cpp | 8 + stdafx.h | 13 + ulib/inc/array.hxx | 354 +++ ulib/inc/arrayit.hxx | 112 + ulib/inc/bitvect.hxx | 432 ++++ ulib/inc/clasdesc.hxx | 111 + ulib/inc/contain.hxx | 35 + ulib/inc/cstring.h | 166 ++ ulib/inc/hmem.hxx | 137 + ulib/inc/ifsentry.hxx | 28 + ulib/inc/iterator.hxx | 60 + ulib/inc/list.hxx | 100 + ulib/inc/listit.hxx | 105 + ulib/inc/mem.hxx | 40 + ulib/inc/membmgr.hxx | 220 ++ ulib/inc/membmgr2.hxx | 75 + ulib/inc/message.hxx | 63 + ulib/inc/object.hxx | 268 ++ ulib/inc/path.hxx | 419 +++ ulib/inc/seqcnt.hxx | 69 + ulib/inc/sortcnt.hxx | 60 + ulib/inc/stack.hxx | 220 ++ ulib/inc/system.hxx | 56 + ulib/inc/ulib.hxx | 64 + ulib/inc/ulibdef.hxx | 249 ++ ulib/inc/wstring.hxx | 1103 ++++++++ ulib/src/array.cxx | 664 +++++ ulib/src/arrayit.cxx | 250 ++ ulib/src/bitvect.cxx | 572 +++++ ulib/src/clasdesc.cxx | 60 + ulib/src/contain.cxx | 27 + ulib/src/hmem.cxx | 258 ++ ulib/src/iterator.cxx | 33 + ulib/src/list.cxx | 336 +++ ulib/src/listit.cxx | 37 + ulib/src/mem.cxx | 8 + ulib/src/membmgr.cxx | 411 +++ ulib/src/membmgr2.cxx | 224 ++ ulib/src/message.cxx | 134 + ulib/src/object.cxx | 76 + ulib/src/path.cxx | 958 +++++++ ulib/src/seqcnt.cxx | 69 + ulib/src/sortcnt.cxx | 27 + ulib/src/system.cxx | 76 + ulib/src/ulib.cxx | 179 ++ ulib/src/wstring.cxx | 1083 ++++++++ untfs/inc/attrib.hxx | 1046 ++++++++ untfs/inc/attrlist.hxx | 251 ++ untfs/inc/attrrec.hxx | 764 ++++++ untfs/inc/badfile.hxx | 90 + untfs/inc/bitfrs.hxx | 49 + untfs/inc/clusrun.hxx | 327 +++ untfs/inc/extents.hxx | 346 +++ untfs/inc/frs.hxx | 662 +++++ untfs/inc/frsstruc.hxx | 884 +++++++ untfs/inc/fsrtlp.h | 265 ++ untfs/inc/indxbuff.hxx | 375 +++ untfs/inc/indxroot.hxx | 268 ++ untfs/inc/indxtree.hxx | 439 ++++ untfs/inc/mft.hxx | 470 ++++ untfs/inc/mftfile.hxx | 134 + untfs/inc/mftinfo.hxx | 79 + untfs/inc/mftref.hxx | 55 + untfs/inc/ntfsbit.hxx | 571 ++++ untfs/inc/ntfssa.hxx | 396 +++ untfs/inc/ntfsvol.hxx | 57 + untfs/inc/untfs.hxx | 1353 ++++++++++ untfs/inc/upcase.hxx | 138 + untfs/inc/upfile.hxx | 52 + untfs/src/attrib.cxx | 3755 +++++++++++++++++++++++++++ untfs/src/attrlist.cxx | 1325 ++++++++++ untfs/src/attrrec.cxx | 911 +++++++ untfs/src/badfile.cxx | 337 +++ untfs/src/bitfrs.cxx | 115 + untfs/src/clusrun.cxx | 161 ++ untfs/src/extents.cxx | 1826 +++++++++++++ untfs/src/frs.cxx | 4147 ++++++++++++++++++++++++++++++ untfs/src/frsstruc.cxx | 769 ++++++ untfs/src/indxbuff.cxx | 888 +++++++ untfs/src/indxroot.cxx | 629 +++++ untfs/src/indxtree.cxx | 1893 ++++++++++++++ untfs/src/largemcb.cxx | 2781 ++++++++++++++++++++ untfs/src/mft.cxx | 380 +++ untfs/src/mftfile.cxx | 602 +++++ untfs/src/mftref.cxx | 118 + untfs/src/ntfsbit.cxx | 705 +++++ untfs/src/ntfssa.cxx | 954 +++++++ untfs/src/ntfsvol.cxx | 150 ++ untfs/src/untfs.cxx | 107 + untfs/src/upcase.cxx | 309 +++ untfs/src/upfile.cxx | 125 + 122 files changed, 51696 insertions(+), 1 deletion(-) create mode 100644 NtfsMarkBad.cpp create mode 100644 NtfsMarkBad_2005.sln create mode 100644 NtfsMarkBad_2005.vcproj create mode 100644 NtfsMarkBad_2019.sln create mode 100644 NtfsMarkBad_2019.vcxproj create mode 100644 NtfsMarkBad_2019.vcxproj.filters create mode 100644 common.h create mode 100644 ifsutil/inc/bigint.hxx create mode 100644 ifsutil/inc/bpb.hxx create mode 100644 ifsutil/inc/dcache.hxx create mode 100644 ifsutil/inc/drive.hxx create mode 100644 ifsutil/inc/ifssys.hxx create mode 100644 ifsutil/inc/intstack.hxx create mode 100644 ifsutil/inc/numset.hxx create mode 100644 ifsutil/inc/secrun.hxx create mode 100644 ifsutil/inc/supera.hxx create mode 100644 ifsutil/inc/untfs2.hxx create mode 100644 ifsutil/inc/volume.hxx create mode 100644 ifsutil/src/bigint.cxx create mode 100644 ifsutil/src/dcache.cxx create mode 100644 ifsutil/src/drive.cxx create mode 100644 ifsutil/src/ifssys.cxx create mode 100644 ifsutil/src/intstack.cxx create mode 100644 ifsutil/src/numset.cxx create mode 100644 ifsutil/src/secrun.cxx create mode 100644 ifsutil/src/supera.cxx create mode 100644 ifsutil/src/volume.cxx create mode 100644 lib_x32/ntdll.lib create mode 100644 lib_x64/ntdll.lib create mode 100644 my_ntddk.h create mode 100644 stdafx.cpp create mode 100644 stdafx.h create mode 100644 ulib/inc/array.hxx create mode 100644 ulib/inc/arrayit.hxx create mode 100644 ulib/inc/bitvect.hxx create mode 100644 ulib/inc/clasdesc.hxx create mode 100644 ulib/inc/contain.hxx create mode 100644 ulib/inc/cstring.h create mode 100644 ulib/inc/hmem.hxx create mode 100644 ulib/inc/ifsentry.hxx create mode 100644 ulib/inc/iterator.hxx create mode 100644 ulib/inc/list.hxx create mode 100644 ulib/inc/listit.hxx create mode 100644 ulib/inc/mem.hxx create mode 100644 ulib/inc/membmgr.hxx create mode 100644 ulib/inc/membmgr2.hxx create mode 100644 ulib/inc/message.hxx create mode 100644 ulib/inc/object.hxx create mode 100644 ulib/inc/path.hxx create mode 100644 ulib/inc/seqcnt.hxx create mode 100644 ulib/inc/sortcnt.hxx create mode 100644 ulib/inc/stack.hxx create mode 100644 ulib/inc/system.hxx create mode 100644 ulib/inc/ulib.hxx create mode 100644 ulib/inc/ulibdef.hxx create mode 100644 ulib/inc/wstring.hxx create mode 100644 ulib/src/array.cxx create mode 100644 ulib/src/arrayit.cxx create mode 100644 ulib/src/bitvect.cxx create mode 100644 ulib/src/clasdesc.cxx create mode 100644 ulib/src/contain.cxx create mode 100644 ulib/src/hmem.cxx create mode 100644 ulib/src/iterator.cxx create mode 100644 ulib/src/list.cxx create mode 100644 ulib/src/listit.cxx create mode 100644 ulib/src/mem.cxx create mode 100644 ulib/src/membmgr.cxx create mode 100644 ulib/src/membmgr2.cxx create mode 100644 ulib/src/message.cxx create mode 100644 ulib/src/object.cxx create mode 100644 ulib/src/path.cxx create mode 100644 ulib/src/seqcnt.cxx create mode 100644 ulib/src/sortcnt.cxx create mode 100644 ulib/src/system.cxx create mode 100644 ulib/src/ulib.cxx create mode 100644 ulib/src/wstring.cxx create mode 100644 untfs/inc/attrib.hxx create mode 100644 untfs/inc/attrlist.hxx create mode 100644 untfs/inc/attrrec.hxx create mode 100644 untfs/inc/badfile.hxx create mode 100644 untfs/inc/bitfrs.hxx create mode 100644 untfs/inc/clusrun.hxx create mode 100644 untfs/inc/extents.hxx create mode 100644 untfs/inc/frs.hxx create mode 100644 untfs/inc/frsstruc.hxx create mode 100644 untfs/inc/fsrtlp.h create mode 100644 untfs/inc/indxbuff.hxx create mode 100644 untfs/inc/indxroot.hxx create mode 100644 untfs/inc/indxtree.hxx create mode 100644 untfs/inc/mft.hxx create mode 100644 untfs/inc/mftfile.hxx create mode 100644 untfs/inc/mftinfo.hxx create mode 100644 untfs/inc/mftref.hxx create mode 100644 untfs/inc/ntfsbit.hxx create mode 100644 untfs/inc/ntfssa.hxx create mode 100644 untfs/inc/ntfsvol.hxx create mode 100644 untfs/inc/untfs.hxx create mode 100644 untfs/inc/upcase.hxx create mode 100644 untfs/inc/upfile.hxx create mode 100644 untfs/src/attrib.cxx create mode 100644 untfs/src/attrlist.cxx create mode 100644 untfs/src/attrrec.cxx create mode 100644 untfs/src/badfile.cxx create mode 100644 untfs/src/bitfrs.cxx create mode 100644 untfs/src/clusrun.cxx create mode 100644 untfs/src/extents.cxx create mode 100644 untfs/src/frs.cxx create mode 100644 untfs/src/frsstruc.cxx create mode 100644 untfs/src/indxbuff.cxx create mode 100644 untfs/src/indxroot.cxx create mode 100644 untfs/src/indxtree.cxx create mode 100644 untfs/src/largemcb.cxx create mode 100644 untfs/src/mft.cxx create mode 100644 untfs/src/mftfile.cxx create mode 100644 untfs/src/mftref.cxx create mode 100644 untfs/src/ntfsbit.cxx create mode 100644 untfs/src/ntfssa.cxx create mode 100644 untfs/src/ntfsvol.cxx create mode 100644 untfs/src/untfs.cxx create mode 100644 untfs/src/upcase.cxx create mode 100644 untfs/src/upfile.cxx diff --git a/NtfsMarkBad.cpp b/NtfsMarkBad.cpp new file mode 100644 index 0000000..04cc7a8 --- /dev/null +++ b/NtfsMarkBad.cpp @@ -0,0 +1,215 @@ +// NtfsMarkBad.cpp : This file contains the 'main' function. Program execution begins and ends there. +// + +#include "stdafx.h" + +#include "common.h" + +#include "ulib.hxx" +#include "message.hxx" +#include "system.hxx" +#include "ifssys.hxx" +#include "ntfsvol.hxx" + +#include // std::cout +#include // std::string, std::stoll +#include +#include +#include + +BOOLEAN DefineClassDescriptors() +{ + return UlibDefineClassDescriptors() && IfsutilDefineClassDescriptors() && UntfsDefineClassDescriptors(); +} + +unsigned char char_toupper(unsigned char c) +{ + return toupper(c); +} + +std::string str_toupper(std::string s) +{ + std::transform(s.begin(), s.end(), s.begin(), char_toupper); + return s; +} + +void About() +{ + std::cout << "Mark clusters as bad on NTFS\n\nUsage:\nNTFSMARKBAD drive: first_drive_sector_to_mark last_drive_sector_to_mark\n"; +}; + +// convert string to long long +__int64 stoll(const std::string& str) +{ + const char* ptr = str.c_str(); + char* error_ptr; + __int64 result = _strtoi64(ptr, &error_ptr, 10); + + if (ptr == error_ptr) + { + throw std::out_of_range("invalid stoll argument"); + } + + if (errno == EINVAL) + { + throw std::out_of_range("stoll argument out of range"); + } + + return result; +} + +int __cdecl +main( + ULONG nArgCount, + PSTR arrArguments[] +) +{ + MESSAGE Message; + Message.Initialize(); + + Message.Out("NTFSMARKBAD ", "0.0.1", +#if defined(_M_AMD64) + " x64" +#else + " x32" +#endif + ); + Message.Out(""); + + DefineClassDescriptors(); + + if (nArgCount != 4) + { + About(); + return 1; + } + + std::string drive = arrArguments[1]; + drive = str_toupper(drive); + + if (drive.length() != 2 + || drive[0] < 'A' || drive[0]>'Z' + || drive[1] != ':') + { + Message.Out("Invalid drive."); + } + + std::string firstSectorStr = arrArguments[2]; + __int64 firstSector; + try + { + firstSector = ::stoll(firstSectorStr); + } + catch (const std::out_of_range&) + { + firstSector = -1; + } + if (firstSector < 0) + { + Message.Out("Invalid first sector number."); + return 1; + } + + std::string lastSectorStr = arrArguments[3]; + __int64 lastSector; + try + { + lastSector = ::stoll(lastSectorStr); + } + catch (const std::out_of_range&) + { + lastSector = -1; + } + if (lastSector < 0 || lastSector < firstSector) + { + Message.Out("Invalid last sector number."); + return 1; + } + + + + DSTRING CurrentDrive; + if (!SYSTEM::QueryCurrentDosDriveName(&CurrentDrive)) + { + Message.Out("Error."); + return 1; + } + + DSTRING InputParamDrive; + InputParamDrive.Initialize(drive.c_str()); + + + DSTRING NtDriveName; + NtDriveName.Initialize("\\??\\"); + NtDriveName.Strcat(&InputParamDrive); + + + if (CurrentDrive == InputParamDrive) + { + Message.Out("Cannot lock current drive."); + return 1; + } + + + DSTRING ntfs_name; + if (!ntfs_name.Initialize("NTFS")) + { + Message.Out("Error."); + return 1; + } + + + NTSTATUS Status; + DSTRING drivename; + + BOOL FsNameIsNtfs; + + if (!IFS_SYSTEM::QueryFileSystemNameIsNtfs(&NtDriveName, + &FsNameIsNtfs, + &Status)) + { + if (Status == STATUS_ACCESS_DENIED) + { + Message.Out("Access denied."); + } + else if (Status != STATUS_SUCCESS) + { + Message.Out("Cannot open volume for direct access."); + } + else + { + Message.Out("Cannot determine file system of drive: ", drive.c_str()); + } + + return 1; + } + + if (!FsNameIsNtfs) //NOT NTFS + { + Message.Out("Only NTFS file system supported."); + return 1; + } + + + NTFS_VOL NtfsVol; + BOOLEAN Result; + + Result = NtfsVol.Initialize(&NtDriveName, &Message); + if (!Result) + { + Message.Out("Failed to initialize."); + return 1; + } + + Result = NtfsVol.MarkBad(firstSector, lastSector, &Message); + if (!Result) + { + Message.Out("An error has occurred."); + return 1; + } + + Message.Out("Completed."); + return 0; +} + + diff --git a/NtfsMarkBad_2005.sln b/NtfsMarkBad_2005.sln new file mode 100644 index 0000000..161c367 --- /dev/null +++ b/NtfsMarkBad_2005.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NtfsMarkBad_2005", "NtfsMarkBad_2005.vcproj", "{6C848416-93D5-41FC-A4DD-4651B7C590B2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Debug|Win32.ActiveCfg = Debug|Win32 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Debug|Win32.Build.0 = Debug|Win32 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Debug|x64.ActiveCfg = Debug|x64 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Debug|x64.Build.0 = Debug|x64 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Release|Win32.ActiveCfg = Release|Win32 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Release|Win32.Build.0 = Release|Win32 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Release|x64.ActiveCfg = Release|x64 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/NtfsMarkBad_2005.vcproj b/NtfsMarkBad_2005.vcproj new file mode 100644 index 0000000..7994dea --- /dev/null +++ b/NtfsMarkBad_2005.vcproj @@ -0,0 +1,842 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NtfsMarkBad_2019.sln b/NtfsMarkBad_2019.sln new file mode 100644 index 0000000..73240b0 --- /dev/null +++ b/NtfsMarkBad_2019.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30907.101 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NtfsMarkBad_2019", "NtfsMarkBad_2019.vcxproj", "{6C848416-93D5-41FC-A4DD-4651B7C590B2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Debug|Win32.ActiveCfg = Debug|Win32 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Debug|Win32.Build.0 = Debug|Win32 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Debug|x64.ActiveCfg = Debug|x64 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Debug|x64.Build.0 = Debug|x64 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Release|Win32.ActiveCfg = Release|Win32 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Release|Win32.Build.0 = Release|Win32 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Release|x64.ActiveCfg = Release|x64 + {6C848416-93D5-41FC-A4DD-4651B7C590B2}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {42BBD416-F019-4A2B-BABD-62299372C084} + EndGlobalSection +EndGlobal diff --git a/NtfsMarkBad_2019.vcxproj b/NtfsMarkBad_2019.vcxproj new file mode 100644 index 0000000..6d2e860 --- /dev/null +++ b/NtfsMarkBad_2019.vcxproj @@ -0,0 +1,306 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {6C848416-93D5-41FC-A4DD-4651B7C590B2} + NtfsMarkBad_2019 + Win32Proj + 10.0 + + + + Application + v142 + Unicode + true + + + Application + v142 + Unicode + true + + + Application + v142 + Unicode + + + Application + v142 + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>16.0.30804.86 + + + $(SolutionDir)$(PlatformShortName)\$(Configuration)\ + $(PlatformShortName)\$(Configuration)\ + true + NtfsMarkBad32 + + + true + NtfsMarkBad + + + $(SolutionDir)$(PlatformShortName)\$(Configuration)\ + $(PlatformShortName)\$(Configuration)\ + false + NtfsMarkBad32 + + + false + NtfsMarkBad + + + + Disabled + $(ProjectDir);$(ProjectDir)ulib\inc;$(ProjectDir)ifsutil\inc;$(ProjectDir)untfs\inc;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + NotUsing + All + $(IntDir)$(TargetName).pdb + Level3 + EditAndContinue + true + + + ntdll.lib;Setupapi.lib;%(AdditionalDependencies) + $(ProjectDir)lib_x32;%(AdditionalLibraryDirectories) + true + Console + MachineX86 + + + + + Disabled + $(ProjectDir);$(ProjectDir)ulib\inc;$(ProjectDir)ifsutil\inc;$(ProjectDir)untfs\inc;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + NotUsing + All + $(IntDir)$(TargetName).pdb + Level3 + ProgramDatabase + true + + + + ntdll.lib;Setupapi.lib;%(AdditionalDependencies) + $(ProjectDir)lib_x64;%(AdditionalLibraryDirectories) + true + Console + + + + + + + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + NotUsing + Level3 + ProgramDatabase + true + $(ProjectDir);$(ProjectDir)ulib\inc;$(ProjectDir)ifsutil\inc;$(ProjectDir)untfs\inc;%(AdditionalIncludeDirectories) + All + + + false + Console + true + true + MachineX86 + $(ProjectDir)lib_x32;%(AdditionalLibraryDirectories) + ntdll.lib;Setupapi.lib;%(AdditionalDependencies) + + + + + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + NotUsing + Level3 + ProgramDatabase + $(ProjectDir);$(ProjectDir)ulib\inc;$(ProjectDir)ifsutil\inc;$(ProjectDir)untfs\inc;%(AdditionalIncludeDirectories) + true + All + false + + + + false + Console + true + true + $(ProjectDir)lib_x64;%(AdditionalLibraryDirectories) + ntdll.lib;Setupapi.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NtfsMarkBad_2019.vcxproj.filters b/NtfsMarkBad_2019.vcxproj.filters new file mode 100644 index 0000000..a687b49 --- /dev/null +++ b/NtfsMarkBad_2019.vcxproj.filters @@ -0,0 +1,384 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {19465af2-b85e-409b-ad32-35f64b8d8073} + + + {e7dcc45c-2c51-4db7-89ac-f7d035fddf99} + + + {8d08cd53-2fe6-4567-8fb7-811b4695a1ba} + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {82e46de5-582e-457f-adfd-2d6419c5b912} + + + {18d04404-af26-4312-805c-f5ae6b821506} + + + {7428b833-904f-485a-a10b-936644985f42} + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files\ifsutil + + + Source Files\ifsutil + + + Source Files\ifsutil + + + Source Files\ifsutil + + + Source Files\ifsutil + + + Source Files\ifsutil + + + Source Files\ifsutil + + + Source Files\ifsutil + + + Source Files\ifsutil + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\ulib + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + Source Files\untfs + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files\ifsutil + + + Header Files\ifsutil + + + Header Files\ifsutil + + + Header Files\ifsutil + + + Header Files\ifsutil + + + Header Files\ifsutil + + + Header Files\ifsutil + + + Header Files\ifsutil + + + Header Files\ifsutil + + + Header Files\ifsutil + + + Header Files\ifsutil + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\ulib + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + Header Files\untfs + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 0d06cb3..1ec8e01 100644 --- a/README.md +++ b/README.md @@ -1 +1,44 @@ -# ntfsmarkbad \ No newline at end of file +# NtfsMarkBad + +## About + +This utility allow to mark clusters as bad without checking on NTFS file system. + +Just specify the first and last sectors of the bad area. Use physical sector numbers of the disk storage, not logical volume sector numbers. + +Only unused clusters are processed. + +Compiled to 64-bit (file NTFSMARKBAD.EXE). Also a 32-bit version available (file NTFSMARKBAD32.EXE). + +Tested on Windows 7 SP1 (x32 and x64) and Windows 10 (x32 and x64). +Can be run from the Windows Setup command line (use the version that exactly matches the Windows version). + +To clear the list of bad clusters reformat volume or run CHKDSK /B to rescan clusters for errors. + +## Usage + +To mark sectors from 100001 to 100010 (physical sector numbers) on volume D: + +1) Open the command prompt as administrator + +``` +CMD +``` + +2) Quick format volume to NTFS + +``` +FORMAT D: /FS:NTFS /Q +``` + +3) Mark sectors as bad + +``` +NTFSMARKBAD D: 100001 100010 +``` + +4) Check file system state + +``` +CHKDSK D: /F +``` diff --git a/common.h b/common.h new file mode 100644 index 0000000..87eaccf --- /dev/null +++ b/common.h @@ -0,0 +1,82 @@ +#pragma once + +#define _CRT_SECURE_NO_DEPRECATE +#define _CRT_NON_CONFORMING_SWPRINTFS +#define _WIN32_WINNT 0x0601 +#define DECLSPEC_DEPRECATED_DDK + +#include + + +#include +#include +#include +#include +#include +#include + + +#include + +#define WIN32_NO_STATUS +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include + +#define NTKERNELAPI __declspec(dllimport) + +typedef ULONG CLONG; + +#include +#include +#include +#include +#include + +#include "my_ntddk.h" + + +#define ASSERT(x) assert(x) +#define ASSERTMSG(message,x) assert((message)&&(x)) +#define NOTHING do{}while(false) +#define ARGUMENT_PRESENT(ArgumentPointer) (\ + (CHAR *)((ULONG_PTR)(ArgumentPointer)) != (CHAR *)(NULL) ) + + + +#include + +#define WIN32_NO_STATUS +//#include + +//#include +//#include +//#include +#include +#include +#include + + +#include +#ifndef _NTSRB_ +#define _NTSRB_ +#endif +#include + + +#include +#include + + +//------------------------- + +#define _NTDEF_ +#include + + +#define ARGUMENT_PRESENT(ArgumentPointer) (\ + (CHAR *)((ULONG_PTR)(ArgumentPointer)) != (CHAR *)(NULL) ) + + + + + diff --git a/ifsutil/inc/bigint.hxx b/ifsutil/inc/bigint.hxx new file mode 100644 index 0000000..e6afeac --- /dev/null +++ b/ifsutil/inc/bigint.hxx @@ -0,0 +1,1268 @@ +/*++ + +Module Name: + + bigint.hxx + +Abstract: + + The BIG_INT class models a 64 bit signed integer. + + This class is meant to be light and will occupy only 64 bits of space. + It should be manipulated exactly as an INT would be. + + There will be no constructor or destructor. A BIG_INT will be + uninitialized until a value is assigned to it. + + This implementation of BIG_INT uses the NT LARGE_INTEGER structure. + +--*/ + + +#pragma once + + +#include "ulib.hxx" + + + +DEFINE_POINTER_AND_REFERENCE_TYPES( LARGE_INTEGER ); + +DECLARE_CLASS( BIG_INT ); + + +class BIG_INT { + + public: + + + BIG_INT( + ); + + + BIG_INT( + IN const INT LowPart + ); + + + BIG_INT( + IN const UINT LowPart + ); + + + BIG_INT( + IN const ULONG LowPart + ); + + + BIG_INT( + IN const SLONG LowPart + ); + + + BIG_INT( + IN const LARGE_INTEGER LargeInteger + ); + + + BIG_INT( + IN const ULONGLONG UlongLong + ); + + + VOID + operator=( + IN const INT LowPart + ); + + + VOID + operator=( + IN const UINT LowPart + ); + + + VOID + operator=( + IN const SLONG LowPart + ); + + + VOID + operator=( + IN const ULONG LowPart + ); + + + VOID + operator=( + IN const LARGE_INTEGER LargeInteger + ); + + + VOID + operator=( + IN const LONG64 Long64 + ) + + { + x = Long64; + } + + + VOID + operator=( + IN const ULONG64 Ulong64 + ) + + { + x = Ulong64; + } + + + VOID + Set( + IN const ULONG LowPart, + IN const SLONG HighPart + ); + + + + VOID + Set( + IN UCHAR ByteCount, + IN PCUCHAR CompressedInteger + ); + + + const ULONG + GetLowPart( + ) CONST; + + + const SLONG + GetHighPart( + ) CONST; + + + LARGE_INTEGER + GetLargeInteger( + ) CONST; + + + LONGLONG + GetQuadPart( + ) CONST; + + + + VOID + QueryCompressedInteger( + OUT PUCHAR ByteCount, + OUT PUCHAR CompressedInteger + ) CONST; + + + VOID + operator+=( + IN const BIG_INT BigInt + ); + + + BIG_INT + operator-( + ) CONST; + + + VOID + operator-=( + IN const BIG_INT BigInt + ); + + FRIEND + BIG_INT + operator+( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + FRIEND + BIG_INT + operator-( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + FRIEND + BIG_INT + operator*( + IN const BIG_INT Left, + IN const SLONG Right + ); + + FRIEND + BIG_INT + operator*( + IN const SLONG Left, + IN const BIG_INT Right + ); + + FRIEND + BIG_INT + operator/( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + FRIEND + BIG_INT + operator%( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + FRIEND + BOOLEAN + operator==( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + FRIEND + BOOLEAN + operator!=( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + FRIEND + BOOLEAN + operator<( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + FRIEND + BOOLEAN + operator<=( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + FRIEND + BOOLEAN + operator>( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + FRIEND + BOOLEAN + operator>=( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + FRIEND + BOOLEAN + CompareLT( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + FRIEND + BOOLEAN + CompareLTEQ( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + FRIEND + BOOLEAN + CompareGT( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + FRIEND + BOOLEAN + CompareGTEQ( + IN const BIG_INT Left, + IN const BIG_INT Right + ); + + private: + + __int64 x; + +}; + +INLINE +BIG_INT::BIG_INT( + ) +/*++ + +Routine Description: + + Constructor for BIG_INT. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + x = 0; +} + +INLINE +VOID +BIG_INT::operator=( + IN const INT LowPart + ) +/*++ + +Routine Description: + + This routine copies an INT into a BIG_INT. + +Arguments: + + LowPart - Supplies an integer. + +Return Value: + + None. + +--*/ +{ + x = LowPart; +} + + +INLINE +VOID +BIG_INT::operator=( + IN const UINT LowPart + ) +/*++ + +Routine Description: + + This routine copies a UINT into a BIG_INT. + +Arguments: + + LowPart - Supplies an unsigned integer. + +Return Value: + + None. + +--*/ +{ + x = LowPart; +} + + +INLINE +VOID +BIG_INT::operator=( + IN const SLONG LowPart + ) +/*++ + +Routine Description: + + This routine copies a LONG into a BIG_INT. + +Arguments: + + LowPart - Supplies a long integer. + +Return Value: + + None. + +--*/ +{ + x = LowPart; +} + + +INLINE +VOID +BIG_INT::operator=( + IN const ULONG LowPart + ) +/*++ + +Routine Description: + + This routine copies a ULONG into a BIG_INT. + +Arguments: + + LowPart - Supplies an unsigned long integer. + +Return Value: + + None. + +--*/ +{ + x = LowPart; +} + +INLINE +VOID +BIG_INT::operator=( + IN const LARGE_INTEGER LargeInteger + ) +/*++ + +Routine Description: + + This routine copies a LARGE_INTEGER into a BIG_INT. + +Arguments: + + LargeInteger -- supplies a large integer + +Return Value: + + None. + +--*/ +{ + x = LargeInteger.QuadPart; +} + + +INLINE +BIG_INT::BIG_INT( + IN const INT LowPart + ) +/*++ + +Routine Description: + + Constructor for BIG_INT. + +Arguments: + + LowPart - Supplies an integer. + +Return Value: + + None. + +--*/ +{ + x = LowPart; +} + + +INLINE +BIG_INT::BIG_INT( + IN const UINT LowPart + ) +/*++ + +Routine Description: + + Constructor for BIG_INT. + +Arguments: + + LowPart - Supplies an unsigned integer. + +Return Value: + + None. + +--*/ +{ + x = LowPart; +} + + +INLINE +BIG_INT::BIG_INT( + IN const SLONG LowPart + ) +/*++ + +Routine Description: + + Constructor for BIG_INT. + +Arguments: + + LowPart - Supplies a long integer. + +Return Value: + + None. + +--*/ +{ + x = LowPart; +} + + +INLINE +BIG_INT::BIG_INT( + IN const ULONG LowPart + ) +/*++ + +Routine Description: + + Constructor for BIG_INT. + +Arguments: + + LowPart - Supplies an unsigned long integer. + +Return Value: + + None. + +--*/ +{ + x = LowPart; +} + +INLINE +BIG_INT::BIG_INT( + IN const LARGE_INTEGER LargeInteger + ) +/*++ + +Routine Description: + + Constructor for BIG_INT to permit initialization with a LARGE_INTEGER + +Arguments: + + LargeInteger -- supplies a large integer. + +Return Value: + + None. + +--*/ +{ + x = LargeInteger.QuadPart; +} + +INLINE +BIG_INT::BIG_INT( + IN const ULONGLONG UlongLong + ) +/*++ + +Routine Description: + + Constructor for BIG_INT to permit initialization with a ULONGLOGN + +Arguments: + + UlongLong -- supplies a unsigned 64-bit int. + +Return Value: + + None. + +--*/ +{ + x = UlongLong; +} + +INLINE +VOID +BIG_INT::Set( + IN const ULONG LowPart, + IN const SLONG HighPart + ) +/*++ + +Routine Description: + + This routine sets a BIG_INT to an initial value. + +Arguments: + + LowPart - Supplies the low part of the BIG_INT. + HighPart - Supplies the high part of the BIG_INT. + +Return Value: + + None. + +--*/ +{ + x = (__int64)(((ULONGLONG)HighPart << 32) | LowPart); +} + + +INLINE +const ULONG +BIG_INT::GetLowPart( + ) CONST +/*++ + +Routine Description: + + This routine computes the low part of the BIG_INT. + +Arguments: + + None. + +Return Value: + + The low part of the BIG_INT. + +--*/ +{ + return (ULONG)(((ULONGLONG)x) & 0xFFFFFFFF); +} + + +// Note: billmc -- this could probably return an RCLONG, for +// greater efficiency, but that generates warnings. + +INLINE +const SLONG +BIG_INT::GetHighPart( + ) CONST +/*++ + +Routine Description: + + This routine computes the high part of the BIG_INT. + +Arguments: + + None. + +Return Value: + + The high part of the BIG_INT. + +--*/ +{ + LARGE_INTEGER r; + + r.QuadPart = x; + return r.HighPart; +} + + +INLINE +LARGE_INTEGER +BIG_INT::GetLargeInteger( + ) CONST +/*++ + +Routine Description: + + This routine returns the large integer embedded in the BIG_INT. + +Arguments: + + None. + +Return Value: + + The large-integer value of the BIG_INT. + +--*/ +{ + LARGE_INTEGER r; + + r.QuadPart = x; + return r; +} + +INLINE +LONGLONG +BIG_INT::GetQuadPart( + ) CONST +/*++ + +Routine Description: + + This routine returns the large integer embedded in the BIG_INT. + +Arguments: + + None. + +Return Value: + + The large-integer value of the BIG_INT. + +--*/ +{ + return x; +} + + +INLINE +VOID +BIG_INT::operator+=( + IN const BIG_INT BigInt + ) +/*++ + +Routine Description: + + This routine adds another BIG_INT to this one. + +Arguments: + + BigInt - Supplies the BIG_INT to add to the current BIG_INT. + +Return Value: + + None. + +--*/ +{ + x += BigInt.x; +} + + + +INLINE +BIG_INT +BIG_INT::operator-( + ) CONST +/*++ + +Routine Description: + + This routine computes the negation of the current BIG_INT. + +Arguments: + + None. + +Return Value: + + The negation of the current BIG_INT. + +--*/ +{ + BIG_INT r; + + r.x = -x; + + return r; +} + + + +INLINE +VOID +BIG_INT::operator-=( + IN const BIG_INT BigInt + ) +/*++ + +Routine Description: + + This routine subtracts a BIG_INT from this one. + +Arguments: + + BigInt - Supplies a BIG_INT to subtract from the current BIG_INT. + +Return Value: + + None. + +--*/ +{ + x -= BigInt.x; +} + + + +INLINE +BIG_INT +operator+( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine computes the sum of two BIG_INTs. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + The sum of Left and Right. + +--*/ +{ + BIG_INT r; + + r.x = Left.x + Right.x; + return r; +} + + + +INLINE +BIG_INT +operator-( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine computes the difference of two BIG_INTs. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + The difference between Left and Right. + +--*/ +{ + BIG_INT r; + + r.x = Left.x - Right.x; + return r; +} + + + +INLINE +BIG_INT +operator*( + IN const BIG_INT Left, + IN const SLONG Right + ) +/*++ + +Routine Description: + + This routine computes the product of a BIG_INT and a LONG. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + The product of Left and Right. + +--*/ +{ + BIG_INT r; + + r.x = Left.x * Right; + return r; +} + + + +INLINE +BIG_INT +operator*( + IN const SLONG Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine computes the product of a BIG_INT and a LONG. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + The product of Left and Right. + +--*/ +{ + return Right*Left; +} + + + +INLINE +BIG_INT +operator/( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine computes the quotient of two BIG_INTs. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + The quotient of Left and Right. + +--*/ +{ + BIG_INT r; + + r.x = Left.x / Right.x; + return r; +} + + + +INLINE +BIG_INT +operator%( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine computes the modulus of two BIG_INTs. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + The modulus of Left and Right. + +--*/ +{ + BIG_INT r; + + r.x = Left.x % Right.x; + return r; +} + + + +INLINE +BOOLEAN +operator<( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine compares two BIG_INTs. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + FALSE - Left is not less than Right. + TRUE - Left is less than Right. + +--*/ +{ + return Left.x < Right.x; +} + + + +INLINE +BOOLEAN +operator<=( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine compares two BIG_INTs. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + FALSE - Left is not less than or equal to Right. + TRUE - Left is less than or equal to Right. + +--*/ +{ + return Left.x <= Right.x; +} + + + +INLINE +BOOLEAN +operator>( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine compares two BIG_INTs. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + FALSE - Left is not greater than Right. + TRUE - Left is greater than Right. + +--*/ +{ + return Left.x > Right.x; +} + + + +INLINE +BOOLEAN +operator>=( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine compares two BIG_INTs. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + FALSE - Left is not greater than or equal to Right. + TRUE - Left is greater than or equal to Right. + +--*/ +{ + return Left.x >= Right.x; +} + + + +INLINE +BOOLEAN +operator==( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine compares two BIG_INTs for equality. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + FALSE - Left is not equal to Right. + TRUE - Left is equal to Right. + +--*/ +{ + return Left.x == Right.x; +} + + + +INLINE +BOOLEAN +operator!=( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine compares two BIG_INTs for equality. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + FALSE - Left is equal to Right. + TRUE - Left is not equal to Right. + +--*/ +{ + return Left.x != Right.x; +} + + +INLINE +BOOLEAN +CompareGTEQ( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine compares two BIG_INTs by treating them + as unsigned numbers. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + FALSE - Left is not greater than or equal to Right. + TRUE - Left is greater than or equal to Right. + +--*/ +{ + return (unsigned __int64)Left.x >= (unsigned __int64)Right.x; +} + +INLINE +BOOLEAN +CompareGT( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine compares two BIG_INTs by treating them + as unsigned numbers. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + FALSE - Left is not greater than Right. + TRUE - Left is greater than Right. + +--*/ +{ + return (unsigned __int64)Left.x > (unsigned __int64)Right.x; +} + + +INLINE +BOOLEAN +CompareLTEQ( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine compares two BIG_INTs by treating them + as unsigned numbers. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + FALSE - Left is not less than or equal to Right. + TRUE - Left is less than or equal to Right. + +--*/ +{ + return (unsigned __int64)Left.x <= (unsigned __int64)Right.x; +} + + +INLINE +BOOLEAN +CompareLT( + IN const BIG_INT Left, + IN const BIG_INT Right + ) +/*++ + +Routine Description: + + This routine compares two BIG_INTs by treating them + as unsigned numbers. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + FALSE - Left is not less than Right. + TRUE - Left is less than Right. + +--*/ +{ + return (unsigned __int64)Left.x < (unsigned __int64)Right.x; +} + diff --git a/ifsutil/inc/bpb.hxx b/ifsutil/inc/bpb.hxx new file mode 100644 index 0000000..9aff4d6 --- /dev/null +++ b/ifsutil/inc/bpb.hxx @@ -0,0 +1,136 @@ +/*++ + + +Module Name: + + bpb.hxx + +Abstract: + + This module contains the declarations for packed and + unpacked Bios Parameter Block + +--*/ + +#pragma once + + +// DEFINE THIS UNCHANGED PBPB for use on NTFS drive PBootSector +typedef struct _OLD_PACKED_BIOS_PARAMETER_BLOCK { + UCHAR BytesPerSector[2]; // offset = 0x000 + UCHAR SectorsPerCluster[1]; // offset = 0x002 + UCHAR ReservedSectors[2]; // offset = 0x003 + UCHAR Fats[1]; // offset = 0x005 + UCHAR RootEntries[2]; // offset = 0x006 + UCHAR Sectors[2]; // offset = 0x008 + UCHAR Media[1]; // offset = 0x00A + UCHAR SectorsPerFat[2]; // offset = 0x00B + UCHAR SectorsPerTrack[2]; // offset = 0x00D + UCHAR Heads[2]; // offset = 0x00F + UCHAR HiddenSectors[4]; // offset = 0x011 + UCHAR LargeSectors[4]; // offset = 0x015 +} OLD_PACKED_BIOS_PARAMETER_BLOCK; // sizeof = 0x019 + + +typedef struct BIOS_PARAMETER_BLOCK { + USHORT BytesPerSector; + UCHAR SectorsPerCluster; + USHORT ReservedSectors; + UCHAR Fats; + USHORT RootEntries; + USHORT Sectors; + UCHAR Media; + USHORT SectorsPerFat; + USHORT SectorsPerTrack; + USHORT Heads; + ULONG HiddenSectors; + ULONG LargeSectors; + ULONG BigSectorsPerFat; + USHORT ExtFlags; + USHORT FS_Version; + ULONG RootDirStrtClus; + USHORT FSInfoSec; + USHORT BkUpBootSec; +} BIOS_PARAMETER_BLOCK; +typedef BIOS_PARAMETER_BLOCK *PBIOS_PARAMETER_BLOCK; + +// +// The following types and macros are used to help unpack the packed and +// misaligned fields found in the Bios parameter block +// +typedef union _UCHAR1 { + UCHAR Uchar[1]; + UCHAR ForceAlignment; +} UCHAR1, *PUCHAR1; + +typedef union _UCHAR2 { + UCHAR Uchar[2]; + USHORT ForceAlignment; +} UCHAR2, *PUCHAR2; + +typedef union _UCHAR4 { + UCHAR Uchar[4]; + ULONG ForceAlignment; +} UCHAR4, *PUCHAR4; + +#define CopyUchar1(Dst,Src) { \ + *((UCHAR1 *)(Dst)) = *((UNALIGNED UCHAR1 *)(Src)); \ +} + +#define CopyUchar2(Dst,Src) { \ + *((UCHAR2 *)(Dst)) = *((UNALIGNED UCHAR2 *)(Src)); \ +} + +#define CopyU2char(Dst,Src) { \ + *((UNALIGNED UCHAR2 *)(Dst)) = *((UCHAR2 *)(Src)); \ +} + +#define CopyUchar4(Dst,Src) { \ + *((UCHAR4 *)(Dst)) = *((UNALIGNED UCHAR4 *)((ULONG_PTR)(Src))); \ +} + +#define CopyU4char(Dst, Src) { \ + *((UNALIGNED UCHAR4 *)(Dst)) = *((UCHAR4 *)(Src)); \ +} + + +// +// This macro takes a Packed BPB and fills in its Unpacked equivalent +// + + +#define UnpackBios(Bios,Pbios) { \ + CopyUchar2(&((Bios)->BytesPerSector), (Pbios)->BytesPerSector ); \ + CopyUchar1(&((Bios)->SectorsPerCluster), (Pbios)->SectorsPerCluster); \ + CopyUchar2(&((Bios)->ReservedSectors), (Pbios)->ReservedSectors ); \ + CopyUchar1(&((Bios)->Fats), (Pbios)->Fats ); \ + CopyUchar2(&((Bios)->RootEntries), (Pbios)->RootEntries ); \ + CopyUchar2(&((Bios)->Sectors), (Pbios)->Sectors ); \ + CopyUchar1(&((Bios)->Media), (Pbios)->Media ); \ + CopyUchar2(&((Bios)->SectorsPerFat), (Pbios)->SectorsPerFat ); \ + CopyUchar2(&((Bios)->SectorsPerTrack), (Pbios)->SectorsPerTrack ); \ + CopyUchar2(&((Bios)->Heads), (Pbios)->Heads ); \ + CopyUchar4(&((Bios)->HiddenSectors), (Pbios)->HiddenSectors ); \ + CopyUchar4(&((Bios)->LargeSectors), (Pbios)->LargeSectors ); \ +} + + +// +// This macro takes an Unpacked BPB and fills in its Packed equivalent +// + +#define PackBios(Bios,Pbios) { \ + CopyU2char((Pbios)->BytesPerSector, &((Bios)->BytesPerSector) ); \ + CopyUchar1((Pbios)->SectorsPerCluster, &((Bios)->SectorsPerCluster)); \ + CopyU2char((Pbios)->ReservedSectors, &((Bios)->ReservedSectors) ); \ + CopyUchar1((Pbios)->Fats, &((Bios)->Fats) ); \ + CopyU2char((Pbios)->RootEntries, &((Bios)->RootEntries) ); \ + CopyU2char((Pbios)->Sectors, &((Bios)->Sectors) ); \ + CopyUchar1((Pbios)->Media, &((Bios)->Media) ); \ + CopyU2char((Pbios)->SectorsPerFat, &((Bios)->SectorsPerFat) ); \ + CopyU2char((Pbios)->SectorsPerTrack, &((Bios)->SectorsPerTrack) ); \ + CopyU2char((Pbios)->Heads, &((Bios)->Heads) ); \ + CopyU4char((Pbios)->HiddenSectors, &((Bios)->HiddenSectors) ); \ + CopyU4char((Pbios)->LargeSectors, &((Bios)->LargeSectors) ); \ +} + diff --git a/ifsutil/inc/dcache.hxx b/ifsutil/inc/dcache.hxx new file mode 100644 index 0000000..24e0e90 --- /dev/null +++ b/ifsutil/inc/dcache.hxx @@ -0,0 +1,66 @@ +/*++ + +Module Name: + + dcache.hxx + +Abstract: + + This class models a general cache for reading and writing. + The actual implementation of this base class is to not have + any cache at all. + +--*/ + +#pragma once + +#include "bigint.hxx" +#include "drive.hxx" + +DECLARE_CLASS(DRIVE_CACHE); + +class DRIVE_CACHE : public OBJECT { + + public: + + DECLARE_CONSTRUCTOR( DRIVE_CACHE ); + + VIRTUAL + ~DRIVE_CACHE( + ); + + BOOLEAN + Initialize( + IN OUT PIO_DP_DRIVE Drive + ); + + VIRTUAL + BOOLEAN + Read( + IN BIG_INT StartingSector, + IN SECTORCOUNT NumberOfSectors, + OUT PVOID Buffer + ); + + VIRTUAL + BOOLEAN + Write( + IN BIG_INT StartingSector, + IN SECTORCOUNT NumberOfSectors, + IN PVOID Buffer + ); + + private: + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + PIO_DP_DRIVE _drive; +}; + diff --git a/ifsutil/inc/drive.hxx b/ifsutil/inc/drive.hxx new file mode 100644 index 0000000..1e965e1 --- /dev/null +++ b/ifsutil/inc/drive.hxx @@ -0,0 +1,529 @@ +/*++ + +Module Name: + + drive.hxx + +Abstract: + + The drive class hierarchy models the concept of a drive in various + stages. It looks like this: + + DRIVE + DP_DRIVE + IO_DP_DRIVE + LOG_IO_DP_DRIVE + PHYS_IO_DP_DRIVE + + DRIVE + ----- + + DRIVE implements a container for the drive path which is recognizable + by the file system. 'Initialize' takes the path as an argument so + that it can be later queried with 'GetNtDriveName'. + + + DP_DRIVE + -------- + + DP_DRIVE (Drive Parameters) implements queries for the geometry of the + drive. 'Initiliaze' queries the information from the drive. What + is returned is the default drive geometry for the drive. The user + may ask by means of 'IsSupported' if the physical device will support + another MEDIA_TYPE. + + A protected member function called 'SetMediaType' allows the a derived + class to set the MEDIA_TYPE to another media type which is supported + by the device. This method is protected because only a low-level + format will actually change the media type. + + + IO_DP_DRIVE + ----------- + + IO_DP_DRIVE implements the reading and writing of sectors as well + as 'Lock', 'Unlock', and 'Dismount'. The 'FormatVerifyFloppy' method + does a low-level format. A version of this method allows the user + to specify a new MEDIA_TYPE for the media. + + + LOG_IO_DP_DRIVE and PHYS_IO_DP_DRIVE + ------------------------------------ + + LOG_IO_DP_DRIVE models logical drive. PHYS_IO_DP_DRIVE models a + physical drive. Currently both implementations just initialize + an IO_DP_DRIVE. The difference is in the drive path specified. + Some drive paths are to logical drives and others are to physical + drives. + +--*/ + +#pragma once + + +#include "wstring.hxx" +#include "bigint.hxx" + + +// +// Forward references +// + +DECLARE_CLASS( DRIVE ); +DECLARE_CLASS( DP_DRIVE ); +DECLARE_CLASS( IO_DP_DRIVE ); +DECLARE_CLASS( LOG_IO_DP_DRIVE ); + +DECLARE_CLASS( NUMBER_SET ); +DECLARE_CLASS( MESSAGE ); +DECLARE_CLASS( DRIVE_CACHE ); + +#include "ifsentry.hxx" + +DEFINE_TYPE( ULONG, SECTORCOUNT ); // count of sectors +DEFINE_TYPE( ULONG, LBN ); // Logical buffer number + +DEFINE_POINTER_AND_REFERENCE_TYPES( MEDIA_TYPE ); +DEFINE_POINTER_AND_REFERENCE_TYPES( DISK_GEOMETRY ); + +struct _DRTYPE { + MEDIA_TYPE MediaType; + ULONG SectorSize; + BIG_INT Sectors; // w/o hidden sectors. + BIG_INT HiddenSectors; + SECTORCOUNT SectorsPerTrack; + ULONG Heads; +}; + +DEFINE_TYPE( struct _DRTYPE, DRTYPE ); + +class DRIVE : public OBJECT { + +public: + + DECLARE_CONSTRUCTOR(DRIVE); + + VIRTUAL + ~DRIVE( + ); + + BOOLEAN + Initialize( + IN PCWSTRING NtDriveName, + IN OUT PMESSAGE Message DEFAULT NULL + ); + +private: + + VOID + Construct( + ); + + VOID + Destroy( + ); + + DSTRING _name; +}; + + +class DP_DRIVE : public DRIVE { + +public: + + DECLARE_CONSTRUCTOR(DP_DRIVE); + + VIRTUAL + + ~DP_DRIVE( + ); + + BOOLEAN + Initialize( + IN PCWSTRING NtDriveName, + IN OUT PMESSAGE Message DEFAULT NULL, + IN BOOLEAN IsTransient DEFAULT FALSE + ); + + MEDIA_TYPE + QueryMediaType( + ) CONST; + + VIRTUAL + ULONG + QuerySectorSize( + ) CONST; + + VIRTUAL + BIG_INT + QuerySectors( + ) CONST; + + + BIG_INT + QueryHiddenSectors( + ) CONST; + + + + ULONG + QueryAlignmentMask( + ) CONST; + + + NTSTATUS + QueryLastNtStatus( + ) CONST; + + +protected: + + + // On a normal drive, _handle is a handle to the drive + HANDLE _handle; + NTSTATUS _last_status; + PARTITION_INFORMATION_EX _partition_info; + +private: + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + STATIC + NTSTATUS + OpenDrive( + IN PCWSTRING NtDriveName, + IN ACCESS_MASK DesiredAccess, + IN BOOLEAN ExclusiveWrite, + OUT PHANDLE Handle, + OUT PULONG Alignment, + IN OUT PMESSAGE Message + ); + + STATIC + VOID + DiskGeometryToDriveType( + IN PCDISK_GEOMETRY DiskGeometry, + OUT PDRTYPE DriveType + ); + + STATIC + VOID + DiskGeometryToDriveType( + IN PCDISK_GEOMETRY DiskGeometry, + IN BIG_INT NumSectors, + IN BIG_INT NumHiddenSectors, + OUT PDRTYPE DriveType + ); + + BOOLEAN + CheckForPrimaryPartition( + ); + + + DRTYPE _actual; + PDRTYPE _supported_list; + INT _num_supported; + ULONG _alignment_mask; + + BOOLEAN _is_writeable; + BOOLEAN _is_primary_partition; + DRIVE_TYPE _drive_type; +}; + +INLINE +MEDIA_TYPE +DP_DRIVE::QueryMediaType( + ) CONST +/*++ + +Routine Description: + + This routine computes the media type. + +Arguments: + + None. + +Return Value: + + The media type. + +--*/ +{ + return _actual.MediaType; +} + + +INLINE +BIG_INT +DP_DRIVE::QueryHiddenSectors( + ) CONST +/*++ + +Routine Description: + + This routine computes the number of hidden sectors. + +Arguments: + + None. + +Return Value: + + The number of hidden sectors. + +--*/ +{ + return _actual.HiddenSectors; +} + +INLINE +ULONG +DP_DRIVE::QueryAlignmentMask( + ) CONST +/*++ + +Routine Description: + + This routine returns the memory alignment requirement for the drive. + +Arguments: + + None. + +Return Value: + + The memory alignment requirement for the drive in the form of a mask. + +--*/ +{ + return _alignment_mask; +} + + +INLINE +NTSTATUS +DP_DRIVE::QueryLastNtStatus( + ) CONST +/*++ + +Routine Description: + + This routine returns the last NT status value. + +Arguments: + + None. + +Return Value: + + The last NT status value. + +--*/ +{ + return _last_status; +} + + + +class IO_DP_DRIVE : public DP_DRIVE { + + FRIEND class DRIVE_CACHE; + +public: + + VIRTUAL + ~IO_DP_DRIVE( + ); + + + BOOLEAN + Read( + IN BIG_INT StartingSector, + IN SECTORCOUNT NumberOfSectors, + OUT PVOID Buffer + ); + + + BOOLEAN + Write( + IN BIG_INT StartingSector, + IN SECTORCOUNT NumberOfSectors, + IN PVOID Buffer + ); + + + BOOLEAN + Verify( + IN BIG_INT StartingSector, + IN BIG_INT NumberOfSectors + ); + + + BOOLEAN + Verify( + IN BIG_INT StartingSector, + IN BIG_INT NumberOfSectors, + IN OUT PNUMBER_SET BadSectors + ); + + + PMESSAGE + GetMessage( + ); + + + BOOLEAN + Lock( + ); + + + + BOOLEAN + Unlock( + ); + + +protected: + + DECLARE_CONSTRUCTOR(IO_DP_DRIVE); + + + BOOLEAN + Initialize( + IN PCWSTRING NtDriveName, + IN OUT PMESSAGE Message DEFAULT NULL + ); + + +private: + + BOOLEAN _is_locked; + BOOLEAN _is_exclusive_write; + PDRIVE_CACHE _cache; + ULONG _ValidBlockLengthForVerify; + PMESSAGE _message; + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + + BOOLEAN + VerifyWithRead( + IN BIG_INT StartingSector, + IN BIG_INT NumberOfSectors + ); + + + BOOLEAN + HardRead( + IN BIG_INT StartingSector, + IN SECTORCOUNT NumberOfSectors, + OUT PVOID Buffer + ); + + + BOOLEAN + HardWrite( + IN BIG_INT StartingSector, + IN SECTORCOUNT NumberOfSectors, + IN PVOID Buffer + ); + + + BOOLEAN + Dismount( + ); + +}; + + +INLINE +PMESSAGE +IO_DP_DRIVE::GetMessage( + ) +/*++ + +Routine Description: + + Retrieve the message object. + +Arguments: + + N/A + +Return Value: + + The message object. + +--*/ +{ + return _message; +} + +class LOG_IO_DP_DRIVE : public IO_DP_DRIVE { + +public: + + + DECLARE_CONSTRUCTOR(LOG_IO_DP_DRIVE); + + VIRTUAL + + ~LOG_IO_DP_DRIVE( + ); + + + BOOLEAN + Initialize( + IN PCWSTRING NtDriveName, + IN OUT PMESSAGE Message DEFAULT NULL + ); + +private: + + VOID + Construct( + ); + + +}; + + +INLINE +VOID +LOG_IO_DP_DRIVE::Construct( + ) +/*++ + +Routine Description: + + Constructor for LOG_IO_DP_DRIVE. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ +} + + diff --git a/ifsutil/inc/ifssys.hxx b/ifsutil/inc/ifssys.hxx new file mode 100644 index 0000000..54e805d --- /dev/null +++ b/ifsutil/inc/ifssys.hxx @@ -0,0 +1,58 @@ +/*++ + +Module Name: + + ifssys.hxx + +Abstract: + + This module contains the definition for the IFS_SYSTEM class. + The IFS_SYSTEM class is an abstract class which offers an + interface for communicating with the underlying operating system + on specific IFS issues. + +--*/ + +#pragma once + +#include "drive.hxx" + + + +DECLARE_CLASS( WSTRING ); +DECLARE_CLASS( BIG_INT ); +DECLARE_CLASS( IFS_SYSTEM ); + +class IFS_SYSTEM { + + public: + + STATIC + BOOLEAN + QueryFileSystemNameIsNtfs( + IN PCWSTRING NtDriveName, + OUT PBOOL FileSystemNameIsNtfs, + OUT PNTSTATUS ErrorCode DEFAULT NULL + ); + + + STATIC + BOOLEAN + IsThisNtfs( + IN BIG_INT Sectors, + IN ULONG SectorSize, + IN PVOID BootSectorData + ); + +}; + + +extern +BOOLEAN +IfsutilDefineClassDescriptors( +); + +extern +BOOLEAN +IfsutilUndefineClassDescriptors( +); diff --git a/ifsutil/inc/intstack.hxx b/ifsutil/inc/intstack.hxx new file mode 100644 index 0000000..d2730f3 --- /dev/null +++ b/ifsutil/inc/intstack.hxx @@ -0,0 +1,111 @@ +/*++ + +Module Name: + + intstack.hxx + +Abstract: + + This class implements a linked list integer stack. + +--*/ + +#pragma once + + +#include "bigint.hxx" + + + + +DECLARE_CLASS( INTSTACK ); + +DEFINE_TYPE( struct _INTNODE, INTNODE ); + +struct _INTNODE { + PINTNODE Next; + BIG_INT Data; +}; + +class INTSTACK : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( INTSTACK ); + + VIRTUAL + ~INTSTACK( + ); + + + BOOLEAN + Initialize( + ); + + + BOOLEAN + Push( + IN BIG_INT Data + ); + + + VOID + Pop( + IN ULONG HowMany DEFAULT 1 + ); + + + BIG_INT + Look( + IN ULONG Index DEFAULT 0 + ) CONST; + + + ULONG + QuerySize( + ) CONST; + + + private: + + + VOID + Construct ( + ); + + + VOID + Destroy( + ); + + PINTNODE _stack; + ULONG _size; + +}; + + +INLINE +ULONG +INTSTACK::QuerySize( + ) CONST +/*++ + +Routine Description: + + This routine computes the number of elements in the stack. + +Arguments: + + None. + +Return Value: + + The number of elements in the stack. + +--*/ +{ + return _size; +} + + diff --git a/ifsutil/inc/numset.hxx b/ifsutil/inc/numset.hxx new file mode 100644 index 0000000..5fbe5ee --- /dev/null +++ b/ifsutil/inc/numset.hxx @@ -0,0 +1,186 @@ +/*++ + +Module Name: + + numset.hxx + +Abstract: + + This class implements a sparse number set. The number are + stored in ascending order. + +--*/ + +#pragma once + +#include "bigint.hxx" +#include "list.hxx" + + +DECLARE_CLASS( NUMBER_SET ); + +class NUMBER_EXTENT : public OBJECT { + + public: + + DECLARE_CONSTRUCTOR( NUMBER_EXTENT ); + + BIG_INT Start; + BIG_INT Length; + +}; + + +DEFINE_POINTER_TYPES(NUMBER_EXTENT); + +class NUMBER_SET : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( NUMBER_SET ); + + VIRTUAL + + ~NUMBER_SET( + ); + + + BOOLEAN + Initialize( + ); + + + BOOLEAN + Add( + IN BIG_INT Number + ); + + BOOLEAN + Add( + IN BIG_INT Start, + IN BIG_INT Length + ); + + BOOLEAN + Remove( + IN BIG_INT Number + ); + + + BOOLEAN + RemoveAll( + ); + + + BOOLEAN + CheckAndRemove( + IN BIG_INT Number, + OUT PBOOLEAN DoesExists + ); + + BIG_INT + QueryCardinality( + ) CONST; + + + + BIG_INT + QueryNumber( + IN BIG_INT Index + ) CONST; + + + BOOLEAN + DoesIntersectSet( + IN BIG_INT Start, + IN BIG_INT Length + ) CONST; + + + ULONG + QueryNumDisjointRanges( + ) CONST; + + + VOID + QueryDisjointRange( + IN ULONG Index, + OUT PBIG_INT Start, + OUT PBIG_INT Length + ) CONST; + + + BOOLEAN + QueryContainingRange( + IN BIG_INT Number, + OUT PBIG_INT Start, + OUT PBIG_INT Length + ) CONST; + + private: + + VOID + Construct ( + ); + + + VOID + Destroy( + ); + + LIST _list; + BIG_INT _card; + PITERATOR _iterator; + +}; + + +INLINE +BIG_INT +NUMBER_SET::QueryCardinality( + ) CONST +/*++ + +Routine Description: + + This routine computes the number of elements in the set. + +Arguments: + + None. + +Return Value: + + The number of elements in the set. + +--*/ +{ + return _card; +} + + +INLINE +ULONG +NUMBER_SET::QueryNumDisjointRanges( + ) CONST +/*++ + +Routine Description: + + This routine computes the number of disjoint ranges contained + in this number set. + +Arguments: + + None. + +Return Value: + + The number of disjoint ranges contained in this number set. + +--*/ +{ + return _list.QueryMemberCount(); +} + diff --git a/ifsutil/inc/secrun.hxx b/ifsutil/inc/secrun.hxx new file mode 100644 index 0000000..c0e4a74 --- /dev/null +++ b/ifsutil/inc/secrun.hxx @@ -0,0 +1,255 @@ +/*++ + +Module Name: + + secrun.hxx + +Abstract: + + This class models a run of sectors. It is able to read in a run of + sectors into memory and write a run of sectors in memory onto disk. + +--*/ + +#pragma once + + +#include "drive.hxx" +#include "mem.hxx" + + + + +DECLARE_CLASS( SECRUN ); + +class SECRUN : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( SECRUN ); + + VIRTUAL + + ~SECRUN( + ); + + + + BOOLEAN + Initialize( + IN OUT PMEM Mem, + IN OUT PIO_DP_DRIVE Drive, + IN BIG_INT StartSector, + IN SECTORCOUNT NumSectors + ); + + + VOID + Relocate( + IN BIG_INT NewStartSector + ); + + VIRTUAL + + BOOLEAN + Read( + ); + + VIRTUAL + + BOOLEAN + Write( + ); + + + PVOID + GetBuf( + ); + + + BIG_INT + QueryStartSector( + ) CONST; + + + LBN + QueryStartLbn( + ) CONST; + + + SECTORCOUNT + QueryLength( + ) CONST; + + + + PIO_DP_DRIVE + GetDrive( + ); + + private: + + + VOID + Construct ( + ); + + + VOID + Destroy( + ); + + PVOID _buf; + PIO_DP_DRIVE _drive; + BIG_INT _start_sector; + SECTORCOUNT _num_sectors; + +}; + +INLINE +VOID +SECRUN::Relocate( + IN BIG_INT NewStartSector + ) +/*++ + +Routine Description: + + This routine relocates the secrun. + +Arguments: + + NewStartSector - Supplies the new starting sector. + +Return Value: + + None. + +--*/ +{ + _start_sector = NewStartSector; +} + + +INLINE +PVOID +SECRUN::GetBuf( + ) +/*++ + +Routine Description: + + This routine returns a pointer to the beginning of the read/write + buffer. + +Arguments: + + None. + +Return Value: + + A pointer to a read/write buffer. + +--*/ +{ + return _buf; +} + + +INLINE +BIG_INT +SECRUN::QueryStartSector( + ) CONST +/*++ + +Routine Description: + + This routine returns the starting sector number for the run of sectors. + +Arguments: + + None. + +Return Value: + + The starting sector number for the run of sectors. + +--*/ +{ + return _start_sector; +} + + +INLINE +LBN +SECRUN::QueryStartLbn( + ) CONST +/*++ + +Routine Description: + + This routine returns the starting LBN for the run of sectors. + +Arguments: + + None. + +Return Value: + + The starting LBN for the run of sectors. + +--*/ +{ + DebugAssert(_start_sector.GetHighPart() == 0); + return _start_sector.GetLowPart(); +} + + +INLINE +SECTORCOUNT +SECRUN::QueryLength( + ) CONST +/*++ + +Routine Description: + + This routine computes the number of sectors in this sector run. + +Arguments: + + None. + +Return Value: + + The number of sectors in this sector run. + +--*/ +{ + return _num_sectors; +} + + +INLINE +PIO_DP_DRIVE +SECRUN::GetDrive( + ) +/*++ + +Routine Description: + + This routine returns a pointer to the drive object. + +Arguments: + + None. + +Return Value: + + A pointer to the drive object. + +--*/ +{ + return _drive; +} + diff --git a/ifsutil/inc/supera.hxx b/ifsutil/inc/supera.hxx new file mode 100644 index 0000000..b89a3ed --- /dev/null +++ b/ifsutil/inc/supera.hxx @@ -0,0 +1,134 @@ +/*++ + +Module Name: + + supera.hxx + +Abstract: + + This class models the root of a file system. This abstract class is + currently the base class of an HPFS and a FAT super area. + +--*/ + +#pragma once + +#include "secrun.hxx" +#include "volume.hxx" +#include "ifsentry.hxx" + +// + +DECLARE_CLASS( SUPERAREA ); +DECLARE_CLASS( NUMBER_SET ); +DECLARE_CLASS( MESSAGE ); +DECLARE_CLASS( WSTRING ); + + +class SUPERAREA : public SECRUN { + +public: + + VIRTUAL + BOOLEAN + MarkBad( + IN __int64 firstPhysicalDriveSector, + IN __int64 lastPhysicalDriveSector, + IN OUT PMESSAGE Message + ) PURE; + + + VIRTUAL + ~SUPERAREA( + ); + + VIRTUAL + PVOID + GetBuf( + ); + + + PIO_DP_DRIVE + GetDrive( + ); + + +protected: + + DECLARE_CONSTRUCTOR(SUPERAREA); + + + BOOLEAN + Initialize( + IN OUT PMEM Mem, + IN OUT PLOG_IO_DP_DRIVE Drive, + IN SECTORCOUNT NumberOfSectors, + IN OUT PMESSAGE Message + ); + + + PLOG_IO_DP_DRIVE _drive; + + +private: + + VOID + Construct( + ); + + + VOID + Destroy( + ); + +}; + + +INLINE +PVOID +SUPERAREA::GetBuf( + ) +/*++ + +Routine Description: + + This routine returns a pointer to the beginning of the read/write + buffer. + +Arguments: + + None. + +Return Value: + + A pointer to a read/write buffer. + +--*/ +{ + return SECRUN::GetBuf(); +} + + +INLINE +PIO_DP_DRIVE +SUPERAREA::GetDrive( + ) +/*++ + +Routine Description: + + Retrieve the drive object. + +Arguments: + + N/A + +Return Value: + + The drive object. + +--*/ +{ + return _drive; +} + diff --git a/ifsutil/inc/untfs2.hxx b/ifsutil/inc/untfs2.hxx new file mode 100644 index 0000000..8e1064b --- /dev/null +++ b/ifsutil/inc/untfs2.hxx @@ -0,0 +1,424 @@ +/*++ + +Module Name: + + untfs2.h + +Abstract: + + This module contains partial basic declarations and definitions for + the NTFS utilities. Note that more extensive description of the + file system structures may be found in ntos\inc\ntfs.h. + + +IMPORTANT NOTE: + + The NTFS on-disk structure must guarantee natural alignment of all + arithmetic quantities on disk up to and including quad-word (64-bit) + numbers. Therefore, all attribute records are quad-word aligned, etc. + +--*/ + +#pragma once + +#include "bigint.hxx" +#include "bpb.hxx" +#include "wstring.hxx" + +#include + +DEFINE_TYPE( BIG_INT, LCN ); +DEFINE_TYPE( BIG_INT, VCN ); + +DEFINE_TYPE( LARGE_INTEGER, LSN ); + +// This definition is for VCNs that appear in Unions, since an object +// with a constructor (like a BIG_INT) can't appear in a union. + +DEFINE_TYPE( LARGE_INTEGER, VCN2 ); + +// An MFT_SEGMENT_REFERENCE identifies a cluster in the Master +// File Table by its file number (VCN in Master File Table) and +// sequence number. If the sequence number is zero, sequence +// number checking is not performed. + +typedef struct _MFT_SEGMENT_REFERENCE { + + ULONG LowPart; + USHORT HighPart; + USHORT SequenceNumber; + +} MFT_SEGMENT_REFERENCE, *PMFT_SEGMENT_REFERENCE; + +DEFINE_TYPE( struct _MFT_SEGMENT_REFERENCE, MFT_SEGMENT_REFERENCE ); + +DEFINE_TYPE( MFT_SEGMENT_REFERENCE, FILE_REFERENCE ); + +// System file numbers: +// +// The first sixteen entries in the Master File Table are reserved for +// system use. The following reserved slots have been defined: + +#define MASTER_FILE_TABLE_NUMBER (0) +#define MASTER_FILE_TABLE2_NUMBER (1) +#define LOG_FILE_NUMBER (2) +#define VOLUME_DASD_NUMBER (3) +#define ATTRIBUTE_DEF_TABLE_NUMBER (4) +#define ROOT_FILE_NAME_INDEX_NUMBER (5) +#define BIT_MAP_FILE_NUMBER (6) +#define BOOT_FILE_NUMBER (7) +#define BAD_CLUSTER_FILE_NUMBER (8) +#define QUOTA_TABLE_NUMBER (9) // for version < 2.0 +#define SECURITY_TABLE_NUMBER (9) // for version >= 2.0 +#define UPCASE_TABLE_NUMBER (10) +#define EXTEND_TABLE_NUMBER (11) // for version >= 2.0 + +#define MFT_OVERFLOW_FRS_NUMBER (15) + +#define FIRST_USER_FILE_NUMBER (16) + +DEFINE_TYPE( ULONG, ATTRIBUTE_TYPE_CODE ); + +// +// System-defined Attribute Type Codes. For the System-defined attributes, +// the Unicode Name is exactly equal to the name of the following symbols. +// For this reason, all of the system-defined attribute names start with "$", +// to always distinguish them when attribute names are listed, and to reserve +// a namespace for attributes defined in the future. I.e., a User-Defined +// attribute name will never collide with a current or future system-defined +// attribute name if it does not start with "$". User attribute numbers +// should not start until $FIRST_USER_DEFINED_ATTRIBUTE, too allow the +// potential for upgrading existing volumes with new user-defined attributes +// in future versions of NTFS. The tagged attribute list is terminated with +// a lone-standing $END - the rest of the attribute record does not exist. +// + +#define $UNUSED (0x0) + +#define $STANDARD_INFORMATION (0x10) +#define $ATTRIBUTE_LIST (0x20) +#define $FILE_NAME (0x30) +#define $VOLUME_VERSION (0x40) +#define $OBJECT_ID (0x40) // starting in NT 5.0 +#define $SECURITY_DESCRIPTOR (0x50) +#define $VOLUME_NAME (0x60) +#define $VOLUME_INFORMATION (0x70) +#define $DATA (0x80) +#define $INDEX_ROOT (0x90) +#define $INDEX_ALLOCATION (0xA0) +#define $BITMAP (0xB0) +#define $SYMBOLIC_LINK (0xC0) +#define $REPARSE_POINT (0xC0) // starting in NT 5.0 +#define $EA_INFORMATION (0xD0) +#define $EA_DATA (0xE0) +#define $FIRST_USER_DEFINED_ATTRIBUTE_1 (0x100) // true up to NT 4.0 +#define $PROPERTY_SET (0xF0) // starting in NT 5.0 +#define $LOGGED_UTILITY_STREAM (0x100) // starting in NT 5.0 +#define $FIRST_USER_DEFINED_ATTRIBUTE_2 (0x1000) // starting in NT 5.0 +#define $END (0xFFFFFFFF) + + +// +// The boot sector is duplicated on the partition. The first copy is on +// the first physical sector (LBN == 0) of the partition, and the second +// copy is at / 2. If the first copy can +// not be read when trying to mount the disk, the second copy may be read +// and has the identical contents. Format must figure out which cluster +// the second boot record belongs in, and it must zero all of the other +// sectors that happen to be in the same cluster. The boot file minimally +// contains with two clusters, which are the two clusters which contain the +// copies of the boot record. If format knows that some system likes to +// put code somewhere, then it should also align this requirement to +// even clusters, and add that to the boot file as well. +// + + +// +// Define the boot sector. Note that MFT2 is exactly three file record +// segments long, and it mirrors the first three file record segments from +// the MFT, which are MFT, MFT2 and the Log File. +// +// The Oem field contains the ASCII characters "NTFS ". +// +// The Checksum field is a simple additive checksum of all of the ULONGs +// which precede the Checksum ULONG. The rest of the sector is not included +// in this Checksum. +// + +typedef struct _PACKED_BOOT_SECTOR { + UCHAR Jump[3]; // offset = 0x000 + UCHAR Oem[8]; // offset = 0x003 + OLD_PACKED_BIOS_PARAMETER_BLOCK PackedBpb; // offset = 0x00B + UCHAR PhysicalDrive; // offset = 0x024 + UCHAR ReservedForBootCode; // offset = 0x025 + UCHAR Unused[2]; // offset = 0x026 + LARGE_INTEGER NumberSectors; // offset = 0x028 + LCN MftStartLcn; // offset = 0x030 + LCN Mft2StartLcn; // offset = 0x038 + CHAR ClustersPerFileRecordSegment; // offset = 0x040 + UCHAR Unused1[3]; // offset = 0x041 + CHAR DefaultClustersPerIndexAllocationBuffer; // offset = 0x044 + UCHAR Unused2[3]; // offset = 0x047 + LARGE_INTEGER SerialNumber; // offset = 0x048 + ULONG Checksum; // offset = 0x050 + UCHAR BootStrap[0x200-0x054]; // offset = 0x054 +} PACKED_BOOT_SECTOR; // sizeof = 0x200 +typedef PACKED_BOOT_SECTOR *PPACKED_BOOT_SECTOR; + +// Update sequence array structures--see ntos\inc\cache.h for +// description. + +#define SEQUENCE_NUMBER_STRIDE (512) + +DEFINE_TYPE( USHORT, UPDATE_SEQUENCE_NUMBER ); + +typedef struct _UNTFS_MULTI_SECTOR_HEADER { + + UCHAR Signature[4]; + USHORT UpdateSequenceArrayOffset; // byte offset + USHORT UpdateSequenceArraySize; // number of Update Sequence Numbers + +}; + +DEFINE_TYPE( _UNTFS_MULTI_SECTOR_HEADER, UNTFS_MULTI_SECTOR_HEADER ); + +typedef UPDATE_SEQUENCE_NUMBER UPDATE_SEQUENCE_ARRAY[1]; +typedef UPDATE_SEQUENCE_ARRAY *PUPDATE_SEQUENCE_ARRAY; + + +// +// File Record Segment. This is the header that begins every File Record +// Segment in the Master File Table. +// + +typedef struct _FILE_RECORD_SEGMENT_HEADER { + + UNTFS_MULTI_SECTOR_HEADER MultiSectorHeader; + LSN Lsn; + USHORT SequenceNumber; + USHORT ReferenceCount; + USHORT FirstAttributeOffset; + USHORT Flags; // FILE_xxx flags + ULONG FirstFreeByte; // byte-offset + ULONG BytesAvailable; // Size of FRS + FILE_REFERENCE BaseFileRecordSegment; + USHORT NextAttributeInstance; // Attribute instance tag for next insert. + USHORT SegmentNumberHighPart; // used in mft recovery + ULONG SegmentNumberLowPart; // used in mft recovery + UPDATE_SEQUENCE_ARRAY UpdateArrayForCreateOnly; + +}; + +typedef struct _FILE_RECORD_SEGMENT_HEADER_V0 { + + UNTFS_MULTI_SECTOR_HEADER MultiSectorHeader; + LSN Lsn; + USHORT SequenceNumber; + USHORT ReferenceCount; + USHORT FirstAttributeOffset; + USHORT Flags; // FILE_xxx flags + ULONG FirstFreeByte; // byte-offset + ULONG BytesAvailable; // Size of FRS + FILE_REFERENCE BaseFileRecordSegment; + USHORT NextAttributeInstance; // Attribute instance tag for next insert. + UPDATE_SEQUENCE_ARRAY UpdateArrayForCreateOnly; + +}; + +DEFINE_TYPE( _FILE_RECORD_SEGMENT_HEADER, FILE_RECORD_SEGMENT_HEADER ); +DEFINE_TYPE( _FILE_RECORD_SEGMENT_HEADER_V0, FILE_RECORD_SEGMENT_HEADER_V0 ); + +// +// FILE_xxx flags. +// + +#define FILE_RECORD_SEGMENT_IN_USE (0x0001) +#define FILE_FILE_NAME_INDEX_PRESENT (0x0002) +#define FILE_SYSTEM_FILE (0x0004) +#define FILE_VIEW_INDEX_PRESENT (0x0008) + + +// +// Attribute Record. Logically an attribute has a type, an optional name, +// and a value, however the storage details make it a little more complicated. +// For starters, an attribute's value may either be resident in the file +// record segment itself, on nonresident in a separate data stream. If it +// is nonresident, it may actually exist multiple times in multiple file +// record segments to describe different ranges of VCNs. +// +// Attribute Records are always aligned on a quad word (64-bit) boundary. +// +// Note that SIZE_OF_RESIDENT_HEADER and SIZE_OF_NONRESIDENT_HEADER +// must correspond to the ATTRIBUTE_RECORD_HEADER structure. + +#define SIZE_OF_RESIDENT_HEADER 24 +#define SIZE_OF_NONRESIDENT_HEADER 64 + +typedef struct _ATTRIBUTE_RECORD_HEADER { + + ATTRIBUTE_TYPE_CODE TypeCode; + ULONG RecordLength; + UCHAR FormCode; + UCHAR NameLength; // length in characters + USHORT NameOffset; // byte offset from start of record + USHORT Flags; // ATTRIBUTE_xxx flags. + USHORT Instance; // FRS-unique attribute instance tag + + union { + + // + // Resident Form. Attribute resides in file record segment. + // + + struct { + + ULONG ValueLength; // in bytes + USHORT ValueOffset; // byte offset from start of record + UCHAR ResidentFlags; // RESIDENT_FORM_xxx Flags. + UCHAR Reserved; + + } Resident; + + // + // Nonresident Form. Attribute resides in separate stream. + // + + struct { + + VCN2 LowestVcn; + VCN2 HighestVcn; + USHORT MappingPairsOffset; // byte offset from start of record + UCHAR CompressionUnit; + UCHAR Reserved[5]; + LARGE_INTEGER AllocatedLength; + LARGE_INTEGER FileSize; + LARGE_INTEGER ValidDataLength; + LARGE_INTEGER TotalAllocated; + + // + // Mapping Pairs Array follows, starting at the offset given + // above. See the extended comment in ntfs.h. + // + + } Nonresident; + + } Form; + +}; + +DEFINE_TYPE( _ATTRIBUTE_RECORD_HEADER, ATTRIBUTE_RECORD_HEADER ); + + +// +// Attribute Form Codes +// + +#define RESIDENT_FORM (0x00) +#define NONRESIDENT_FORM (0x01) + +// +// Define Attribute Flags +// + +#define ATTRIBUTE_FLAG_COMPRESSION_MASK (0x00FF) +#define ATTRIBUTE_FLAG_SPARSE (0x8000) +#define ATTRIBUTE_FLAG_ENCRYPTED (0x4000) + +// +// RESIDENT_FORM_xxx flags +// + +// +// This attribute is indexed. +// + +#define RESIDENT_FORM_INDEXED (0x01) + +// +// The maximum attribute name length is 255 (in chars) +// + +#define NTFS_MAX_ATTR_NAME_LEN (255) + + +// +// Volume Information attribute. This attribute is only intended to be +// used on the Volume DASD file. +// + +typedef struct _VOLUME_INFORMATION { + + LARGE_INTEGER Reserved; + + // + // Major and minor version number of NTFS on this volume, starting + // with 1.0. The major and minor version numbers are set from the + // major and minor version of the Format and NTFS implementation for + // which they are initialized. The policy for incementing major and + // minor versions will always be decided on a case by case basis, however, + // the following two paragraphs attempt to suggest an approximate strategy. + // + // The major version number is incremented if/when a volume format + // change is made which requires major structure changes (hopefully + // never?). If an implementation of NTFS sees a volume with a higher + // major version number, it should refuse to mount the volume. If a + // newer implementation of NTFS sees an older major version number, + // it knows the volume cannot be accessed without performing a one-time + // conversion. + // + // The minor version number is incremented if/when minor enhancements + // are made to a major version, which potentially support enhanced + // functionality through additional file or attribute record fields, + // or new system-defined files or attributes. If an older implementation + // of NTFS sees a newer minor version number on a volume, it may issue + // some kind of warning, but it will proceed to access the volume - with + // presumably some degradation in functionality compared to the version + // of NTFS which initialized the volume. If a newer implementation of + // NTFS sees a volume with an older minor version number, it may issue + // a warning and proceed. In this case, it may choose to increment the + // minor version number on the volume and begin full or incremental + // upgrade of the volume on an as-needed basis. It may also leave the + // minor version number unchanged, until some sort of explicit directive + // from the user specifies that the minor version should be updated. + // + + UCHAR MajorVersion; + UCHAR MinorVersion; + + // + // VOLUME_xxx flags. + // + + USHORT VolumeFlags; + +} VOLUME_INFORMATION; +typedef VOLUME_INFORMATION *PVOLUME_INFORMATION; + +// Current version number: +// +#define NTFS_CURRENT_MAJOR_VERSION 1 +#define NTFS_CURRENT_MINOR_VERSION 2 + +// +// Volume is dirty +// + +#define VOLUME_DIRTY (0x0001) + + +#include + +// Macros: + +#define QuadAlign( n ) \ + (((n) + 7) & ~7 ) + +#define DwordAlign( n ) \ + (((n) + 3) & ~3 ) + +#define IsQuadAligned( n ) \ + (((n) & 7) == 0) + +#define IsDwordAligned( n ) \ + (((n) & 3) == 0) + diff --git a/ifsutil/inc/volume.hxx b/ifsutil/inc/volume.hxx new file mode 100644 index 0000000..8dd86ce --- /dev/null +++ b/ifsutil/inc/volume.hxx @@ -0,0 +1,132 @@ +/*++ + +Module Name: + + volume.hxx + +Abstract: + + Provides volume methods. + +--*/ + +#pragma once + + +#include "drive.hxx" +#include "numset.hxx" + +// +// Forward references +// + +DECLARE_CLASS( HMEM ); +DECLARE_CLASS( MESSAGE ); +DECLARE_CLASS( SUPERAREA ); +DECLARE_CLASS( VOL_LIODPDRV ); +DECLARE_CLASS( WSTRING ); +DECLARE_CLASS( WSTRING ); + +// This number describes the minimum number of bytes in a boot sector. +#define BYTES_PER_BOOT_SECTOR 512 + + +typedef ULONG VOLID; + +#define MAXVOLNAME 11 + +#define AUTOCHK_TIMEOUT 10 // 10 seconds before initiating autochk +#define MAX_AUTOCHK_TIMEOUT_VALUE (3*24*3600) // 3 days maximum + +enum FIX_LEVEL { + CheckOnly, + TotalFix, + SetupSpecial +}; + +enum FORMAT_ERROR_CODE { + GeneralError, + NoError, + LockError +}; + + +class VOL_LIODPDRV : public LOG_IO_DP_DRIVE { + + public: + + BOOLEAN + MarkBad( + IN __int64 firstPhysicalDriveSector, + IN __int64 lastPhysicalDriveSector, + IN OUT PMESSAGE Message + ); + + VIRTUAL + + ~VOL_LIODPDRV( + ); + + + + PSUPERAREA + GetSa( + ); + + + protected: + + + DECLARE_CONSTRUCTOR( VOL_LIODPDRV ); + + + FORMAT_ERROR_CODE + Initialize( + IN PCWSTRING NtDriveName, + IN PSUPERAREA SuperArea, + IN OUT PMESSAGE Message DEFAULT NULL + ); + + + private: + + + VOID + Construct ( + ); + + + VOID + Destroy( + ); + + + PSUPERAREA _sa; + NUMBER_SET _bad_sectors; + +}; + + +INLINE +PSUPERAREA +VOL_LIODPDRV::GetSa( + ) +/*++ + +Routine Description: + + This routine returns a pointer to the current super area. + +Arguments: + + None. + +Return Value: + + A pointer to the current super area. + +--*/ +{ + return _sa; +} + diff --git a/ifsutil/src/bigint.cxx b/ifsutil/src/bigint.cxx new file mode 100644 index 0000000..8f67d0c --- /dev/null +++ b/ifsutil/src/bigint.cxx @@ -0,0 +1,142 @@ +#include "stdafx.h" + + +#include "ulib.hxx" + + +#include "bigint.hxx" + + +VOID +BIG_INT::Set( + IN UCHAR ByteCount, + IN PCUCHAR CompressedInteger + ) +/*++ + +Routine Description: + + This routine sets the big_int with the given compressed integer. + +Arguments: + + ByteCount - Supplies the number of bytes in the compressed + integer. + CompressedInteger - Supplies the compressed integer. + +Return Value: + + None. + +--*/ +{ + // If the number is completely compressed then we'll say that the + // number is zero. QueryCompressed should always return at least + // one byte though. + + if (ByteCount == 0) { + x = 0; + return; + } + + + // First fill the integer with -1 if it's negative or 0 if it's + // positive. + + if (CompressedInteger[ByteCount - 1] >= 0x80) { + + x = -1; + + } else { + + x = 0; + } + + + // Now copy over the integer. + + DebugAssert( ByteCount <= 8 ); + + memcpy( &x, CompressedInteger, ByteCount ); +} + + +VOID +BIG_INT::QueryCompressedInteger( + OUT PUCHAR ByteCount, + OUT PUCHAR CompressedInteger +) CONST +/*++ + +Routine Descrtiption: + + This routine returns a compressed form of the integer. + +Arguments: + + ByteCount - Returns the number of bytes in the compressed + integer. + CompressedInteger - Returns a 'little endian' string of bytes + representing a signed 'ByteCount' byte integer + into this supplied buffer. + +Return Value: + + None. + +--*/ +{ + INT i; + PUCHAR p; + + DebugAssert(ByteCount); + DebugAssert(CompressedInteger); + + // First copy over the whole thing then determine the number + // of bytes that you have to keep. + + memcpy(CompressedInteger, &x, sizeof(LARGE_INTEGER)); + + + p = CompressedInteger; + + + // First check to see whether the number is positive or negative. + + if (p[7] >= 0x80) { // high byte is negative. + + for (i = 7; i >= 0 && p[i] == 0xFF; i--) { + } + + if (i < 0) { + *ByteCount = 1; + return; + } + + if (p[i] < 0x80) { // high byte is non-negative. + i++; + } + + } + else { // high byte is non-negative. + + for (i = 7; i >= 0 && p[i] == 0; i--) { + } + + if (i < 0) { + *ByteCount = 1; + return; + } + + if (p[i] >= 0x80) { // high byte is negative. + i++; + } + + } + + + // Now 'i' marks the position of the last character that you + // have to keep. + + *ByteCount = (UCHAR)(i + 1); +} diff --git a/ifsutil/src/dcache.cxx b/ifsutil/src/dcache.cxx new file mode 100644 index 0000000..9da4416 --- /dev/null +++ b/ifsutil/src/dcache.cxx @@ -0,0 +1,169 @@ +#include "stdafx.h" + + + + + +#include "ulib.hxx" +#include "dcache.hxx" + + +DEFINE_CONSTRUCTOR( DRIVE_CACHE, OBJECT ); + + +DRIVE_CACHE::~DRIVE_CACHE( + ) +/*++ + +Routine Description: + + Destructor for DRIVE_CACHE. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + +VOID +DRIVE_CACHE::Construct ( + ) +/*++ + +Routine Description: + + Contructor for DRIVE_CACHE. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _drive = NULL; +} + + +VOID +DRIVE_CACHE::Destroy( + ) +/*++ + +Routine Description: + + Destructor for DRIVE_CACHE. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _drive = NULL; +} + + +BOOLEAN +DRIVE_CACHE::Initialize( + IN OUT PIO_DP_DRIVE Drive + ) +/*++ + +Routine Description: + + This routine initializes a DRIVE_CACHE object. + +Arguments: + + Drive - Supplies the drive to cache for. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + DebugAssert(Drive); + + Destroy(); + _drive = Drive; + return TRUE; +} + + +BOOLEAN +DRIVE_CACHE::Read( + IN BIG_INT StartingSector, + IN SECTORCOUNT NumberOfSectors, + OUT PVOID Buffer + ) +/*++ + +Routine Description: + + This routine reads the requested sectors directly from the disk. + +Arguments: + + StartingSector - Supplies the first sector to be read. + NumberOfSectors - Supplies the number of sectors to be read. + Buffer - Supplies the buffer to read the run of sectors to. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + DebugAssert(_drive); + return _drive->HardRead(StartingSector, NumberOfSectors, Buffer); +} + + +BOOLEAN +DRIVE_CACHE::Write( + IN BIG_INT StartingSector, + IN SECTORCOUNT NumberOfSectors, + IN PVOID Buffer + ) +/*++ + +Routine Description: + + This routine writes the requested sectors directly to the disk. + +Arguments: + + StartingSector - Supplies the first sector to be written. + NumberOfSectors - Supplies the number of sectors to be written. + Buffer - Supplies the buffer to write the run of sectors from. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + DebugAssert(_drive); + return _drive->HardWrite(StartingSector, NumberOfSectors, Buffer); +} + diff --git a/ifsutil/src/drive.cxx b/ifsutil/src/drive.cxx new file mode 100644 index 0000000..c37b3f0 --- /dev/null +++ b/ifsutil/src/drive.cxx @@ -0,0 +1,1847 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + drive.cxx + +Abstract: + + Provide drive methods. + See drive.hxx for details. + +--*/ + + +#include "ulib.hxx" + + +#include "drive.hxx" + +#include "message.hxx" +#include "numset.hxx" +#include "dcache.hxx" +#include "hmem.hxx" +#include "ifssys.hxx" + + + +// Don't lock down more that 64K for IO. +CONST int MaxIoSize = 65536; + +DEFINE_CONSTRUCTOR( DRIVE, OBJECT ); + +VOID +DRIVE::Construct ( + ) +/*++ + +Routine Description: + + Contructor for DRIVE. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + // unreferenced parameters + (void)(this); +} + + +DRIVE::~DRIVE( + ) +/*++ + +Routine Description: + + Destructor for DRIVE. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + +BOOLEAN +DRIVE::Initialize( + IN PCWSTRING NtDriveName, + IN OUT PMESSAGE Message + ) +/*++ + +Routine Description: + + This routine initializes a drive object. + +Arguments: + + NtDriveName - Supplies an NT style drive name. + Message - Supplies an outlet for messages. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + Destroy(); + + if (!NtDriveName) { + Destroy(); + return FALSE; + } + + if (!_name.Initialize(NtDriveName)) + { + Destroy(); + Message ? Message->Out("Insufficient memory.") : 1; + return FALSE; + } + + return TRUE; +} + + +VOID +DRIVE::Destroy( + ) +/*++ + +Routine Description: + + This routine returns a DRIVE object to its initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + // unreferenced parameters + (void)(this); +} + + +DEFINE_CONSTRUCTOR( DP_DRIVE, DRIVE ); + +VOID +DP_DRIVE::Construct ( + ) +/*++ + +Routine Description: + + Constructor for DP_DRIVE. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + memset(&_actual, 0, sizeof(DRTYPE)); + _supported_list = NULL; + _num_supported = 0; + _alignment_mask = 0; + _last_status = 0; + _handle = 0; + _is_writeable = FALSE; + _is_primary_partition = FALSE; + + memset(&_partition_info, 0, sizeof(_partition_info)); +} + + + +DP_DRIVE::~DP_DRIVE( + ) +/*++ + +Routine Description: + + Destructor for DP_DRIVE. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + +NTSTATUS +DP_DRIVE::OpenDrive( + IN PCWSTRING NtDriveName, + IN ACCESS_MASK DesiredAccess, + IN BOOLEAN ExclusiveWrite, + OUT PHANDLE Handle, + OUT PULONG Alignment, + IN OUT PMESSAGE Message + ) +/*++ + +Routine Description: + + This method is a worker function for the Initialize methods, + to open a volume and determine its alignment requirement. + +Arguments: + + NtDriveName - Supplies the name of the drive. + DesiredAccess - Supplies the access the client desires to the volume. + ExclusiveWrite - Supplies a flag indicating whether the client + wishes to exclude other write handles. + Handle - Receives the handle to the opened volume. + Alignment - Receives the alignment requirement for the volume. + Message - Supplies an outlet for messages. + +Return Value: + + TRUE upon successful completion. + + +--*/ +{ + UNICODE_STRING string; + OBJECT_ATTRIBUTES oa; + IO_STATUS_BLOCK status_block; + FILE_ALIGNMENT_INFORMATION alignment_info; + NTSTATUS Status; + + + string.Length = (USHORT) NtDriveName->QueryChCount() * sizeof(WCHAR); + string.MaximumLength = string.Length; + string.Buffer = (PWSTR)NtDriveName->GetWSTR(); + + InitializeObjectAttributes( &oa, + &string, + OBJ_CASE_INSENSITIVE, + 0, + 0 ); + + Status = NtOpenFile(Handle, + DesiredAccess, + &oa, &status_block, + FILE_SHARE_READ | + (ExclusiveWrite ? 0 : FILE_SHARE_WRITE), + FILE_SYNCHRONOUS_IO_ALERT); + + if (!NT_SUCCESS(Status)) + { + char* message = (Status == STATUS_ACCESS_DENIED) ? + "Access denied." : + "Cannot open volume for direct access."; + Message ? Message->Out(message) : 1; + return Status; + } + + + // Query the disk alignment information. + + Status = NtQueryInformationFile(*Handle, + &status_block, + &alignment_info, + sizeof(FILE_ALIGNMENT_INFORMATION), + FileAlignmentInformation); + + if (!NT_SUCCESS(Status)) + { + const char* message; + switch( Status ) { + + case STATUS_DEVICE_BUSY: + case STATUS_DEVICE_NOT_READY: + message = "The device is busy."; + break; + + case STATUS_DEVICE_OFF_LINE: + message = "The device is offline."; + break; + + default: + message = "Error in IOCTL call."; + break; + } + + DebugPrintTrace(("Failed NtQueryInformationFile (%x)\n", Status)); + + Message ? Message->Out(message) : 1; + + + return Status; + } + + *Alignment = alignment_info.AlignmentRequirement; + + // + // Set the ALLOW_EXTENDED_DASD_IO flag for the file handle, + // which ntfs format and chkdsk depend on to write the backup + // boot sector. We ignore the return code from this, but we + // could go either way. + // + + (VOID)NtFsControlFile( *Handle, + 0, NULL, NULL, + &status_block, + FSCTL_ALLOW_EXTENDED_DASD_IO, + NULL, 0, NULL, 0); + + return Status; +} + + + +BOOLEAN +DP_DRIVE::Initialize( + IN PCWSTRING NtDriveName, + IN OUT PMESSAGE Message, + IN BOOLEAN IsTransient + ) +/*++ + +Routine Description: + + This routine initializes a DP_DRIVE object based on the supplied drive + path. + +Arguments: + + NtDriveName - Supplies the drive path. + Message - Supplies an outlet for messages. + IsTransient - Supplies whether or not to keep the handle to the + drive open beyond this method. + ExclusiveWrite - Supplies whether or not to open the drive for + exclusive write. + FormatType - Supplies the file system type in the event of a format + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + IO_STATUS_BLOCK status_block; + DISK_GEOMETRY disk_geometry; + PARTITION_INFORMATION_EX partition_info; + GET_LENGTH_INFORMATION length_info; + BOOLEAN partition; + + FILE_FS_DEVICE_INFORMATION DeviceInfo; + + Destroy(); + + if (!DRIVE::Initialize(NtDriveName, Message)) { + Destroy(); + return FALSE; + } + + BOOL ExclusiveWrite = false; + _last_status = OpenDrive( NtDriveName, + SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, + ExclusiveWrite, + &_handle, + &_alignment_mask, + Message ); + + if(!NT_SUCCESS(_last_status)) + { + Destroy(); + DebugPrintTrace(("Can't open drive. Status returned = %x.\n", _last_status)); + return FALSE; + } + + _last_status = NtDeviceIoControlFile(_handle, 0, NULL, NULL, + &status_block, + IOCTL_DISK_IS_WRITABLE, + NULL, 0, NULL, 0); + + _is_writeable = (_last_status != STATUS_MEDIA_WRITE_PROTECTED); + + + _last_status = NtQueryVolumeInformationFile(_handle, + &status_block, + &DeviceInfo, + sizeof(DeviceInfo), + FileFsDeviceInformation); + + // this is from ::GetDriveType() + + if (!NT_SUCCESS(_last_status)) + { + _drive_type = UnknownDrive; + } else if (DeviceInfo.Characteristics & FILE_REMOTE_DEVICE) + { + _drive_type = RemoteDrive; + } + else + { + switch (DeviceInfo.DeviceType) + { + case FILE_DEVICE_NETWORK: + case FILE_DEVICE_NETWORK_FILE_SYSTEM: + _drive_type = RemoteDrive; + break; + + case FILE_DEVICE_CD_ROM: + case FILE_DEVICE_CD_ROM_FILE_SYSTEM: + _drive_type = CdRomDrive; + break; + + case FILE_DEVICE_VIRTUAL_DISK: + _drive_type = RamDiskDrive; + break; + + case FILE_DEVICE_DISK: + case FILE_DEVICE_DISK_FILE_SYSTEM: + + if ( DeviceInfo.Characteristics & FILE_REMOVABLE_MEDIA ) + { + _drive_type = RemovableDrive; + } + else { + _drive_type = FixedDrive; + } + break; + + default: + _drive_type = UnknownDrive; + break; + } + } +// #endif + + + // Query the disk geometry. + + _last_status = NtDeviceIoControlFile(_handle, 0, NULL, NULL, + &status_block, + IOCTL_DISK_GET_DRIVE_GEOMETRY, + NULL, 0, &disk_geometry, + sizeof(DISK_GEOMETRY)); + + if (!NT_SUCCESS(_last_status)) + { + DebugPrintTrace(("Can't query disk geometry. Status returned = %x.\n", _last_status)); + if ((_last_status == STATUS_UNSUCCESSFUL) || + (_last_status == STATUS_UNRECOGNIZED_MEDIA)) + { + disk_geometry.MediaType = Unknown; + } + else + { + Destroy(); + const char* message; + switch (_last_status) + { + case STATUS_NO_MEDIA_IN_DEVICE: + message = "Cannot open volume for direct access."; + break; + + case STATUS_DEVICE_BUSY: + case STATUS_DEVICE_NOT_READY: + message = "The device is busy."; + break; + + case STATUS_DEVICE_OFF_LINE: + message = "The device is offline."; + break; + + default: + message = "Error in IOCTL call."; + break; + } + + Message ? Message->Out(message) : 1; + + return FALSE; + } + } + + if (disk_geometry.MediaType == Unknown) + { + memset(&disk_geometry, 0, sizeof(DISK_GEOMETRY)); + disk_geometry.MediaType = Unknown; + } + + partition = FALSE; + + // Try to read the partition entry. + + if (disk_geometry.MediaType == FixedMedia || + disk_geometry.MediaType == RemovableMedia) + { + _last_status = NtDeviceIoControlFile(_handle, 0, NULL, NULL, + &status_block, + IOCTL_DISK_GET_LENGTH_INFO, + NULL, 0, &length_info, + sizeof(GET_LENGTH_INFORMATION)); + + partition = (BOOLEAN) NT_SUCCESS(_last_status); + + if (!NT_SUCCESS(_last_status) && + _last_status != STATUS_INVALID_DEVICE_REQUEST) { + DebugPrintTrace(("Can't get volume size. Status returned = %x.\n", _last_status)); + Destroy(); + Message ? Message->Out("Error reading partition table.") : 1; + return FALSE; + } + + if (partition) { + + _last_status = NtDeviceIoControlFile( + _handle, 0, NULL, NULL, &status_block, + IOCTL_DISK_GET_PARTITION_INFO_EX, NULL, 0, + &partition_info, + sizeof(PARTITION_INFORMATION_EX)); + + if (!NT_SUCCESS(_last_status)) { + if (_last_status != STATUS_INVALID_DEVICE_REQUEST) { + DebugPrintTrace(("Can't read partition entry. Status returned = %x.\n", _last_status)); + Destroy(); + Message ? Message->Out("Error reading partition table.") : 1; + return FALSE; + } + + // + // GET_PARTITION_INFO_EX will fail outright on an EFI Dynamic + // Volume. In this case, just make up the starting offset + // so that FORMAT/CHKDSK can proceed normally. + // + + partition_info.PartitionStyle = PARTITION_STYLE_GPT; + partition_info.StartingOffset.QuadPart = 0x7E00; + partition_info.PartitionLength.QuadPart = length_info.Length.QuadPart; + partition_info.Gpt.PartitionType = PARTITION_BASIC_DATA_GUID; + } + + if (partition_info.PartitionStyle != PARTITION_STYLE_MBR) { + + // If this is EFI, then just make up reasonable MBR values + // so that CHKDSK/FORMAT can proceed with business as usual. + + partition_info.PartitionStyle = PARTITION_STYLE_MBR; + if (IsEqualGUID(partition_info.Gpt.PartitionType, + PARTITION_BASIC_DATA_GUID)) + { + partition_info.Mbr.PartitionType = 0x7; + } + else + { + partition_info.Mbr.PartitionType = 0xEE; + } + + partition_info.Mbr.BootIndicator = FALSE; + partition_info.Mbr.RecognizedPartition = TRUE; + partition_info.Mbr.HiddenSectors = + (ULONG) (partition_info.StartingOffset.QuadPart/ + disk_geometry.BytesPerSector); + } + } + } + + + // Store the information in the class. + + if (partition) + { + _partition_info = partition_info; + + DiskGeometryToDriveType(&disk_geometry, + partition_info.PartitionLength/ + disk_geometry.BytesPerSector, + partition_info.Mbr.HiddenSectors, + &_actual); + } + else + { + DiskGeometryToDriveType(&disk_geometry, &_actual); + } + + if (!_num_supported) + { + _num_supported = 1; + + if (!(_supported_list = NEW DRTYPE[1])) + { + Destroy(); + Message ? Message->Out("Insufficient memory.") : 1; + return FALSE; + } + + _supported_list[0] = _actual; + } + + if (!CheckForPrimaryPartition()) + { + DebugPrintTrace(("Failed CheckForPrimaryPartition (%x)\n", _last_status)); + Destroy(); + if (Message) + { + switch (_last_status) + { + case STATUS_NO_MEDIA_IN_DEVICE: + Message->Out("There is no media in the drive."); + break; + + case STATUS_DEVICE_BUSY: + case STATUS_DEVICE_NOT_READY: + Message->Out("The device is busy."); + break; + + case STATUS_DEVICE_OFF_LINE: + Message->Out("The device is offline."); + break; + + default: + Message->Out("Error in IOCTL call."); + break; + } + } + return FALSE; + } + + // + // Determine whether the media is a super-floppy; non-floppy + // removable media which is not partitioned. Such media will + // have but a single partition, normal media will have at least 4. + // + + if (disk_geometry.MediaType == RemovableMedia) + { + CONST INT EntriesPerBootRecord = 4; + CONST INT MaxLogicalVolumes = 23; + CONST INT Length = sizeof(DRIVE_LAYOUT_INFORMATION_EX) + + EntriesPerBootRecord * (MaxLogicalVolumes + 1) * + sizeof(PARTITION_INFORMATION_EX); + + UCHAR buf[Length]; + + DRIVE_LAYOUT_INFORMATION_EX *layout_info = (DRIVE_LAYOUT_INFORMATION_EX *)buf; + + _last_status = NtDeviceIoControlFile(_handle, 0, NULL, NULL, + &status_block, + IOCTL_DISK_GET_DRIVE_LAYOUT_EX, + NULL, 0, layout_info, + Length); + +#if 1 + if (!NT_SUCCESS(_last_status)) + { + if (_last_status == STATUS_INVALID_DEVICE_REQUEST) + { + } + else + { + DebugPrintTrace(("Failed IOCTL_DISK_GET_DRIVE_LAYOUT_EX (%x)\n", _last_status)); + } + } +#endif + + if (!NT_SUCCESS(_last_status)) + { + Destroy(); + + if (Message) + { + switch (_last_status) { + case STATUS_NO_MEDIA_IN_DEVICE: + Message->Out("There is no media in the drive."); + break; + + case STATUS_DEVICE_BUSY: + case STATUS_DEVICE_NOT_READY: + Message->Out("The device is busy."); + break; + + case STATUS_DEVICE_OFF_LINE: + Message->Out("The device is offline."); + break; + + default: + Message->Out("Error in IOCTL call."); + break; + + } + } + return FALSE; + } + + } + + if (!IsTransient) + { + NtClose(_handle); + _handle = 0; + } + + return TRUE; + +} + + +ULONG +DP_DRIVE::QuerySectorSize( + ) CONST +/*++ + +Routine Description: + + This routine computes the number of bytes per sector. + +Arguments: + + None. + +Return Value: + + The number of bytes per sector. + +--*/ +{ + return _actual.SectorSize; +} + + +BIG_INT +DP_DRIVE::QuerySectors( + ) CONST +/*++ + +Routine Description: + + This routine computes the number sectors on the disk. This does not + include the hidden sectors. + +Arguments: + + None. + +Return Value: + + The number of sectors on the disk. + +--*/ +{ + return _actual.Sectors; +} + + +VOID +DP_DRIVE::Destroy( + ) +/*++ + +Routine Description: + + This routine returns a DP_DRIVE to its initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + memset(&_actual, 0, sizeof(DRTYPE)); + DELETE_ARRAY(_supported_list); + _num_supported = 0; + _alignment_mask = 0; + _is_writeable = FALSE; + _is_primary_partition = FALSE; + + + if (_handle) + { + NtClose(_handle); + _handle = 0; + } + + memset(&_partition_info, 0, sizeof(_partition_info)); +} + + +VOID +DP_DRIVE::DiskGeometryToDriveType( + IN PCDISK_GEOMETRY DiskGeometry, + OUT PDRTYPE DriveType + ) +/*++ + +Routine Description: + + This routine computes the drive type given the disk geometry. + +Arguments: + + DiskGeometry - Supplies the disk geometry for the drive. + DriveType - Returns the drive type for the drive. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + DriveType->MediaType = DiskGeometry->MediaType; + DriveType->SectorSize = DiskGeometry->BytesPerSector; + DriveType->Sectors = DiskGeometry->Cylinders* + DiskGeometry->TracksPerCylinder* + DiskGeometry->SectorsPerTrack; + DriveType->HiddenSectors = 0; + DriveType->SectorsPerTrack = DiskGeometry->SectorsPerTrack; + DriveType->Heads = DiskGeometry->TracksPerCylinder; +} + + +VOID +DP_DRIVE::DiskGeometryToDriveType( + IN PCDISK_GEOMETRY DiskGeometry, + IN BIG_INT NumSectors, + IN BIG_INT NumHiddenSectors, + OUT PDRTYPE DriveType + ) +/*++ + +Routine Description: + + This routine computes the drive type given the disk geometry. + +Arguments: + + DiskGeometry - Supplies the disk geometry for the drive. + NumSectors - Supplies the total number of non-hidden sectors on + the disk. + NumHiddenSectors - Supplies the number of hidden sectors on the disk. + DriveType - Returns the drive type for the drive. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + DriveType->MediaType = DiskGeometry->MediaType; + DriveType->SectorSize = DiskGeometry->BytesPerSector; + DriveType->Sectors = NumSectors; + DriveType->HiddenSectors = NumHiddenSectors; + DriveType->SectorsPerTrack = DiskGeometry->SectorsPerTrack; + DriveType->Heads = DiskGeometry->TracksPerCylinder; +} + +DEFINE_CONSTRUCTOR( IO_DP_DRIVE, DP_DRIVE ); + +VOID +IO_DP_DRIVE::Construct ( + ) + +/*++ + +Routine Description: + + Constructor for IO_DP_DRIVE. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _is_locked = FALSE; + _is_exclusive_write = FALSE; + _cache = NULL; + _ValidBlockLengthForVerify = 0; + _message = NULL; +} + + +VOID +IO_DP_DRIVE::Destroy( + ) +/*++ + +Routine Description: + + This routine returns an IO_DP_DRIVE object to its initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + DELETE(_cache); + + if (_is_exclusive_write) + { + Dismount(); + _is_exclusive_write = FALSE; + } + + if (_is_locked) + { + Unlock(); + _is_locked = FALSE; + } + + _ValidBlockLengthForVerify = 0; + _message = NULL; +} + + +IO_DP_DRIVE::~IO_DP_DRIVE( + ) +/*++ + +Routine Description: + + Destructor for IO_DP_DRIVE. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + +BOOLEAN +IO_DP_DRIVE::Initialize( + IN PCWSTRING NtDriveName, + IN OUT PMESSAGE Message + ) +/*++ + +Routine Description: + + This routine initializes an IO_DP_DRIVE object. + +Arguments: + + NtDriveName - Supplies the drive path. + Message - Supplies an outlet for messages. + ExclusiveWrite - Supplies whether or not to open the drive for + exclusive write. + FormatType - Supplies the file system type in the event of a format + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + Destroy(); + + if (!DP_DRIVE::Initialize(NtDriveName, Message, TRUE)) + { + Destroy(); + return FALSE; + } + + _is_exclusive_write = false; + + if (!(_cache = NEW DRIVE_CACHE) || + !_cache->Initialize(this)) + { + Destroy(); + return FALSE; + } + + _ValidBlockLengthForVerify = 0; + _message = Message; + + return TRUE; +} + + +BOOLEAN +IO_DP_DRIVE::Read( + IN BIG_INT StartingSector, + IN SECTORCOUNT NumberOfSectors, + OUT PVOID Buffer + ) +/*++ + +Routine Description: + + This routine reads a run of sectors into the buffer pointed to by + 'Buffer'. + +Arguments: + + StartingSector - Supplies the first sector to be read. + NumberOfSectors - Supplies the number of sectors to be read. + Buffer - Supplies a buffer to read the run of sectors into. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + DebugAssert(_cache); + return _cache->Read(StartingSector, NumberOfSectors, Buffer); +} + + + +BOOLEAN +IO_DP_DRIVE::Write( + BIG_INT StartingSector, + SECTORCOUNT NumberOfSectors, + PVOID Buffer + ) +/*++ + +Routine Description: + + This routine writes a run of sectors onto the disk from the buffer pointed + to by 'Buffer'. Writing is only permitted if 'Lock' was called. + +Arguments: + + StartingSector - Supplies the first sector to be written. + NumberOfSectors - Supplies the number of sectors to be written. + Buffer - Supplies the buffer to write the run of sectors from. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + DebugAssert(_cache); + return _cache->Write(StartingSector, NumberOfSectors, Buffer); +} + + +BOOLEAN +IO_DP_DRIVE::HardRead( + IN BIG_INT StartingSector, + IN SECTORCOUNT NumberOfSectors, + OUT PVOID Buffer + ) +/*++ + +Routine Description: + + This routine reads a run of sectors into the buffer pointed to by + 'Buffer'. + +Arguments: + + StartingSector - Supplies the first sector to be read. + NumberOfSectors - Supplies the number of sectors to be read. + Buffer - Supplies a buffer to read the run of sectors into. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + ULONG sector_size; + ULONG buffer_size; + IO_STATUS_BLOCK status_block; + BIG_INT secptr; + BIG_INT endofrange; + SECTORCOUNT increment; + PCHAR bufptr; + BIG_INT byte_offset; + BIG_INT tmp; + LARGE_INTEGER l; + + DebugAssert(!(((ULONG_PTR) Buffer) & QueryAlignmentMask())); + + sector_size = QuerySectorSize(); + endofrange = StartingSector + NumberOfSectors; + increment = MaxIoSize/sector_size; + + bufptr = (PCHAR) Buffer; + for (secptr = StartingSector; secptr < endofrange; secptr += increment) { + + byte_offset = secptr*sector_size; + + if (secptr + increment > endofrange) { + tmp = endofrange - secptr; + DebugAssert(tmp.GetHighPart() == 0); + buffer_size = sector_size*tmp.GetLowPart(); + + } else { + buffer_size = sector_size*increment; + + } + + l = byte_offset.GetLargeInteger(); + + _last_status = NtReadFile(_handle, 0, NULL, NULL, &status_block, + bufptr, buffer_size, &l, NULL); + + if (_last_status == STATUS_NO_MEMORY) { + increment /= 2; + secptr -= increment; + continue; + } + + if (NT_ERROR(_last_status) || status_block.Information != buffer_size) { + + if (NT_ERROR(_last_status)) { + DebugPrintTrace(("HardRead: NtReadFile failure: %x, %I64x, %x\n", + _last_status, l, buffer_size)); + } else { + DebugPrintTrace(("HardRead: NtReadFile failure: %I64x, %x, %x\n", + l, status_block.Information, buffer_size)); + } + + if (_message) { + if (NT_ERROR(_last_status)) + { + _message->Out("Read failure with status ", _last_status, " at offset ", l.QuadPart, " for ", buffer_size, " bytes."); + } + else + { + _message->Out("Incorrect read at offset ", l.QuadPart," for ", buffer_size," bytes but got ", status_block.Information," bytes."); + } + } + + return FALSE; + } + + bufptr += buffer_size; + } + + return TRUE; +} + + +BOOLEAN +IO_DP_DRIVE::HardWrite( + BIG_INT StartingSector, + SECTORCOUNT NumberOfSectors, + PVOID Buffer + ) +/*++ + +Routine Description: + + This routine writes a run of sectors onto the disk from the buffer pointed + to by 'Buffer'. Writing is only permitted if 'Lock' was called. + + After writing each chunk, we read it back to make sure the write + really succeeded. + +Arguments: + + StartingSector - Supplies the first sector to be written. + NumberOfSectors - Supplies the number of sectors to be written. + Buffer - Supplies the buffer to write the run of sectors from. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + ULONG sector_size; + ULONG buffer_size; + IO_STATUS_BLOCK status_block; + BIG_INT secptr; + BIG_INT endofrange; + SECTORCOUNT increment; + PCHAR bufptr; + PCHAR scratch_ptr; + BIG_INT byte_offset; + BIG_INT tmp; + LARGE_INTEGER l; + CHAR ScratchIoBuf[MaxIoSize + 511]; + + DebugAssert(!(((ULONG_PTR) Buffer) & QueryAlignmentMask())); + DebugAssert(QueryAlignmentMask() < 0x200); + + if (! ((ULONG_PTR)ScratchIoBuf & QueryAlignmentMask())) { + scratch_ptr = ScratchIoBuf; + } else { + scratch_ptr = (PCHAR)((ULONG_PTR) ((PCHAR)ScratchIoBuf + + QueryAlignmentMask()) & (~(ULONG_PTR)QueryAlignmentMask())); + } + DebugAssert(!(((ULONG_PTR) scratch_ptr) & QueryAlignmentMask())); + + sector_size = QuerySectorSize(); + endofrange = StartingSector + NumberOfSectors; + increment = MaxIoSize/sector_size; + + bufptr = (PCHAR) Buffer; + for (secptr = StartingSector; secptr < endofrange; secptr += increment) { + + byte_offset = secptr*sector_size; + + if (secptr + increment > endofrange) { + tmp = endofrange - secptr; + DebugAssert(tmp.GetHighPart() == 0); + buffer_size = sector_size*tmp.GetLowPart(); + } else { + buffer_size = sector_size*increment; + } + + l = byte_offset.GetLargeInteger(); + + _last_status = NtWriteFile(_handle, 0, NULL, NULL, &status_block, + bufptr, buffer_size, &l, NULL); + + if (_last_status == STATUS_NO_MEMORY) { + increment /= 2; + secptr -= increment; + continue; + } + + if (NT_ERROR(_last_status) || status_block.Information != buffer_size) { + + if (NT_ERROR(_last_status)) { + DebugPrintTrace(("HardWrite: NtWriteFile failure: %x, %I64x, %x\n", + _last_status, l, buffer_size)); + } else { + DebugPrintTrace(("HardWrite: NtWriteFile failure: %I64x, %x, %x\n", + l, status_block.Information, buffer_size)); + } + + return FALSE; + } + + DebugAssert(buffer_size <= MaxIoSize); + + _last_status = NtReadFile(_handle, 0, NULL, NULL, &status_block, + scratch_ptr, buffer_size, &l, NULL); + + if (NT_ERROR(_last_status) || status_block.Information != buffer_size) { + + if (NT_ERROR(_last_status)) { + DebugPrintTrace(("HardWrite: NtReadFile failure: %x, %I64x, %x\n", + _last_status, l, buffer_size)); + } else { + DebugPrintTrace(("HardWrite: NtReadFile failure: %I64x, %x, %x\n", + l, status_block.Information, buffer_size)); + } + + if (_message) { + if (NT_ERROR(_last_status)) + + { + _message->Out("Write failure with status ", _last_status, " at offset ", l.QuadPart, " for ", buffer_size, " bytes."); + } + else + { + _message->Out("Incorrect write at offset ", l.QuadPart, " for ", buffer_size, " bytes but wrote ", status_block.Information, " bytes."); + } + } + + return FALSE; + } + + if (0 != memcmp(scratch_ptr, bufptr, buffer_size)) { + + DebugPrint("What's read back does not match what's written out\n"); + if (_message) + { + _message->Out("The data written out is different from what is being read back at offset ", l.QuadPart, " for ", buffer_size, " bytes"); + } + + return FALSE; + } + + bufptr += buffer_size; + } + + return TRUE; +} + + +BOOLEAN +DP_DRIVE::CheckForPrimaryPartition( + ) +/*++ + +Routine Description: + + This routine checks to see if the volume is on a primary partition. + It sets the result returned by IsPrimaryPartition routine. + +Arguments: + + N/A + +Return Value: + + TRUE if successfully determined if the volume is on a primary + partition. +--*/ +{ + CONST INT EntriesPerBootRecord = 4; + CONST INT MaxLogicalVolumes = 23; + CONST INT Info_Length = EntriesPerBootRecord * (MaxLogicalVolumes + 1) * + sizeof(PARTITION_INFORMATION_EX); + INT Length = sizeof(DRIVE_LAYOUT_INFORMATION_EX) + Info_Length; + + IO_STATUS_BLOCK status_block; + STORAGE_DEVICE_NUMBER device_info; + DRIVE_LAYOUT_INFORMATION_EX *layout_info, *temp; + + ULONG i, partition_count; + + _is_primary_partition = FALSE; + + _last_status = NtDeviceIoControlFile(_handle, 0, NULL, NULL, + &status_block, + IOCTL_STORAGE_GET_DEVICE_NUMBER, + NULL, 0, &device_info, + sizeof(STORAGE_DEVICE_NUMBER)); + + if (NT_SUCCESS(_last_status) && device_info.DeviceType == FILE_DEVICE_DISK && + (device_info.PartitionNumber != -1 && device_info.PartitionNumber != 0)) { + + layout_info = (DRIVE_LAYOUT_INFORMATION_EX *) MALLOC(Length); + + do { + + _last_status = NtDeviceIoControlFile(_handle, 0, NULL, NULL, + &status_block, + IOCTL_DISK_GET_DRIVE_LAYOUT_EX, + NULL, 0, layout_info, + Length); + + if (!NT_SUCCESS(_last_status)) { + if ((_last_status == STATUS_BUFFER_TOO_SMALL) + && (Length += Info_Length) + && ((temp = (DRIVE_LAYOUT_INFORMATION_EX *)REALLOC(layout_info, Length)) != NULL)) { + layout_info = temp; + } else { + FREE(layout_info); + return FALSE; + } + } + }while (_last_status == STATUS_BUFFER_TOO_SMALL); + + if (layout_info->PartitionStyle == PARTITION_STYLE_MBR) { + partition_count = min(4, layout_info->PartitionCount); + + for (i=0; iPartitionEntry[i].PartitionNumber == + device_info.PartitionNumber) { + _is_primary_partition = TRUE; + break; + } + } + } + + FREE(layout_info); + } + + return TRUE; +} + + + +BOOLEAN +IO_DP_DRIVE::Verify( + IN BIG_INT StartingSector, + IN BIG_INT NumberOfSectors + ) +/*++ + +Routine Description: + + This routine verifies a run of sectors on the disk. + +Arguments: + + StartingSector - Supplies the first sector of the run to verify. + NumberOfSectors - Supplies the number of sectors in the run to verify. + +Return Value: + + FALSE - Some of the sectors in the run are bad. + TRUE - All of the sectors in the run are good. + +--*/ +{ + VERIFY_INFORMATION verify_info; + IO_STATUS_BLOCK status_block; + BIG_INT starting_offset; + BIG_INT verify_size; + + DebugAssert(QuerySectorSize()); + + _last_status = STATUS_SUCCESS; + + if (!_is_exclusive_write) + { + return VerifyWithRead(StartingSector, NumberOfSectors); + } + + starting_offset = StartingSector*QuerySectorSize(); + verify_size = NumberOfSectors*QuerySectorSize(); + + verify_info.StartingOffset = starting_offset.GetLargeInteger(); + + // Note: norbertk Verify IOCTL is destined to go to a BIG_INT length. + DebugAssert(verify_size.GetHighPart() == 0); + verify_info.Length = verify_size.GetLowPart(); + + _last_status = NtDeviceIoControlFile(_handle, 0, NULL, NULL, + &status_block, IOCTL_DISK_VERIFY, + &verify_info, + sizeof(VERIFY_INFORMATION), + NULL, 0); + + return (BOOLEAN) NT_SUCCESS(_last_status); +} + + +BOOLEAN +IO_DP_DRIVE::Verify( + IN BIG_INT StartingSector, + IN BIG_INT NumberOfSectors, + IN OUT PNUMBER_SET BadSectors + ) +/*++ + +Routine Description: + + This routine computes which sectors in the given range are bad + and adds these bad sectors to the bad sectors list. + +Arguments: + + StartingSector - Supplies the starting sector. + NumberOfSectors - Supplies the number of sectors. + BadSectors - Supplies the bad sectors list. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + ULONG MaxSectorsInVerify = 512; + + ULONG MaxDiskHits; + BIG_INT half; + PBIG_INT starts; + PBIG_INT run_lengths=NULL; + ULONG i, n; + BIG_INT num_sectors; + + if (NumberOfSectors == 0) { + return TRUE; + } + + if (NumberOfSectors.GetHighPart() != 0) { + DebugPrint("Number of sectors to verify exceeded 32 bits\n"); + return FALSE; + } + + + // + // Check to see if block length has been set + // + if (!_ValidBlockLengthForVerify) + { + num_sectors = min(NumberOfSectors, MaxSectorsInVerify); + + while (!Verify(StartingSector, num_sectors)) + { + if (QueryLastNtStatus() == STATUS_INVALID_BLOCK_LENGTH || + QueryLastNtStatus() == STATUS_INVALID_DEVICE_REQUEST) + { + if (num_sectors == 1) { + DebugPrint("Number of sectors to verify mysteriously down to 1\n"); + return FALSE; + } + num_sectors = num_sectors / 2; + } else + break; + } + + DebugAssert(num_sectors.GetHighPart() == 0); + + if (QueryLastNtStatus() == STATUS_SUCCESS) { + + if (!(NumberOfSectors < MaxSectorsInVerify && + NumberOfSectors == num_sectors)) + MaxSectorsInVerify = _ValidBlockLengthForVerify = num_sectors.GetLowPart(); + + + if (num_sectors == NumberOfSectors) + return TRUE; // done, return + else { + // + // skip over sectors that has been verified + // + StartingSector += num_sectors; + NumberOfSectors -= num_sectors; + } + } + } else + MaxSectorsInVerify = _ValidBlockLengthForVerify; + + + // Allow 20 retries so that a single bad sector in this region + // will be found accurately. + + MaxDiskHits = (20 + NumberOfSectors/MaxSectorsInVerify + 1).GetLowPart(); + + if (!(starts = NEW BIG_INT[MaxDiskHits]) || + !(run_lengths = NEW BIG_INT[MaxDiskHits])) { + + DELETE_ARRAY(starts); + DELETE_ARRAY(run_lengths); + return FALSE; + } + + num_sectors = NumberOfSectors; + for (i = 0; num_sectors > 0; i++) { + starts[i] = StartingSector + i*MaxSectorsInVerify; + if (MaxSectorsInVerify > num_sectors) { + run_lengths[i] = num_sectors; + } else { + run_lengths[i] = MaxSectorsInVerify; + } + num_sectors -= run_lengths[i]; + } + + n = i; + + for (i = 0; i < n; i++) { + + if (!Verify(starts[i], run_lengths[i])) { + + if (QueryLastNtStatus() == STATUS_NO_MEDIA_IN_DEVICE) { + DELETE_ARRAY(starts); + DELETE_ARRAY(run_lengths); + return FALSE; + } + + DebugAssert(QueryLastNtStatus() != STATUS_INVALID_BLOCK_LENGTH && + QueryLastNtStatus() != STATUS_INVALID_DEVICE_REQUEST); + + if (BadSectors == NULL) { + DELETE_ARRAY(starts); + DELETE_ARRAY(run_lengths); + return FALSE; + } + + if (n + 2 > MaxDiskHits) { + + if (!BadSectors->Add(starts[i], run_lengths[i])) { + DELETE_ARRAY(starts); + DELETE_ARRAY(run_lengths); + return FALSE; + } + + } else { + + if (run_lengths[i] == 1) { + + if (!BadSectors->Add(starts[i])) { + DELETE_ARRAY(starts); + DELETE_ARRAY(run_lengths); + return FALSE; + } + + } else { + + half = run_lengths[i]/2; + + starts[n] = starts[i]; + run_lengths[n] = half; + starts[n + 1] = starts[i] + half; + run_lengths[n + 1] = run_lengths[i] - half; + + n += 2; + } + } + } + } + + + DELETE_ARRAY(starts); + DELETE_ARRAY(run_lengths); + + return TRUE; +} + + +BOOLEAN +IO_DP_DRIVE::VerifyWithRead( + IN BIG_INT StartingSector, + IN BIG_INT NumberOfSectors + ) +/*++ + +Routine Description: + + This routine verifies the usability of the given range of sectors + using read. + +Arguments: + + StartingSector - Supplies the starting sector of the verify. + Number OfSectors - Supplies the number of sectors to verify. + +Return Value: + + FALSE - At least one of the sectors in the given range was unreadable. + TRUE - All of the sectors in the given range are readable. + +--*/ +{ + HMEM hmem; + ULONG grab; + BIG_INT i; + + if (!hmem.Initialize() || + !hmem.Acquire(MaxIoSize, QueryAlignmentMask())) { + + return FALSE; + } + + grab = MaxIoSize/QuerySectorSize(); + for (i = 0; i < NumberOfSectors; i += grab) { + + if (NumberOfSectors - i < grab) { + grab = (NumberOfSectors - i).GetLowPart(); + } + + if (!HardRead(StartingSector + i, grab, hmem.GetBuf())) { + return FALSE; + } + } + + return TRUE; +} + + +BOOLEAN +IO_DP_DRIVE::Lock( + ) +/*++ + +Routine Description: + + This routine locks the drive. If the drive is already locked then + this routine will do nothing. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + IO_STATUS_BLOCK status_block; + + if (_is_locked) + { + return TRUE; + } + + _last_status = NtFsControlFile( _handle, + 0, NULL, NULL, + &status_block, + FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0); + + _is_locked = (BOOLEAN) NT_SUCCESS(_last_status); + + if (_is_locked) + { + _is_exclusive_write = TRUE; + } + else + { + DebugPrintTrace(("Unable to lock the volume (%x)\n", _last_status)); + } + + return _is_locked; +} + + +BOOLEAN +IO_DP_DRIVE::Unlock( + ) +/*++ + +Routine Description: + + This routine unlocks the drive. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + IO_STATUS_BLOCK status_block; + + _last_status = NtFsControlFile( _handle, + 0, NULL, NULL, + &status_block, + FSCTL_UNLOCK_VOLUME, + NULL, 0, NULL, 0); + + return NT_SUCCESS(_last_status); +} + + +BOOLEAN +IO_DP_DRIVE::Dismount( + ) +/*++ + +Routine Description: + + This routine dismounts the drive. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + IO_STATUS_BLOCK status_block; + + if( !NT_SUCCESS(_last_status = NtFsControlFile( _handle, + 0, NULL, NULL, + &status_block, + FSCTL_DISMOUNT_VOLUME, + NULL, 0, NULL, 0)) ) { + return FALSE; + } + + return TRUE; +} + + + +DEFINE_CONSTRUCTOR( LOG_IO_DP_DRIVE, IO_DP_DRIVE ); + + + +LOG_IO_DP_DRIVE::~LOG_IO_DP_DRIVE( + ) +/*++ + +Routine Description: + + Destructor for LOG_IO_DP_DRIVE. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ +} + + + +BOOLEAN +LOG_IO_DP_DRIVE::Initialize( + IN PCWSTRING NtDriveName, + IN OUT PMESSAGE Message + ) +/*++ + +Routine Description: + + This routine initializes a LOG_IO_DP_DRIVE object. + +Arguments: + + NtDriveName - Supplies the path of the drive object. + Message - Supplies an outlet for messages. + ExclusiveWrite - Supplies whether or not to open the drive for + exclusive write. + FormatType - Supplies the file system type in the event of a format + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + return IO_DP_DRIVE::Initialize(NtDriveName, Message); +} + diff --git a/ifsutil/src/ifssys.cxx b/ifsutil/src/ifssys.cxx new file mode 100644 index 0000000..f4ced96 --- /dev/null +++ b/ifsutil/src/ifssys.cxx @@ -0,0 +1,343 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + ifssys.cxx + +Abstract: + + This module contains the implementation for the IFS_SYSTEM class. + The IFS_SYSTEM class is an abstract class which offers an + interface for communicating with the underlying operating system + on specific IFS issues. + +--*/ + + +#include "ulib.hxx" + + +#include "ifssys.hxx" +#include "bigint.hxx" +#include "wstring.hxx" + +#include "drive.hxx" +#include "secrun.hxx" +#include "hmem.hxx" +#include "bpb.hxx" +#include "volume.hxx" + +#include "untfs2.hxx" + + +BOOLEAN +IFS_SYSTEM::IsThisNtfs( + IN BIG_INT Sectors, + IN ULONG SectorSize, + IN PVOID BootSectorData + ) +/*++ + +Routine Description: + + This routine determines whether or not the given structure + is part of an NTFS partition. + +Arguments: + + Sectors - Supplies the number of sectors on the drive. + SectorSize - Supplies the number of bytes per sector. + BootSectorData + - Supplies an unaligned boot sector. + +Return Value: + + FALSE - The supplied boot sector is not part of an NTFS + TRUE - The supplied boot sector is part of an NTFS volume. + +--*/ +{ + PPACKED_BOOT_SECTOR BootSector = (PPACKED_BOOT_SECTOR)BootSectorData; + BOOLEAN r; + ULONG checksum; + PULONG l; + USHORT reserved_sectors, root_entries, sectors, sectors_per_fat; + USHORT bytes_per_sector; + ULONG large_sectors; + + memcpy(&reserved_sectors, BootSector->PackedBpb.ReservedSectors, sizeof(USHORT)); + memcpy(&root_entries, BootSector->PackedBpb.RootEntries, sizeof(USHORT)); + memcpy(§ors, BootSector->PackedBpb.Sectors, sizeof(USHORT)); + memcpy(§ors_per_fat, BootSector->PackedBpb.SectorsPerFat, sizeof(USHORT)); + memcpy(&bytes_per_sector, BootSector->PackedBpb.BytesPerSector, sizeof(USHORT)); + memcpy(&large_sectors, BootSector->PackedBpb.LargeSectors, sizeof(ULONG)); + + + r = TRUE; + + checksum = 0; + for (l = (PULONG) BootSector; l < (PULONG) &BootSector->Checksum; l++) { + checksum += *l; + } + + if (BootSector->Oem[0] != 'N' || + BootSector->Oem[1] != 'T' || + BootSector->Oem[2] != 'F' || + BootSector->Oem[3] != 'S' || + BootSector->Oem[4] != ' ' || + BootSector->Oem[5] != ' ' || + BootSector->Oem[6] != ' ' || + BootSector->Oem[7] != ' ' || + // BootSector->Checksum != checksum || + bytes_per_sector != SectorSize) { + + r = FALSE; + + } else if ((BootSector->PackedBpb.SectorsPerCluster[0] != 0x1) && + (BootSector->PackedBpb.SectorsPerCluster[0] != 0x2) && + (BootSector->PackedBpb.SectorsPerCluster[0] != 0x4) && + (BootSector->PackedBpb.SectorsPerCluster[0] != 0x8) && + (BootSector->PackedBpb.SectorsPerCluster[0] != 0x10) && + (BootSector->PackedBpb.SectorsPerCluster[0] != 0x20) && + (BootSector->PackedBpb.SectorsPerCluster[0] != 0x40) && + (BootSector->PackedBpb.SectorsPerCluster[0] != 0x80)) { + + r = FALSE; + + } else if (reserved_sectors != 0 || + BootSector->PackedBpb.Fats[0] != 0 || + root_entries != 0 || + sectors != 0 || + sectors_per_fat != 0 || + large_sectors != 0 || + BootSector->NumberSectors > Sectors || + BootSector->MftStartLcn >= Sectors || + BootSector->Mft2StartLcn >= Sectors) { + + r = FALSE; + } + + if (!r) { + return r; + } + + if (BootSector->ClustersPerFileRecordSegment < 0) { + + LONG temp = LONG(BootSector->ClustersPerFileRecordSegment); + + temp = 2 << -temp; + + if (temp < 512) { + return FALSE; + } + } else if ((BootSector->ClustersPerFileRecordSegment != 0x1) && + (BootSector->ClustersPerFileRecordSegment != 0x2) && + (BootSector->ClustersPerFileRecordSegment != 0x4) && + (BootSector->ClustersPerFileRecordSegment != 0x8) && + (BootSector->ClustersPerFileRecordSegment != 0x10) && + (BootSector->ClustersPerFileRecordSegment != 0x20) && + (BootSector->ClustersPerFileRecordSegment != 0x40) && + (BootSector->ClustersPerFileRecordSegment != 0x80)) { + + return FALSE; + } + + if (BootSector->DefaultClustersPerIndexAllocationBuffer < 0) { + + LONG temp = LONG(BootSector->DefaultClustersPerIndexAllocationBuffer); + + temp = 2 << -temp; + + if (temp < 512) { + return FALSE; + } + } else if ((BootSector->DefaultClustersPerIndexAllocationBuffer != 0x1) && + (BootSector->DefaultClustersPerIndexAllocationBuffer != 0x2) && + (BootSector->DefaultClustersPerIndexAllocationBuffer != 0x4) && + (BootSector->DefaultClustersPerIndexAllocationBuffer != 0x8) && + (BootSector->DefaultClustersPerIndexAllocationBuffer != 0x10) && + (BootSector->DefaultClustersPerIndexAllocationBuffer != 0x20) && + (BootSector->DefaultClustersPerIndexAllocationBuffer != 0x40) && + (BootSector->DefaultClustersPerIndexAllocationBuffer != 0x80)) { + + r = FALSE; + } + + return r; +} + + +#define BOOTBLKSECTORS 4 +typedef int DSKPACKEDBOOTSECT; + + + +BOOLEAN +IFS_SYSTEM::QueryFileSystemNameIsNtfs( + IN PCWSTRING NtDriveName, + OUT PBOOL FileSystemNameIsNtfs, + OUT PNTSTATUS ErrorCode +) +/*++ + +Routine Description: + + This routine computes the file system name for the drive specified. + +Arguments: + + NtDriveName - Supplies an NT style drive name. + FileSystemName - Returns the file system name for the drive. + ErrorCode - Receives an error code (if the method fails). + Note that this may be NULL, in which case the + exact error is not reported. + FileSystemNameAndVersion + - Returns the file system name and version for the drive. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + LOG_IO_DP_DRIVE drive; + HMEM bootsec_hmem; + SECRUN bootsec; + HMEM super_hmem; + SECRUN super_secrun; + HMEM spare_hmem; + SECRUN spare; + BOOLEAN could_be_fat; + BOOLEAN could_be_hpfs; + BOOLEAN could_be_ntfs; + BOOLEAN could_be_ofs; + ULONG num_boot_sectors; + BOOLEAN first_read_failed = FALSE; + + DSTRING fs_name_version; + + if (ErrorCode) + { + *ErrorCode = 0; + } + *FileSystemNameIsNtfs = false; + + + if (!drive.Initialize(NtDriveName)) + { + if (ErrorCode) + { + *ErrorCode = drive.QueryLastNtStatus(); + } + return FALSE; + } + + could_be_fat = could_be_hpfs = could_be_ntfs = could_be_ofs = TRUE; + + + if (drive.QueryMediaType() == Unknown) + { + *FileSystemNameIsNtfs = false; + return true; + } + + num_boot_sectors = max(1, BYTES_PER_BOOT_SECTOR/drive.QuerySectorSize()); + + if (!bootsec_hmem.Initialize() || + !bootsec.Initialize(&bootsec_hmem, &drive, 0, num_boot_sectors)) + { + *FileSystemNameIsNtfs = false; + return true; + } + + if (!bootsec.Read()) { + + could_be_fat = could_be_hpfs = FALSE; + first_read_failed = TRUE; + + bootsec.Relocate(drive.QuerySectors()); + + if (!bootsec.Read()) { + + bootsec.Relocate(drive.QuerySectors()/2); + + if (!bootsec.Read()) + { + could_be_ntfs = FALSE; + } + } + } + + if (could_be_ntfs && + IsThisNtfs(drive.QuerySectors(), + drive.QuerySectorSize(), + (PPACKED_BOOT_SECTOR)bootsec.GetBuf())) + { + *FileSystemNameIsNtfs = true; + return true; + } + + *FileSystemNameIsNtfs = false; + return true; +} + + +DECLARE_CLASS(DP_DRIVE); +DECLARE_CLASS(DRIVE); +DECLARE_CLASS(DRIVE_CACHE); +DECLARE_CLASS(INTSTACK); +DECLARE_CLASS(IO_DP_DRIVE); +DECLARE_CLASS(LOG_IO_DP_DRIVE); +DECLARE_CLASS(NUMBER_EXTENT); +DECLARE_CLASS(NUMBER_SET); +DECLARE_CLASS(SECRUN); +DECLARE_CLASS(SUPERAREA); +DECLARE_CLASS(VOL_LIODPDRV); + + +BOOLEAN +IfsutilDefineClassDescriptors( +) +{ + if (DEFINE_CLASS_DESCRIPTOR(DP_DRIVE) && + DEFINE_CLASS_DESCRIPTOR(DRIVE) && + DEFINE_CLASS_DESCRIPTOR(DRIVE_CACHE) && + DEFINE_CLASS_DESCRIPTOR(INTSTACK) && + DEFINE_CLASS_DESCRIPTOR(IO_DP_DRIVE) && + DEFINE_CLASS_DESCRIPTOR(LOG_IO_DP_DRIVE) && + DEFINE_CLASS_DESCRIPTOR(NUMBER_EXTENT) && + DEFINE_CLASS_DESCRIPTOR(NUMBER_SET) && + DEFINE_CLASS_DESCRIPTOR(SECRUN) && + DEFINE_CLASS_DESCRIPTOR(SUPERAREA) && + DEFINE_CLASS_DESCRIPTOR(VOL_LIODPDRV)) { + + return TRUE; + + } + else { + + DebugPrint("Could not initialize class descriptors!"); + return FALSE; + } +} + +BOOLEAN +IfsutilUndefineClassDescriptors( +) +{ + UNDEFINE_CLASS_DESCRIPTOR(DP_DRIVE); + UNDEFINE_CLASS_DESCRIPTOR(DRIVE); + UNDEFINE_CLASS_DESCRIPTOR(DRIVE_CACHE); + UNDEFINE_CLASS_DESCRIPTOR(INTSTACK); + UNDEFINE_CLASS_DESCRIPTOR(IO_DP_DRIVE); + UNDEFINE_CLASS_DESCRIPTOR(LOG_IO_DP_DRIVE); + UNDEFINE_CLASS_DESCRIPTOR(NUMBER_EXTENT); + UNDEFINE_CLASS_DESCRIPTOR(NUMBER_SET); + UNDEFINE_CLASS_DESCRIPTOR(SECRUN); + UNDEFINE_CLASS_DESCRIPTOR(SUPERAREA); + UNDEFINE_CLASS_DESCRIPTOR(VOL_LIODPDRV); + return TRUE; +} diff --git a/ifsutil/src/intstack.cxx b/ifsutil/src/intstack.cxx new file mode 100644 index 0000000..717d4b2 --- /dev/null +++ b/ifsutil/src/intstack.cxx @@ -0,0 +1,231 @@ +#include "stdafx.h" + + + + + + +#include "ulib.hxx" + + +#include "intstack.hxx" + + +DEFINE_CONSTRUCTOR( INTSTACK, OBJECT ); + +VOID +INTSTACK::Construct ( + ) +/*++ + +Routine Description: + + Constructor for INTSTACK. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _stack = NULL; + _size = 0; +} + + + +INTSTACK::~INTSTACK( + ) +/*++ + +Routine Description: + + Destructor for INTSTACK. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + + +BOOLEAN +INTSTACK::Initialize( + ) +/*++ + +Routine Description: + + This routine initializes the stack for new input. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + Destroy(); + return TRUE; +} + + + +BOOLEAN +INTSTACK::Push( + IN BIG_INT Data + ) +/*++ + +Routine Description: + + This routine pushes 'Data' on the stack. + +Arguments: + + Data - Supplies the integer to push on the stack. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + PINTNODE p; + + p = _stack; + if (!(_stack = NEW INTNODE)) { + _stack = p; + return FALSE; + } + + _stack->Next = p; + _stack->Data = Data; + _size++; + + return TRUE; +} + + +VOID +INTSTACK::Pop( + IN ULONG HowMany + ) +/*++ + +Routine Description: + + This routine attempts to remove 'HowMany' elements from the top of + the stack. If there are not that many to remove then all that + can be removed, will be removed and FALSE will be returned. + +Arguments: + + HowMany - Supplies the number of elements to remove from the top of the + stack. + +Return Value: + + None. + +--*/ +{ + PINTNODE p; + + for (; HowMany; HowMany--) { + + DebugAssert(_stack); + + p = _stack->Next; + DELETE( _stack ); + _stack = p; + _size--; + } +} + + +BIG_INT +INTSTACK::Look( + IN ULONG Index + ) CONST +/*++ + +Routine Description: + + This routine returns the 'Index'th element of the stack. Index 0 denotes + the top of the stack. Index 1 denotes one element from the top of the + stack and so on. If the stack is smaller than the element requested then + this routine will return 0. This is not a limitation since 'QuerySize' + will return the depth of the stack. + +Arguments: + + Index - Supplies the index of the data requested. + +Return Value: + + The value of the stack element at position 'Index' or 0. + +--*/ +{ + PINTNODE p; + + p = _stack; + for (; Index; Index--) { + p = p ? p->Next : NULL; + } + + if (!p) { + return 0; + } + + return p->Data; +} + + +VOID +INTSTACK::Destroy( + ) +/*++ + +Routine Description: + + This routine returns the INTSTACK to its initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + PINTNODE p; + + while (_stack) { + p = _stack->Next; + DELETE( _stack ); + _stack = p; + } + _size = 0; +} + diff --git a/ifsutil/src/numset.cxx b/ifsutil/src/numset.cxx new file mode 100644 index 0000000..3ac95dd --- /dev/null +++ b/ifsutil/src/numset.cxx @@ -0,0 +1,638 @@ +#include "stdafx.h" + + +#include "ulib.hxx" + + +#include "numset.hxx" +#include "iterator.hxx" + +DEFINE_CONSTRUCTOR( NUMBER_SET, OBJECT ); + +DEFINE_CONSTRUCTOR( NUMBER_EXTENT, OBJECT ); + +VOID +NUMBER_SET::Construct ( + ) +/*++ + +Routine Description: + + Constructor for NUMBER_SET. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _card = 0; + _iterator = NULL; +} + + +VOID +NUMBER_SET::Destroy( + ) +/*++ + +Routine Description: + + This routine returns the NUMBER_SET to its initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _list.DeleteAllMembers(); + _card = 0; + DELETE(_iterator); +} + + + +NUMBER_SET::~NUMBER_SET( + ) +/*++ + +Routine Description: + + Destructor for NUMBER_SET. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + + +BOOLEAN +NUMBER_SET::Initialize( + ) +/*++ + +Routine Description: + + This routine initializes the stack for new input. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + Destroy(); + + if (!_list.Initialize() || + !(_iterator = _list.QueryIterator())) { + + Destroy(); + return FALSE; + } + + return TRUE; +} + + +BOOLEAN +NUMBER_SET::Add( + IN BIG_INT Number + ) +/*++ + +Routine Description: + + This routine adds 'Number' to the set. + +Arguments: + + Number - Supplies the number to add to the set. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + PNUMBER_EXTENT p, pn; + PNUMBER_EXTENT new_extent; + BIG_INT next; + + DebugAssert(_iterator); + + _iterator->Reset(); + while (p = (PNUMBER_EXTENT) _iterator->GetPrevious()) { + if (p->Start <= Number) { + break; + } + } + + if (p) { + + next = p->Start + p->Length; + + if (Number < next) { + return TRUE; + } + + if (Number == next) { + + p->Length += 1; + _card += 1; + + if (pn = (PNUMBER_EXTENT) _iterator->GetNext()) { + + if (pn->Start == Number + 1) { + + p->Length += pn->Length; + pn = (PNUMBER_EXTENT) _list.Remove(_iterator); + DELETE(pn); + } + } + + return TRUE; + } + } + + if (p = (PNUMBER_EXTENT) _iterator->GetNext()) { + + if (Number + 1 == p->Start) { + + p->Start = Number; + p->Length += 1; + _card += 1; + return TRUE; + } + } + + if (!(new_extent = NEW NUMBER_EXTENT)) { + return FALSE; + } + + new_extent->Start = Number; + new_extent->Length = 1; + + if (!_list.Insert(new_extent, _iterator)) { + DELETE(new_extent); + return FALSE; + } + + _card += 1; + + return TRUE; +} + +BOOLEAN +NUMBER_SET::Add( + IN BIG_INT Start, + IN BIG_INT Length + ) +/*++ + +Routine Description: + + This routine adds the range of numbers to the set. + +Arguments: + + Start - Supplies the first number to add to the set. + Length - Supplies the length of the run to add. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + BIG_INT i, sup; + BOOLEAN r; + + sup = Start + Length; + + r = TRUE; + for (i = Start; i < sup; i += 1) { + r = Add(i) && r; + } + + return r; +} + + +BOOLEAN +NUMBER_SET::Remove( + IN BIG_INT Number + ) +/*++ + +Routine Description: + + This routine removes a number from the number set. + +Arguments: + + Number - Supplies the number to remove. + +Routine Description: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + PNUMBER_EXTENT p; + PNUMBER_EXTENT new_extent; + BIG_INT next, new_length; + + DebugAssert(_iterator); + + _iterator->Reset(); + while (p = (PNUMBER_EXTENT) _iterator->GetNext()) { + if (p->Start > Number) { + break; + } + } + + if (p = (PNUMBER_EXTENT) _iterator->GetPrevious()) { + + next = p->Start + p->Length; + + if (p->Start == Number) { + p->Start += 1; + p->Length -= 1; + _card -= 1; + + if (p->Length == 0) { + p = (PNUMBER_EXTENT) _list.Remove(_iterator); + DELETE(p); + } + + return TRUE; + } + + if (Number + 1 == next) { + p->Length -= 1; + _card -= 1; + return TRUE; + } + + if (Number < next) { + + if (!(new_extent = NEW NUMBER_EXTENT)) { + return FALSE; + } + + _iterator->GetNext(); + + if (!_list.Insert(new_extent, _iterator)) { + DELETE(new_extent); + return FALSE; + } + + new_length = Number - p->Start; + + new_extent->Start = Number + 1; + new_extent->Length = p->Length - 1 - new_length; + + p->Length = new_length; + + _card -= 1; + } + } + + return TRUE; +} + + +BOOLEAN +NUMBER_SET::RemoveAll( + ) +{ + PNUMBER_EXTENT p; + + DebugAssert(_iterator); + + _iterator->Reset(); + if ((p = (PNUMBER_EXTENT) _iterator->GetNext())) + do { + p = (PNUMBER_EXTENT) _list.Remove(_iterator); + DELETE(p); + } while ((p=(PNUMBER_EXTENT)_iterator->GetCurrent())); + _card = 0; + return TRUE; +} + + +BOOLEAN +NUMBER_SET::CheckAndRemove( + IN BIG_INT Number, + OUT PBOOLEAN DoesExists + ) +/*++ + +Routine Description: + + This routine removes a number from the number set. + +Arguments: + + Number - Supplies the number to remove. + DoesExists - TRUE if Number was found in the set + +Routine Description: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + PNUMBER_EXTENT p; + PNUMBER_EXTENT new_extent; + BIG_INT next, new_length; + + DebugAssert(_iterator); + DebugAssert(DoesExists); + + *DoesExists = FALSE; + + _iterator->Reset(); + while (p = (PNUMBER_EXTENT) _iterator->GetNext()) { + if (p->Start > Number) { + break; + } + } + + if (p = (PNUMBER_EXTENT) _iterator->GetPrevious()) { + + next = p->Start + p->Length; + + if (p->Start == Number) { + p->Start += 1; + p->Length -= 1; + _card -= 1; + *DoesExists = TRUE; + + if (p->Length == 0) { + p = (PNUMBER_EXTENT) _list.Remove(_iterator); + DELETE(p); + } + + return TRUE; + } + + if (Number + 1 == next) { + p->Length -= 1; + _card -= 1; + *DoesExists = TRUE; + return TRUE; + } + + if (Number < next) { + + if (!(new_extent = NEW NUMBER_EXTENT)) { + return FALSE; + } + + _iterator->GetNext(); + + if (!_list.Insert(new_extent, _iterator)) { + DELETE(new_extent); + return FALSE; + } + + new_length = Number - p->Start; + + new_extent->Start = Number + 1; + new_extent->Length = p->Length - 1 - new_length; + + p->Length = new_length; + + _card -= 1; + *DoesExists = TRUE; + } + } + + return TRUE; +} + + + + +BIG_INT +NUMBER_SET::QueryNumber( + IN BIG_INT Index + ) CONST +/*++ + +Routine Description: + + This routine returns the Nth number contained in this set. + +Arguments: + + Index - Supplies a zero-based index into the ordered set. + +Return Value: + + The Nth number in this set. + +--*/ +{ + PNUMBER_EXTENT p; + BIG_INT r; + BIG_INT count; + + DebugAssert(Index < _card); + + _iterator->Reset(); + count = 0; + while (p = (PNUMBER_EXTENT) _iterator->GetNext()) { + + count += p->Length; + + if (count > Index) { + break; + } + } + + DebugAssert(p); + + return p->Start + Index - (count - p->Length); +} + + + +BOOLEAN +NUMBER_SET::DoesIntersectSet( + IN BIG_INT Start, + IN BIG_INT Length + ) CONST +/*++ + +Routine Description: + + This routine computes whether or not the range specified intersects + the current number set. This routine will return FALSE if the + intersection is empty, TRUE otherwise. + +Arguments: + + Start - Supplies the start of the range. + Length - Supplies the length of the range. + +Return Value: + + FALSE - The specified range does not intersect the number set. + TRUE - The specified range makes a non-empty intersection with + the number set. + +--*/ +{ + PNUMBER_EXTENT p; + BIG_INT pnext, next; + + DebugAssert(_iterator); + + if (Length == 0) { + return FALSE; + } + + next = Start + Length; + + _iterator->Reset(); + while (p = (PNUMBER_EXTENT) _iterator->GetNext()) { + + pnext = p->Start + p->Length; + + if (Start >= p->Start) { + + if (Start < pnext) { + return TRUE; + } + } else { + + if (next > p->Start) { + return TRUE; + } + } + } + + return FALSE; +} + + + +VOID +NUMBER_SET::QueryDisjointRange( + IN ULONG Index, + OUT PBIG_INT Start, + OUT PBIG_INT Length + ) CONST +/*++ + +Routine Description: + + This routine returns the 'Index'th disjoint range. (This is zero + based). + +Arguments: + + Index - Supplies the index of the disjoint range. + Start - Returns the start of the disjoint range. + Length - Returns the length of the disjoint range. + +Return Value: + + None. + +--*/ +{ + ULONG i; + PNUMBER_EXTENT p=NULL; + + DebugAssert(_iterator); + + _iterator->Reset(); + for (i = 0; i <= Index; i++) { + p = (PNUMBER_EXTENT) _iterator->GetNext(); + } + + DebugAssert(p); + DebugAssert(Start); + DebugAssert(Length); + + *Start = p->Start; + *Length = p->Length; +} + + + +BOOLEAN +NUMBER_SET::QueryContainingRange( + IN BIG_INT Number, + OUT PBIG_INT Start, + OUT PBIG_INT Length + ) CONST +/*++ + +Routine Description: + + This routine returns the range that contains the given number. + +Arguments: + + Number - Supplies the number. + Start - Returns the start of the range. + Length - Returns the length of the range. + +Return Value: + + FALSE - The given number was not in the set. + TRUE - Success. + +--*/ +{ + PNUMBER_EXTENT p; + + DebugAssert(_iterator); + + _iterator->Reset(); + while (p = (PNUMBER_EXTENT) _iterator->GetPrevious()) { + if (p->Start <= Number) { + break; + } + } + + if (!p || Number >= p->Start + p->Length) { + return FALSE; + } + + *Start = p->Start; + *Length = p->Length; + + return TRUE; +} diff --git a/ifsutil/src/secrun.cxx b/ifsutil/src/secrun.cxx new file mode 100644 index 0000000..c36b144 --- /dev/null +++ b/ifsutil/src/secrun.cxx @@ -0,0 +1,196 @@ +#include "stdafx.h" + + + + + + +#include "ulib.hxx" + + +#include "secrun.hxx" + + +DEFINE_CONSTRUCTOR( SECRUN, OBJECT ); + + +VOID +SECRUN::Construct ( + ) + +/*++ + +Routine Description: + + Constructor for class SECRUN. This function initializes the + member variables to "dummy" states. The member function 'Init' + must be called to make this class "work". + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _buf = NULL; + _drive = NULL; + _start_sector = 0; + _num_sectors = 0; +} + + +VOID +SECRUN::Destroy( + ) +/*++ + +Routine Description: + + This routine puts the object back into its initial and empty state. + It is not necessary to call this function between calls to 'Init' + as Init will call this function automatically. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _buf = NULL; + _drive = NULL; + _start_sector = 0; + _num_sectors = 0; +} + + + +SECRUN::~SECRUN( + ) +/*++ + +Routine Description: + + Destructor of sector run object. Returns references. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + + +BOOLEAN +SECRUN::Initialize( + IN OUT PMEM Mem, + IN OUT PIO_DP_DRIVE Drive, + IN BIG_INT StartSector, + IN SECTORCOUNT NumSectors + ) +/*++ + +Routine Description: + + This member function initializes the class so that reads and writes + may take place. + +Arguments: + + Mem - Supplies means by which to acquire sufficient memory. + Drive - Supplies drive interface. + StartSector - Supplies starting LBN. + NumSector - Supplies the number of LBNs. + +Return Value: + + TRUE - Success. + FALSE - Failure. + +--*/ +{ + ULONG size; + + Destroy(); + + DebugAssert(Drive); + DebugAssert(Mem); + + _drive = Drive; + _start_sector = StartSector; + _num_sectors = NumSectors; + size = _num_sectors*_drive->QuerySectorSize(); + _buf = Mem->Acquire(size, _drive->QueryAlignmentMask()); + + if (!size || !_buf) { + return FALSE; + } + + return TRUE; +} + + + +BOOLEAN +SECRUN::Read( + ) +/*++ + +Routine Description: + + This member function reads the sectors on disk into memory. + +Arguments: + + None. + +Return Value: + + TRUE - Success. + FALSE - Failure. + +--*/ +{ + DebugAssert(_buf); + return _drive->Read(_start_sector, _num_sectors, _buf); +} + + + +BOOLEAN +SECRUN::Write( + ) +/*++ + +Routine Description: + + This member function writes onto the disk. + +Arguments: + + None. + +Return Value: + + TRUE - Success. + FALSE - Failure. + +--*/ +{ + DebugAssert(_buf); + return _drive->Write(_start_sector, _num_sectors, _buf); +} diff --git a/ifsutil/src/supera.cxx b/ifsutil/src/supera.cxx new file mode 100644 index 0000000..be6e0e2 --- /dev/null +++ b/ifsutil/src/supera.cxx @@ -0,0 +1,130 @@ +#include "stdafx.h" + + + +#include "ulib.hxx" + + +#include "supera.hxx" +#include "message.hxx" + +#include "ifssys.hxx" + + +DEFINE_CONSTRUCTOR( SUPERAREA, SECRUN ); + + +SUPERAREA::~SUPERAREA( + ) +/*++ + +Routine Description: + + Destructor for SUPERAREA. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + +VOID +SUPERAREA::Construct( + ) +/*++ + +Routine Description: + + Constructor for SUPERAREA. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _drive = NULL; +} + + +VOID +SUPERAREA::Destroy( + ) +/*++ + +Routine Description: + + This routine returns the object to its initial state freeing up + any memory in the process. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _drive = NULL; +} + + + +BOOLEAN +SUPERAREA::Initialize( + IN OUT PMEM Mem, + IN OUT PLOG_IO_DP_DRIVE Drive, + IN SECTORCOUNT NumberOfSectors, + IN OUT PMESSAGE Message +) +/*++ + +Routine Description: + + This routine initializes the SUPERAREA for the given drive. + +Arguments: + + Mem - Supplies necessary memory for the underlying sector run. + Drive - Supplies the drive where the superarea resides. + NumberOfSectors - Supplies the number of sectors in the superarea. + Message - Supplies an outlet for messages. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + Destroy(); + + DebugAssert(Mem); + DebugAssert(Drive); + DebugAssert(NumberOfSectors); + + if (!SECRUN::Initialize(Mem, Drive, 0, NumberOfSectors)) + { + Message->Out("Insufficient memory."); + return FALSE; + } + + _drive = Drive; + + return TRUE; +} + diff --git a/ifsutil/src/volume.cxx b/ifsutil/src/volume.cxx new file mode 100644 index 0000000..b666b38 --- /dev/null +++ b/ifsutil/src/volume.cxx @@ -0,0 +1,182 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + volume.cxx + +Abstract: + + Provides volume methods. + +--*/ + + +#include "ulib.hxx" + + +#include "volume.hxx" +#include "supera.hxx" +#include "hmem.hxx" +#include "message.hxx" + +#include "ifsentry.hxx" + +#include "path.hxx" + + +DEFINE_CONSTRUCTOR( VOL_LIODPDRV, LOG_IO_DP_DRIVE ); + +VOID +VOL_LIODPDRV::Construct ( + ) +/*++ + +Routine Description: + + Constructor for VOL_LIODPDRV. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _sa = NULL; +} + + +VOL_LIODPDRV::~VOL_LIODPDRV( + ) +/*++ + +Routine Description: + + Destructor for VOL_LIODPDRV. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + +VOID +VOL_LIODPDRV::Destroy( + ) +/*++ + +Routine Description: + + This routine returns a VOL_LIODPDRV to its initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _sa = NULL; +} + + + +FORMAT_ERROR_CODE +VOL_LIODPDRV::Initialize( + IN PCWSTRING NtDriveName, + IN PSUPERAREA SuperArea, + IN OUT PMESSAGE Message + ) +/*++ + +Routine Description: + + This routine initializes a VOL_LIODPDRV to a valid state. + +Arguments: + + NtDriveName - Supplies the drive path for the volume. + SuperArea - Supplies the superarea for the volume. + Message - Supplies an outlet for messages. + ExclusiveWrite - Supplies whether or not the drive should be + opened for exclusive write. + FormatMedia - Supplies whether or not to format the media. + MediaType - Supplies the type of media to format to. + FormatType - Supplies the file system type in the event of a format + ForceDismount - Supplies whether the drive should be dismounted + and locked + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + Destroy(); + + DebugAssert(NtDriveName); + DebugAssert(SuperArea); + + if (!LOG_IO_DP_DRIVE::Initialize(NtDriveName, Message)) + { + return GeneralError; + } + + if (!_bad_sectors.Initialize()) { + return GeneralError; + } + + _sa = SuperArea; + + + if (QueryMediaType() == Unknown) + { + Message ? Message->Out("Disk is not formatted.") : 1; + return GeneralError; + } + + + if (QueryMediaType() != Unknown && QuerySectors() == 0) + { + if (Message) { + Message->Out("Cannot determine the number of sectors on this volume."); + } else { + DebugPrint("Sectors is 0"); + } + return GeneralError; + } + + return NoError; +} + + +BOOLEAN +VOL_LIODPDRV::MarkBad( + IN __int64 firstPhysicalDriveSector, + IN __int64 lastPhysicalDriveSector, + IN OUT PMESSAGE Message +) +{ + if (!_sa) { + return FALSE; + } + + return _sa->MarkBad(firstPhysicalDriveSector, lastPhysicalDriveSector, Message); +} \ No newline at end of file diff --git a/lib_x32/ntdll.lib b/lib_x32/ntdll.lib new file mode 100644 index 0000000000000000000000000000000000000000..02a0a080a0a43b6409a4aba6233fca1f8e261132 GIT binary patch literal 523922 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
BlkR_nALZV`P)y|3-E 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 literal 0 HcmV?d00001 diff --git a/lib_x64/ntdll.lib b/lib_x64/ntdll.lib new file mode 100644 index 0000000000000000000000000000000000000000..c9691eefab6c954f7a90fe1375713b5eafa8e4ed GIT binary patch literal 498012 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 0) +#endif + +// +// Generic test for error on any status value. +// + +#ifndef NT_ERROR +#define NT_ERROR(Status) ((((ULONG)(Status)) >> 30) == 3) +#endif + +#ifndef InitializeObjectAttributes +#define InitializeObjectAttributes( p, n, a, r, s ) { \ + (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \ + (p)->RootDirectory = r; \ + (p)->Attributes = a; \ + (p)->ObjectName = n; \ + (p)->SecurityDescriptor = s; \ + (p)->SecurityQualityOfService = NULL; \ + } +#endif + +// +// Valid values for the Attributes field +// + +#define OBJ_CASE_INSENSITIVE 0x00000040L + +// +// Define the create/open option flags +// + +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 + + + +typedef struct _IO_STATUS_BLOCK { +#pragma warning(push) +#pragma warning(disable: 4201) + union { + NTSTATUS Status; + PVOID Pointer; + } DUMMYUNIONNAME; +#pragma warning(pop) + + ULONG_PTR Information; +} IO_STATUS_BLOCK, * PIO_STATUS_BLOCK; + + + +typedef +VOID +(NTAPI* PIO_APC_ROUTINE) ( + IN PVOID ApcContext, + IN PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG Reserved + ); + +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; + } DUMMYUNIONNAME; +} REPARSE_DATA_BUFFER, * PREPARSE_DATA_BUFFER; + + +//================ FileAlignmentInformation =================================== + +typedef struct _FILE_ALIGNMENT_INFORMATION { + ULONG AlignmentRequirement; +} FILE_ALIGNMENT_INFORMATION, * PFILE_ALIGNMENT_INFORMATION; + + + +typedef struct _FILE_FS_DEVICE_INFORMATION { + DEVICE_TYPE DeviceType; + ULONG Characteristics; +} FILE_FS_DEVICE_INFORMATION, * PFILE_FS_DEVICE_INFORMATION; + + +typedef enum _FSINFOCLASS { + FileFsVolumeInformation = 1, + FileFsLabelInformation, // 2 + FileFsSizeInformation, // 3 + FileFsDeviceInformation, // 4 + FileFsAttributeInformation, // 5 + FileFsControlInformation, // 6 + FileFsFullSizeInformation, // 7 + FileFsObjectIdInformation, // 8 + FileFsDriverPathInformation, // 9 + FileFsVolumeFlagsInformation, // 10 + FileFsSectorSizeInformation, // 11 + FileFsDataCopyInformation, // 12 + FileFsMetadataSizeInformation, // 13 + FileFsFullSizeInformationEx, // 14 + FileFsMaximumInformation +} FS_INFORMATION_CLASS, * PFS_INFORMATION_CLASS; + + + +// +// Define the file information class values +// +// WARNING: The order of the following values are assumed by the I/O system. +// Any changes made here should be reflected there as well. +// + +typedef enum _FILE_INFORMATION_CLASS { + FileDirectoryInformation = 1, + FileFullDirectoryInformation, // 2 + FileBothDirectoryInformation, // 3 + FileBasicInformation, // 4 + FileStandardInformation, // 5 + FileInternalInformation, // 6 + FileEaInformation, // 7 + FileAccessInformation, // 8 + FileNameInformation, // 9 + FileRenameInformation, // 10 + FileLinkInformation, // 11 + FileNamesInformation, // 12 + FileDispositionInformation, // 13 + FilePositionInformation, // 14 + FileFullEaInformation, // 15 + FileModeInformation, // 16 + FileAlignmentInformation, // 17 + FileAllInformation, // 18 + FileAllocationInformation, // 19 + FileEndOfFileInformation, // 20 + FileAlternateNameInformation, // 21 + FileStreamInformation, // 22 + FilePipeInformation, // 23 + FilePipeLocalInformation, // 24 + FilePipeRemoteInformation, // 25 + FileMailslotQueryInformation, // 26 + FileMailslotSetInformation, // 27 + FileCompressionInformation, // 28 + FileObjectIdInformation, // 29 + FileCompletionInformation, // 30 + FileMoveClusterInformation, // 31 + FileQuotaInformation, // 32 + FileReparsePointInformation, // 33 + FileNetworkOpenInformation, // 34 + FileAttributeTagInformation, // 35 + FileTrackingInformation, // 36 + FileIdBothDirectoryInformation, // 37 + FileIdFullDirectoryInformation, // 38 + FileValidDataLengthInformation, // 39 + FileShortNameInformation, // 40 + FileIoCompletionNotificationInformation, // 41 + FileIoStatusBlockRangeInformation, // 42 + FileIoPriorityHintInformation, // 43 + FileSfioReserveInformation, // 44 + FileSfioVolumeInformation, // 45 + FileHardLinkInformation, // 46 + FileProcessIdsUsingFileInformation, // 47 + FileNormalizedNameInformation, // 48 + FileNetworkPhysicalNameInformation, // 49 + FileIdGlobalTxDirectoryInformation, // 50 + FileIsRemoteDeviceInformation, // 51 + FileUnusedInformation, // 52 + FileNumaNodeInformation, // 53 + FileStandardLinkInformation, // 54 + FileRemoteProtocolInformation, // 55 + + // + // These are special versions of these operations (defined earlier) + // which can be used by kernel mode drivers only to bypass security + // access checks for Rename and HardLink operations. These operations + // are only recognized by the IOManager, a file system should never + // receive these. + // + + FileRenameInformationBypassAccessCheck, // 56 + FileLinkInformationBypassAccessCheck, // 57 + + // + // End of special information classes reserved for IOManager. + // + + FileVolumeNameInformation, // 58 + FileIdInformation, // 59 + FileIdExtdDirectoryInformation, // 60 + FileReplaceCompletionInformation, // 61 + FileHardLinkFullIdInformation, // 62 + FileIdExtdBothDirectoryInformation, // 63 + FileDispositionInformationEx, // 64 + FileRenameInformationEx, // 65 + FileRenameInformationExBypassAccessCheck, // 66 + FileDesiredStorageClassInformation, // 67 + FileStatInformation, // 68 + FileMemoryPartitionInformation, // 69 + FileStatLxInformation, // 70 + FileCaseSensitiveInformation, // 71 + FileLinkInformationEx, // 72 + FileLinkInformationExBypassAccessCheck, // 73 + FileStorageReserveIdInformation, // 74 + FileCaseSensitiveInformationForceAccessCheck, // 75 + + FileMaximumInformation +} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS; + + + +// +// Interrupt Request Level (IRQL) +// +typedef UCHAR KIRQL; + + + +// +// These data structures and type definitions are needed for compilation and +// use of the internal Windows APIs defined in this header. +// + + +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING; +typedef UNICODE_STRING* PUNICODE_STRING; + + +typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} OBJECT_ATTRIBUTES; +typedef OBJECT_ATTRIBUTES* POBJECT_ATTRIBUTES; + + +// +// These functions are intended for use by internal core Windows components +// since these functions may change between Windows releases. +// + +#define RtlMoveMemory(Destination,Source,Length) memmove((Destination),(Source),(Length)) + + + + +#ifdef __cplusplus +extern "C" { +#endif + + + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReadFile( + IN HANDLE FileHandle, + IN HANDLE Event, + IN PIO_APC_ROUTINE ApcRoutine, + IN PVOID ApcContext, + OUT PIO_STATUS_BLOCK IoStatusBlock, + OUT PVOID Buffer, + IN ULONG Length, + IN PLARGE_INTEGER ByteOffset, + IN PULONG Key); + + + +NTSYSAPI +NTSTATUS +NTAPI +RtlOemToUnicodeN( + OUT PWCH UnicodeString, + IN ULONG MaxBytesInUnicodeString, + OUT PULONG BytesInUnicodeString, + IN PCCH OemString, + IN ULONG BytesInOemString +); + + +NTSYSAPI +NTSTATUS +NTAPI +RtlUnicodeToOemN( + OUT PCHAR OemString, + IN ULONG MaxBytesInOemString, + OUT PULONG BytesInOemString, + IN PCWCH UnicodeString, + IN ULONG BytesInUnicodeString +); + + +NTSYSAPI +NTSTATUS +NTAPI +RtlDecompressBuffer( + OUT USHORT CompressionFormat, + OUT PUCHAR UncompressedBuffer, + OUT ULONG UncompressedBufferSize, + IN PUCHAR CompressedBuffer, + OUT ULONG CompressedBufferSize, + OUT PULONG FinalUncompressedSize +); + + +NTSYSAPI +NTSTATUS +NTAPI +NtFsControlFile( + OUT HANDLE FileHandle, + IN HANDLE Event, + IN PIO_APC_ROUTINE ApcRoutine, + IN PVOID ApcContext, + OUT PIO_STATUS_BLOCK IoStatusBlock, + OUT ULONG FsControlCode, + IN PVOID InputBuffer, + OUT ULONG InputBufferLength, + OUT PVOID OutputBuffer, + OUT ULONG OutputBufferLength +); + + + +NTSYSAPI +NTSTATUS +NTAPI +NtQueryVolumeInformationFile( + OUT HANDLE FileHandle, + OUT PIO_STATUS_BLOCK IoStatusBlock, + OUT PVOID FsInformation, + OUT ULONG Length, + OUT FS_INFORMATION_CLASS FsInformationClass +); + + +NTSYSAPI +NTSTATUS +NTAPI +NtWriteFile( + OUT HANDLE FileHandle, + IN HANDLE Event, + IN PIO_APC_ROUTINE ApcRoutine, + IN PVOID ApcContext, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PVOID Buffer, + OUT ULONG Length, + IN PLARGE_INTEGER ByteOffset, + IN PULONG Key +); + + + + +NTSYSAPI +NTSTATUS +NTAPI +NtQueryInformationFile( + OUT HANDLE FileHandle, + OUT PIO_STATUS_BLOCK IoStatusBlock, + OUT PVOID FileInformation, + OUT ULONG Length, + OUT FILE_INFORMATION_CLASS FileInformationClass +); + + +NTSYSAPI +VOID +NTAPI +RtlRaiseStatus( + IN NTSTATUS Status +); + + + + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetThreadExecutionState( + IN EXECUTION_STATE esFlags, // ES_xxx flags + OUT EXECUTION_STATE *PreviousFlags + ); + + +// +// use the Win32 API instead +// CloseHandle +// +NTSTATUS + NTAPI + NtClose( + IN HANDLE Handle + ); + + +// +// use the Win32 API instead +// CreateFile +// +NTSTATUS + NTAPI + NtOpenFile( + OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG ShareAccess, + IN ULONG OpenOptions + ); + + + + +// +// use the Win32 API instead +// DeviceIoControl +// +NTSTATUS + NTAPI + NtDeviceIoControlFile( + IN HANDLE FileHandle, + IN HANDLE Event OPTIONAL, + IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, + IN PVOID ApcContext OPTIONAL, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG IoControlCode, + IN PVOID InputBuffer OPTIONAL, + IN ULONG InputBufferLength, + OUT PVOID OutputBuffer OPTIONAL, + IN ULONG OutputBufferLength + ); + +#ifdef __cplusplus +} +#endif + + + + + + + diff --git a/stdafx.cpp b/stdafx.cpp new file mode 100644 index 0000000..daae43e --- /dev/null +++ b/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// test1.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/stdafx.h b/stdafx.h new file mode 100644 index 0000000..caa5610 --- /dev/null +++ b/stdafx.h @@ -0,0 +1,13 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + + + +#include "common.h" + + +// TODO: reference additional headers your program requires here diff --git a/ulib/inc/array.hxx b/ulib/inc/array.hxx new file mode 100644 index 0000000..b9afc29 --- /dev/null +++ b/ulib/inc/array.hxx @@ -0,0 +1,354 @@ +/*++ + +Module Name: + + array.hxx + +Abstract: + + This module contains the declaration for the ARRAY class. + + ARRAY is a concrete implementation of a dynamic (i.e. growable) array + derived from the abstract class SORTABLE_CONTAINER. + + ARRAY's support one 'writer' (i.e. one client poutting OBJECTs into + the ARRAY) and multiple readers via an ITERATOR. It is the client's + responsibility to synchronize multiple writers. + + The ARRAY does not contain holes, i.e. all elements of the array (up + to QueryMemberCount() ) have objects. + +--*/ + +#pragma once + +#include "sortcnt.hxx" + +// +// Forward references +// +DECLARE_CLASS( ARRAY ); +DECLARE_CLASS( ARRAY_ITERATOR ); + +// +// Pointer to array of POBJECTs +// +typedef POBJECT* PPOBJECT; + +// +// Default values for an ARRAY object. +// +// - Capacity is the total number of elements that can be stored in an ARRAY +// - CapacityIncrement is the number of elemnts that the ARRAY's Capacity +// will be increased by when it's Capacity is exceeded +// +CONST ULONG DefaultCapacity = 50; +CONST ULONG DefaultCapacityIncrement = 25; + +// +// Invalid index within the array +// +CONST ULONG INVALID_INDEX = (ULONG)(-1); + + +class ARRAY : public SORTABLE_CONTAINER { + + friend ARRAY_ITERATOR; + + public: + + + DECLARE_CONSTRUCTOR( ARRAY ); + + DECLARE_CAST_MEMBER_FUNCTION( ARRAY ); + + VIRTUAL + ~ARRAY( + ); + + + BOOLEAN + Initialize ( + IN ULONG Capacity DEFAULT DefaultCapacity, + IN ULONG CapacityIncrement DEFAULT DefaultCapacityIncrement + ); + + VIRTUAL + BOOLEAN + DeleteAllMembers( + ); + + VIRTUAL + POBJECT + GetAt( + IN ULONG Index + ) CONST; + + VIRTUAL + ULONG + GetMemberIndex( + IN POBJECT Object + ) CONST; + + VIRTUAL + BOOLEAN + PutAt( + IN OUT POBJECT Member, + IN ULONG Index + ); + + + ULONG + QueryCapacity ( + ) CONST; + + + ULONG + QueryCapacityIncrement ( + ) CONST; + + VIRTUAL + + PITERATOR + QueryIterator( + ) CONST; + + VIRTUAL + ULONG + QueryMemberCount( + ) CONST; + + VIRTUAL + + POBJECT + Remove( + IN OUT PITERATOR Position + ); + + VIRTUAL + POBJECT + RemoveAt( + IN ULONG Index + ); + + + ULONG + SetCapacity ( + IN ULONG Capacity + ); + + + VOID + SetCapacityIncrement ( + IN ULONG CapacityIncrement + ); + + + BOOLEAN + Insert( + IN OUT POBJECT Member, + IN ULONG Index + ); + + + protected: + + STATIC + LONG + CompareAscDesc( + IN POBJECT Object1, + IN POBJECT Object2, + IN BOOLEAN Ascending DEFAULT TRUE + ); + + + VOID + Construct ( + ); + + + PPOBJECT + GetObjectArray ( + ); + + private: + + + ULONG + SetArrayCapacity( + IN ULONG NumberOfElements + ); + + STATIC + int __cdecl + CompareAscending ( + IN const void * Object1, + IN const void * Object2 + ); + + STATIC + int __cdecl + CompareDescending ( + IN const void * Object1, + IN const void * Object2 + ); + + PPOBJECT _ObjectArray; // Array of pointers to OBJECTs + ULONG _PutIndex; // Put Index + ULONG _Capacity; // Capacity of the array + ULONG _CapacityIncrement; // Increment + +#if DBG==1 + ULONG _IteratorCount; // Count of iterators +#endif + +}; + + +INLINE +ULONG +ARRAY::QueryCapacity ( + ) CONST + +/*++ + +Routine Description: + + Return the current capacity (maximum number of members) of the ARRAY. + +Arguments: + + None. + +Return Value: + + ULONG - Current capacity. + + +--*/ + +{ + return( _Capacity ); +} + + + +INLINE +ULONG +ARRAY::QueryCapacityIncrement ( + ) CONST + +/*++ + +Routine Description: + + Return the current capacity increment (realloc amount) of the ARRAY. + +Arguments: + + None. + +Return Value: + + ULONG - Current capacity increment. + +--*/ + +{ + return( _CapacityIncrement ); +} + + + +INLINE +VOID +ARRAY::SetCapacityIncrement ( + IN ULONG CapacityIncrement + ) + +/*++ + +Routine Description: + + Set the capacity incement value. + +Arguments: + + CapacityIncrement - Supplies the new value for the capacity increment. + +Return Value: + + None. + +--*/ + +{ + _CapacityIncrement = CapacityIncrement; +} + + + +INLINE +LONG +ARRAY::CompareAscDesc( + IN POBJECT Object1, + IN POBJECT Object2, + IN BOOLEAN Ascending + ) +/*++ + +Routine Description: + + Compares two object accordint to an Ascending flag + +Arguments: + + Object1 - Supplies first object + Object2 - Supplies second object + Ascending - Supplies ascending flag + +Return Value: + + LONG - If Ascending: + <0 if Object1 is less that Object2 + 0 if Object1 is equal to Object2 + >0 if Object1 is greater than Object2 + + If !Ascending: + <0 if Object2 is less that Object1 + 0 if Object2 is equal to Object1 + >0 if Object2 is greater than Object1 + + +--*/ +{ + return ( Ascending ? CompareAscending( &Object1, &Object2 ) : + CompareDescending( &Object1, &Object2) ); +} + + + +INLINE +PPOBJECT +ARRAY::GetObjectArray ( + ) +/*++ + +Routine Description: + + Obtains pointer to the array of objects + +Arguments: + + None + +Return Value: + + PPOBJECT - Pointer to array of objects + +--*/ + +{ + return _ObjectArray; +} + diff --git a/ulib/inc/arrayit.hxx b/ulib/inc/arrayit.hxx new file mode 100644 index 0000000..3ff8cab --- /dev/null +++ b/ulib/inc/arrayit.hxx @@ -0,0 +1,112 @@ +/*++ + +Module Name: + + arrayit.hxx + +Abstract: + + This module contains the declaration for the ARRAY_ITERATOR class. + ARRAY_ITERATOR is a concrete implementation derived from the abstarct + ITERATOR class. It is used to 'read' (iterate) over an ARRAY object. + It has no public constructor and therefore can only be queried from an + ARRAY object. It is the client's responsibility to detsroy + ARRAY_ITERATORs when they are done. ARRAY_ITERATORs maintain the currency + for reading an ARRAY in order. + +--*/ + +#pragma once + +#include "iterator.hxx" + +// +// Forward references +// + +DECLARE_CLASS( ARRAY ); +DECLARE_CLASS( ARRAY_ITERATOR ); + + +class ARRAY_ITERATOR : public ITERATOR { + + friend ARRAY; + + public: + + VIRTUAL + ~ARRAY_ITERATOR( + ); + + VIRTUAL + VOID + Reset( + ); + + VIRTUAL + POBJECT + GetCurrent( + ); + + VIRTUAL + POBJECT + GetNext( + ); + + VIRTUAL + POBJECT + GetPrevious( + ); + + + ULONG + QueryCurrentIndex( + ); + + protected: + + DECLARE_CAST_MEMBER_FUNCTION( ARRAY_ITERATOR ); + DECLARE_CONSTRUCTOR( ARRAY_ITERATOR ); + + + BOOLEAN + Initialize( + IN OUT PARRAY Array + ); + + + VOID + Construct( + ); + + private: + + PARRAY _Array; // Array + ULONG _CurrentIndex; // Current index + +}; + + +INLINE +ULONG +ARRAY_ITERATOR::QueryCurrentIndex( + ) +/*++ + +Routine Description: + + Obtains the current index + +Arguments: + + None + +Return Value: + + ULONG - The current index + +--*/ + +{ + return _CurrentIndex; +} diff --git a/ulib/inc/bitvect.hxx b/ulib/inc/bitvect.hxx new file mode 100644 index 0000000..8d2670c --- /dev/null +++ b/ulib/inc/bitvect.hxx @@ -0,0 +1,432 @@ +/*++ + +Module Name: + + bitvect.hxx + +Abstract: + + This module contains the definition for the BITVECTOR class. BITVECTOR + is a concrete class which implements a bit array. + +Notes: + + BITVECTOR is an implementation of a general purpose bit + vector. It is based around an abstract type called PT + (Primitive Type) to increase it's portability. BITVECTORs + can be created of arbitrary size. If required they will + allocate memory to maintain the bits or will use a memory + buffer supplied by the client. This allows the client to + superimpose a bit vector onto a predefined buffer (e.g. onto + a SECBUF). + +--*/ +#if ! defined ( _BITVECTOR_ ) + +#define _BITVECTOR_ + +DECLARE_CLASS( BITVECTOR ); + +// +// BITVECTOR allocation increment - Primitive Type +// + +DEFINE_TYPE( ULONG, PT ); + +// +// BIT values +// + +enum BIT { + SET = 1, + RESET = 0 +}; + +// +// Invalid bit count +// + +extern CONST PT InvalidBitCount; + +class BITVECTOR : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( BITVECTOR ); + + DECLARE_CAST_MEMBER_FUNCTION( BITVECTOR ); + + + ~BITVECTOR ( + ); + + + BOOLEAN + Initialize ( + IN PT Size DEFAULT 0, + IN BIT InitialValue DEFAULT RESET, + IN PPT Memory DEFAULT NULL + ); + + + PCPT + GetBuf ( + ) CONST; + + + BOOLEAN + IsBitSet ( + IN PT Index + ) CONST; + + + PT + QueryCountSet ( + ) CONST; + + + PT + QuerySize ( + ) CONST; + + + VOID + ResetAll ( + ); + + + VOID + ResetBit ( + IN PT Index + ); + + + + VOID + ResetBit ( + IN PT Index, + IN PT Count + ); + + + VOID + SetAll ( + ); + + + VOID + SetBit ( + IN PT Index + ); + + + + VOID + SetBit ( + IN PT Index, + IN PT Count + ); + + + + PT + SetSize ( + IN PT Size, + IN BIT InitialValue DEFAULT RESET + ); + + + VOID + ToggleBit ( + IN PT Index, + IN PT Count DEFAULT 1 + ); + + private: + + + VOID + Construct ( + ); + + + + PT + ComputeCountSet ( + ) CONST; + + + VOID + Destroy ( + ); + + VOID + + InitAll ( + IN PT InitialValue + ); + + // Note that these three member data items cannot + // be static because they are used in INLINE functions + // that are called from outside the DLL. + + PT _BitsPerPT; + PT _IndexShiftCount; + PT _BitPositionMask; + + STATIC + CONST + BYTE _BitsSetLookUp[ 256 ]; + + PPT _BitVector; + PT _PTCount; + BOOLEAN _FreeBitVector; + +}; + +INLINE +PCPT +BITVECTOR::GetBuf ( + ) CONST + +/*++ + +Routine Description: + + Returns the underlying bit vector. + +Arguments: + + None. + +Return Value: + + PCPT - Returns a pointer to the underlying bitvector. + +--*/ + +{ + return( _BitVector ); +} + +INLINE +PT +BITVECTOR::QuerySize ( + ) CONST + +/*++ + +Routine Description: + + Returns the number of bits in this BITVECTOR. + +Arguments: + + None. + +Return Value: + + PT + +--*/ + +{ + return( _PTCount * _BitsPerPT ); +} + +INLINE +VOID +BITVECTOR::InitAll ( + IN PT InitialValue + ) + +/*++ + +Routine Description: + + Initialize each element in the internal bit vector to the + supplied bit pattern. + +Arguments: + + InitialValue - Supplies the pattern for each element. + +Return Value: + + None. + +--*/ + +{ + DebugAssert( _BitVector != NULL ); + + memset(( PVOID ) _BitVector, + ( int ) InitialValue, ( size_t ) ( sizeof( PT ) * _PTCount )); +} + +INLINE +VOID +BITVECTOR::SetAll ( + ) + +/*++ + +Routine Description: + + Set (to one) all of the bits in this BITVECTOR. + +Arguments: + + None. + +Return Value: + + None. + +--*/ + +{ + InitAll(( PT ) ~0 ); +} + +INLINE +VOID +BITVECTOR::ResetAll ( + ) + +/*++ + +Routine Description: + + Reset (to zero) all of the bits in this BITVECTOR. + +Arguments: + + None. + +Return Value: + + None. + +--*/ + +{ + InitAll(( PT ) 0 ); +} + +INLINE +BOOLEAN +BITVECTOR::IsBitSet ( + IN PT Index + ) CONST + +/*++ + +Routine Description: + + Determine if the bit at the supplied index is set. + +Arguments: + + Index - Supplies the index to the bit of interest. + +Return Value: + + BOOLEAN - Returns TRUE if the bit of interest is set. + +--*/ + +{ + DebugAssert( _BitVector != NULL ); + DebugAssert( Index < ( _PTCount * _BitsPerPT )); + + return (_BitVector[Index >> _IndexShiftCount] & + (1 << (Index & _BitPositionMask))) ? TRUE : FALSE; +} + +INLINE +VOID +BITVECTOR::SetBit ( + IN PT Index + ) + +/*++ + +Routine Description: + + Set the bit at the supplied index. + +Arguments: + + Index - Supplies the index to the bit of interest. + +Return Value: + + None. + +--*/ + +{ + DebugAssert( _BitVector != NULL ); + DebugAssert( Index < ( _PTCount * _BitsPerPT )); + + _BitVector[Index >> _IndexShiftCount] |= + (1 << (Index & _BitPositionMask)); +} + +INLINE +VOID +BITVECTOR::ResetBit ( + IN PT Index + ) + +/*++ + +Routine Description: + + Reset the bit at the supplied index. + +Arguments: + + Index - Supplies the index to the bit of interest. + +Return Value: + + None. + +--*/ + +{ + DebugAssert( _BitVector != NULL ); + DebugAssert( Index < ( _PTCount * _BitsPerPT )); + + _BitVector[Index >> _IndexShiftCount] &= + ~(1 << (Index & _BitPositionMask)); +} + +INLINE +PT +BITVECTOR::QueryCountSet ( + ) CONST + +/*++ + +Routine Description: + + Determine the number of bits set in this BITVECTOR. + +Arguments: + + None. + +Return Value: + + PT - Returns the number of set bits. + +--*/ +{ + return ComputeCountSet(); +} + +#endif // _BITVECTOR_ diff --git a/ulib/inc/clasdesc.hxx b/ulib/inc/clasdesc.hxx new file mode 100644 index 0000000..400ed91 --- /dev/null +++ b/ulib/inc/clasdesc.hxx @@ -0,0 +1,111 @@ +/*++ + +Module Name: + + clasdesc.hxx + +Abstract: + + This module contains the declaration for the CLASS_DESCRIPTOR class. + A CLASS_DESCRIPTOR contains informatiom to help identify an object's + type at run-time. It is a concrete class which is associated with all + other concrete classes (i.e. there is one instance of a CLASS_DESCRIPTOR + for every concrete class in Ulib). The most important aspect of a + CLASS_DESCRIPTOR is that it supplies a guaranteed unique id for the class + that it decsribes. + +--*/ + +#pragma once + +// +// Forward references +// + +DECLARE_CLASS( CLASS_DESCRIPTOR ); + + +#define UNDEFINE_CLASS_DESCRIPTOR( c ) delete (PCLASS_DESCRIPTOR) c##_cd, c##_cd = 0 + + +#define DEFINE_CLASS_DESCRIPTOR( c ) \ + ((( c##_cd = NEW CLASS_DESCRIPTOR ) != NULL ) && \ + ((( PCLASS_DESCRIPTOR ) c##_cd )->Initialize( ))) + + #define CLASS_DESCRIPTOR_INITIALIZE \ + \ + BOOLEAN \ + Initialize ( \ + ) + + #define DECLARE_CD_DBG_DATA \ + static char d + + #define DECLARE_CD_DBG_FUNCTIONS \ + void f(void){} + + #define DEFINE_CD_INLINE_DBG_FUNCTIONS \ + inline void f(void){} + + #define DbgGetClassName( pobj ) + + +class CLASS_DESCRIPTOR { + + friend + BOOLEAN + InitializeUlib( + IN HANDLE DllHandle, + IN ULONG Reason, + IN PVOID Reserved + ); + +public: + + CLASS_DESCRIPTOR( + ); + + CLASS_DESCRIPTOR_INITIALIZE; + + CLASS_ID + QueryClassId( + ) CONST; + + DECLARE_CD_DBG_FUNCTIONS; + +private: + + DECLARE_CD_DBG_DATA; + + CLASS_ID _ClassID; +}; + + +INLINE +CLASS_ID +CLASS_DESCRIPTOR::QueryClassId ( + ) CONST + +/*++ + +Routine Description: + + Return the CLASSID for the object described by this CLASS_DESCRIPTOR. + +Arguments: + + None. + +Return Value: + + CLASSID - The CLASSID as maintained by this CLASS_DESCRIPTOR. + +--*/ + +{ + return( _ClassID ); +} + + +DEFINE_CD_INLINE_DBG_FUNCTIONS; + diff --git a/ulib/inc/contain.hxx b/ulib/inc/contain.hxx new file mode 100644 index 0000000..ba2b012 --- /dev/null +++ b/ulib/inc/contain.hxx @@ -0,0 +1,35 @@ +/*++ + +Module Name: + + contain.hxx + +Abstract: + + This module contains the definition for the CONTAINER class, the most + primitive, abstract class in the container sub-hierarchy. CONTAINERs + of all types are repositories for OBJECTs. CONTAINER is the most abstract + in that it makes no assumptions about the ordering of it's contents. + +--*/ + +#pragma once + +DECLARE_CLASS( CONTAINER ); + +class CONTAINER : public OBJECT { + + public: + + VIRTUAL + ~CONTAINER( + ); + + protected: + + DECLARE_CONSTRUCTOR( CONTAINER ); + +}; + + + diff --git a/ulib/inc/cstring.h b/ulib/inc/cstring.h new file mode 100644 index 0000000..226d68c --- /dev/null +++ b/ulib/inc/cstring.h @@ -0,0 +1,166 @@ +/*++ + +Module Name: + + wstring.h + +Abstract: + + This module contains the prototypes for the wide character + C-runtime support. Since there is no C-runtime support for + wide characters, the functions here are wrappers to the + single-byte counterparts + +--*/ + +typedef char wchar; +typedef WCHAR wchar_t; +typedef size_t wsize_t; + +long watol( const wchar *); +wchar * wcschr(const wchar *, int); +wchar * wcslwr(wchar *); +wchar * wcsrchr(const wchar *, int); +wchar * wcsupr(wchar *); +wsize_t wcscspn(const wchar *, const wchar *); +wsize_t wcsspn(const wchar *, const wchar *); +wchar * wcsstr(const wchar *, const wchar *); +int wctomb( char *s, wchar_t wchar ); +int mbtowc(wchar_t *pwc, const char *s, size_t n); +wchar_t towupper( wchar_t wc); + +INLINE +long +watol( + const wchar * p + ) +{ + return atol( (char *)p ); +} + + +INLINE +wchar * +wcschr ( + const wchar * p, + int c + ) +{ + return (wchar *)strchr( (char *)p, c); +} + + +INLINE +wchar * +wcslwr ( + wchar * p + ) +{ + return (wchar *)strlwr( (char *)p ); +} + +INLINE +wchar * +wcsrchr ( + const wchar * p, + int c + ) +{ + return (char *)strrchr( (char *)p, c); +} + +INLINE +wchar * +wcsupr ( + wchar * p + ) +{ + return (char *)strupr( (char *)p ); +} + + +INLINE +wsize_t +wcscspn ( + const wchar *p1, + const wchar *p2 + ) +{ + + return (wsize_t)strcspn( (char *)p1, (char *)p2); + +} + +INLINE +wsize_t +wcsspn ( + const wchar *p1, + const wchar *p2 + ) +{ + + return (wsize_t)strspn( (char *)p1, (char *)p2); + +} + + +INLINE +wchar * +wcsstr ( + const wchar *p1, + const wchar *p2 + ) +{ + + return (wchar *)strstr( (char *)p1, (char *)p2); + +} + + +INLINE +int +wctomb ( + char *s, + wchar_t wchar + ) +{ + + if (s) { + *s = (char)wchar; + return 1; + } else { + return 0; + } +} + + +INLINE +int +mbtowc ( + wchar_t *pwc, + const char *s, + size_t n + ) +{ + UNREFERENCED_PARAMETER( n ); + + if ( s && *s && (n > 0) ) { + *pwc = (wchar_t)(*s); + return 1; + } else { + return 0; + } + +} + + +INLINE +wchar_t +towupper( + wchar_t wc + ) +{ + + return (wchar_t)toupper( (char)wc ); + +} diff --git a/ulib/inc/hmem.hxx b/ulib/inc/hmem.hxx new file mode 100644 index 0000000..3ce7dfe --- /dev/null +++ b/ulib/inc/hmem.hxx @@ -0,0 +1,137 @@ +/*++ + +Module Name: + + hmem.hxx + +Abstract: + + The class HMEM is an implementation of the class MEM which uses the + memory resources of the heap. + + After the first call to Acquire that succeeds, all successive calls + will return the same memory that was returned by the first call + provided that the size requested is within the bounds of the first call. + The common buffer which was created upon the first successful call to + Acquire will be available along with its size by calling GetBuf and + QuerySize. + + Calling Destroy will put the object back in its initial state thus + invalidating any pointers to its memory and enabling future calls + to Acquire to succeed regardless of the size specicified. + +--*/ + +#pragma once + +#include "mem.hxx" + +DECLARE_CLASS( HMEM ); + + +class HMEM : public MEM { + + public: + + + DECLARE_CONSTRUCTOR( HMEM ); + + VIRTUAL + ~HMEM( + ); + + + BOOLEAN + Initialize( + ); + + VIRTUAL + PVOID + Acquire( + IN ULONG Size, + IN ULONG AlignmentMask DEFAULT 0 + ); + + + PVOID + GetBuf( + ) CONST; + + + ULONG + QuerySize( + ) CONST; + + + BOOLEAN + Resize( + IN ULONG NewSize, + IN ULONG AlignmentMask DEFAULT 0 + ); + + private: + + + VOID + Construct ( + ); + + + VOID + Destroy( + ); + + ULONG _size; + PVOID _real_buf; + PVOID _buf; + +}; + + +INLINE +PVOID +HMEM::GetBuf( + ) CONST +/*++ + +Routine Description: + + This routine returns the memory that was previously 'Acquired'. + +Arguments: + + None. + +Return Value: + + A pointer to the beginning of the memory buffer. + +--*/ +{ + return _buf; +} + + +INLINE +ULONG +HMEM::QuerySize( + ) CONST +/*++ + +Routine Description: + + This routine returns the size of the memory that was previously + 'Acquired'. + +Arguments: + + None. + +Return Value: + + The size of the memory returned by 'GetBuf'. + +--*/ +{ + return _size; +} diff --git a/ulib/inc/ifsentry.hxx b/ulib/inc/ifsentry.hxx new file mode 100644 index 0000000..9e024a7 --- /dev/null +++ b/ulib/inc/ifsentry.hxx @@ -0,0 +1,28 @@ +/*++ + +Module Name: + + ifsentry.hxx + +Abstract: + + Contains prototypes for entry points to the IFS + utility DLLs. + +--*/ + +#pragma once + +// +// Definition of DRIVE_TYPE +// +enum DRIVE_TYPE { + UnknownDrive, + RemovableDrive, + FixedDrive, + RemoteDrive, + CdRomDrive, + RamDiskDrive +}; + + diff --git a/ulib/inc/iterator.hxx b/ulib/inc/iterator.hxx new file mode 100644 index 0000000..7b52465 --- /dev/null +++ b/ulib/inc/iterator.hxx @@ -0,0 +1,60 @@ +/*++ + +Module Name: + + iterator.hxx + +Abstract: + + This module contains the declaration for the abstract ITERATOR class. + ITERATORS are used to iterate over a CONTAINER allowing for multiple, + simultaneous readers. ITERATORs maintain the currency needed to perform + an iteration. This includes the current OBJECT in the CONTAINER and the + currency needed to get the next or previous OBJECT. ITERATORs also + provide the capability of wrapping when the end or begin of the container + is reached. + +--*/ + +#pragma once + +DECLARE_CLASS( ITERATOR ); + + +class ITERATOR : public OBJECT { + + public: + + VIRTUAL + ~ITERATOR( + ); + + + VIRTUAL + POBJECT + GetCurrent( + ) PURE; + + VIRTUAL + POBJECT + GetNext( + ) PURE; + + VIRTUAL + POBJECT + GetPrevious( + ) PURE; + + VIRTUAL + VOID + Reset( + ) PURE; + + + protected: + + DECLARE_CONSTRUCTOR( ITERATOR ); + +}; + + diff --git a/ulib/inc/list.hxx b/ulib/inc/list.hxx new file mode 100644 index 0000000..f5853e8 --- /dev/null +++ b/ulib/inc/list.hxx @@ -0,0 +1,100 @@ +/*++ + +Module Name: + + list.hxx + +Abstract: + + This class is an implementation of the SEQUENTIAL_CONTAINER + protocol. The specific implementation is that of a doubly + linked list. + +--*/ + +#pragma once + +#include "seqcnt.hxx" +#include "membmgr.hxx" + + +struct OBJECT_LIST_NODE { + OBJECT_LIST_NODE* next; + OBJECT_LIST_NODE* prev; + POBJECT data; +}; + +DEFINE_POINTER_AND_REFERENCE_TYPES( OBJECT_LIST_NODE ); + +DECLARE_CLASS( LIST ); + +class LIST : public SEQUENTIAL_CONTAINER { + + FRIEND class LIST_ITERATOR; + + public: + + DECLARE_CONSTRUCTOR( LIST ); + + VIRTUAL + + ~LIST( + ); + + + BOOLEAN + Initialize( + ); + + VIRTUAL + ULONG + QueryMemberCount( + ) CONST; + + VIRTUAL + + BOOLEAN + Put( + IN OUT POBJECT Member + ); + + VIRTUAL + POBJECT + Remove( + IN OUT PITERATOR Position + ); + + VIRTUAL + + PITERATOR + QueryIterator( + ) CONST; + + + BOOLEAN + Insert( + IN OUT POBJECT Member, + IN OUT PITERATOR Position + ); + + private: + + POBJECT_LIST_NODE _head; + POBJECT_LIST_NODE _tail; + ULONG _count; + MEM_BLOCK_MGR _mem_block_mgr; + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + +}; + + + diff --git a/ulib/inc/listit.hxx b/ulib/inc/listit.hxx new file mode 100644 index 0000000..868892c --- /dev/null +++ b/ulib/inc/listit.hxx @@ -0,0 +1,105 @@ +/*++ + +Module Name: + + listit.hxx + +Abstract: + + This is an implementation of iterator for LIST. + +--*/ + +#pragma once + +#include "iterator.hxx" +#include "list.hxx" + +DECLARE_CLASS( LIST_ITERATOR ); + +class LIST_ITERATOR : public ITERATOR { + + FRIEND class LIST; + + public: + + DECLARE_CONSTRUCTOR( LIST_ITERATOR ); + + DECLARE_CAST_MEMBER_FUNCTION( LIST_ITERATOR ); + + + BOOLEAN + Initialize( + IN PCLIST List + ); + + VIRTUAL + VOID + Reset( + ); + + VIRTUAL + POBJECT + GetCurrent( + ); + + VIRTUAL + POBJECT + GetNext( + ); + + VIRTUAL + POBJECT + GetPrevious( + ); + + private: + + POBJECT_LIST_NODE _current; + PCLIST _list; + + + VOID + Construct( + ); + +}; + + +INLINE +VOID +LIST_ITERATOR::Construct( + ) +/*++ + +Routine Description: + + This routine resets LIST_ITERATOR. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _current = NULL; + _list = NULL; +} + + +INLINE +BOOLEAN +LIST_ITERATOR::Initialize( + IN PCLIST List + ) +{ + DebugAssert(List); + _list = List; + return TRUE; +} + + diff --git a/ulib/inc/mem.hxx b/ulib/inc/mem.hxx new file mode 100644 index 0000000..b54bbd5 --- /dev/null +++ b/ulib/inc/mem.hxx @@ -0,0 +1,40 @@ +/*++ + +Module Name: + + mem.hxx + +Abstract: + + The class MEM contains one pure virtual function named 'Acquire'. + The implementors and users of this class must follow some simple + constraints. + + Acquire will return a pointer to Size bytes of memory or NULL. + + A function taking a MEM as an argument should call Acquire at most one + time. It should not, for instance, cache a pointer to the MEM for + future calls to Acquire. + +--*/ + +#pragma once + +DECLARE_CLASS( MEM ); + +class MEM : public OBJECT { + + public: + + VIRTUAL + PVOID + Acquire( + IN ULONG Size, + IN ULONG AlignmentMask DEFAULT 0 + ) PURE; + + protected: + + DECLARE_CONSTRUCTOR( MEM ); + +}; diff --git a/ulib/inc/membmgr.hxx b/ulib/inc/membmgr.hxx new file mode 100644 index 0000000..b4a19fa --- /dev/null +++ b/ulib/inc/membmgr.hxx @@ -0,0 +1,220 @@ +/*++ + +Module Name: + + membmgr.hxx + +Abstract: + + This class offers two classes for the management of fixed + size blocks of memory. The first class STATIC_MEM_BLOCK_MGR + allows the user to allocate and free fixed size memory blocks + up to the specified limit that the class was initialized to. + + The second class MEM_BLOCK_MGR offers a scheme for memory block + management that will grow as the clients needs increase. + +--*/ + +#pragma once + + +#include "array.hxx" +#include "bitvect.hxx" + + +DECLARE_CLASS( STATIC_MEM_BLOCK_MGR ); + +class STATIC_MEM_BLOCK_MGR : public OBJECT { + + public: + + DECLARE_CONSTRUCTOR( STATIC_MEM_BLOCK_MGR ); + + VIRTUAL + ~STATIC_MEM_BLOCK_MGR( + ); + + + BOOLEAN + Initialize( + IN ULONG MemBlockSize, + IN ULONG NumBlocks DEFAULT 128 + ); + + + PVOID + Alloc( + ); + + + BOOLEAN + Free( + OUT PVOID MemBlock + ); + + + ULONG + QueryBlockSize( + ) CONST; + + + ULONG + QueryNumBlocks( + ) CONST; + + private: + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + PCHAR _heap; + ULONG _num_blocks; + ULONG _block_size; + ULONG _num_allocated; + ULONG _next_alloc; + BITVECTOR _bitvector; + +}; + + +INLINE +ULONG +STATIC_MEM_BLOCK_MGR::QueryBlockSize( + ) CONST +/*++ + +Routine Description: + + This routine return the number of bytes in a block returned + by this object. + +Arguments: + + None. + +Return Value: + + The number of bytes per block. + +--*/ +{ + return _block_size; +} + + +INLINE +ULONG +STATIC_MEM_BLOCK_MGR::QueryNumBlocks( + ) CONST +/*++ + +Routine Description: + + This routine return the number of blocks contained + by this object. + +Arguments: + + None. + +Return Value: + + The number of blocks. + +--*/ +{ + return _num_blocks; +} + + + +DECLARE_CLASS( MEM_BLOCK_MGR ); + +class MEM_BLOCK_MGR : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( MEM_BLOCK_MGR ); + + VIRTUAL + + ~MEM_BLOCK_MGR( + ); + + + + BOOLEAN + Initialize( + IN ULONG MemBlockSize, + IN ULONG InitialNumBlocks DEFAULT 128 + ); + + + + PVOID + Alloc( + ); + + + + BOOLEAN + Free( + OUT PVOID MemBlock + ); + + + ULONG + QueryBlockSize( + ) CONST; + + private: + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + PSTATIC_MEM_BLOCK_MGR _static_mem_list[32]; + +}; + + +INLINE +ULONG +MEM_BLOCK_MGR::QueryBlockSize( + ) CONST +/*++ + +Routine Description: + + This routine return the number of bytes in a block returned + by this object. + +Arguments: + + None. + +Return Value: + + The number of bytes per block. + +--*/ +{ + return _static_mem_list[0] ? _static_mem_list[0]->QueryBlockSize() : 0; +} + + diff --git a/ulib/inc/membmgr2.hxx b/ulib/inc/membmgr2.hxx new file mode 100644 index 0000000..f35229a --- /dev/null +++ b/ulib/inc/membmgr2.hxx @@ -0,0 +1,75 @@ +/*++ + +Module Name: + + membmgr2.hxx + +Abstract: + + This module contains the declarations for the MEM_ALLOCATOR + class, which provides memory block of varialble size from + a bigger block. All bigger blocks are then linked together + thru a pointer at the end of each bigger block. This class + is particularly suitable to user who needs a lot of small + blocks but does not free any of them until there is no need + to use the object anymore. + +--*/ + +#pragma once + +// +// This class allocates a big buffer and then give a chunk of +// it away each time Allocate is called. Each big buffer +// is linked to the next one by having a pointer at the end +// of the big buffer. The last buffer should have a NULL +// pointer at the end of it. +// +class MEM_ALLOCATOR : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( MEM_ALLOCATOR ); + + VIRTUAL + + ~MEM_ALLOCATOR( + ); + + + + BOOLEAN + Initialize( + IN ULONG64 MaximumMemoryToUse DEFAULT -1, + IN ULONG IncrementalBlockSize DEFAULT 1024*1024 + ); + + + + PVOID + Allocate( + IN ULONG SizeInBytes + ); + + private: + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + PVOID _head_ptr; // points to the first big buffer + PVOID _next_free_ptr; // points to the free space of the current big buffer + ULONG _free_space_in_current_block; // amount of user space that's free in the + // current big buffer + ULONG _incremental_size; // how large is each big buffer + ULONG64 _max_mem_use; + ULONG64 _mem_use; +}; + diff --git a/ulib/inc/message.hxx b/ulib/inc/message.hxx new file mode 100644 index 0000000..1ad533a --- /dev/null +++ b/ulib/inc/message.hxx @@ -0,0 +1,63 @@ +/*++ + +Module Name: + + message.hxx + +Abstract: + + The MESSAGE class provides a implementation of a message displayer + class. Message displayers are meant to be used by applications to + relay information to the user. Many functions will require a 'MESSAGE' + parameter through which to relay their output. + +--*/ + +#pragma once + +#include "wstring.hxx" + + +DECLARE_CLASS( MESSAGE ); + +class MESSAGE : public OBJECT { + + public: + + DECLARE_CONSTRUCTOR(MESSAGE); + + VIRTUAL + ~MESSAGE( + ); + + void Out(const char* str); + void Out(const char* str1, const char* str2); + void Out(const char* str1, const char* str2, const char* str3); + void Out(const char* str, LONGLONG number); + void Out(const char* str1, LONGLONG number, const char* str2); + void Out(const char* str1, LONGLONG number1, const char* str2, LONGLONG number2, const char* str3); + void Out(const char* str1, LONGLONG number1, const char* str2, LONGLONG number2, const char* str3, LONGLONG number3, const char* str4); + + inline void OutIncorrectStructure() + { + Out("Incorrect file system structure found."); + } + + BOOLEAN + Initialize( + ); + + private: + + VOID + Construct( + ); + + + VOID + Destroy( + ); + +}; + + diff --git a/ulib/inc/object.hxx b/ulib/inc/object.hxx new file mode 100644 index 0000000..8870eaa --- /dev/null +++ b/ulib/inc/object.hxx @@ -0,0 +1,268 @@ +/*++ + +Module Name: + + Object.hxx + +Abstract: + + This module contains the class declaration for the OBJECT class, + the root of the Ulib hierarchy. OBJECT is a very important class as + it provides default (and sometimes ) implementations for + helping to identify and classify all other objects at run-time. This + capability allows CONTAINERs to manipulate OBJECTs rather than a given + derived class of objects. OBJECTs supply the most primitive support for + polymorphism within the library. Along with CLASS_DESCRIPTORs they supply + the ability to safely determine (Cast) if an OBJECT is of a given class + at run-time. + +--*/ + +#pragma once + +#include "clasdesc.hxx" + +DECLARE_CLASS( OBJECT ); + + + #define DECLARE_OBJECT_DBG_FUNCTIONS + + #define DEFINE_OBJECT_DBG_FUNCTIONS + + #define DbgDump( pobj ) + + #define DbgDumpAll( pobj ) + + #define DbgDumpDeep( pobj, flag ) + + +class OBJECT { + + public: + + VIRTUAL + ~OBJECT( + ); + + VIRTUAL + LONG + Compare( + IN PCOBJECT Object + ) CONST; + + + PCCLASS_DESCRIPTOR + GetClassDescriptor( + ) CONST; + + + BOOLEAN + IsSameClass( + IN PCOBJECT Object + ) CONST; + + + BOOLEAN + IsSameObject( + IN PCOBJECT Object + ) CONST; + + + CLASS_ID + QueryClassId( + ) CONST; + + DECLARE_OBJECT_DBG_FUNCTIONS + + protected: + + + OBJECT( + ); + + + VOID + Construct( + ); + + + VOID + SetClassDescriptor( + IN PCCLASS_DESCRIPTOR ClassDescriptor + ); + + private: + + PCCLASS_DESCRIPTOR _ClassDescriptor; + +}; + + +INLINE +VOID +OBJECT::Construct( + ) +{ +} + + +INLINE +CLASS_ID +OBJECT::QueryClassId ( + ) CONST + +/*++ + +Routine Description: + + Return the CLASSID for this object. + +Arguments: + + None. + +Return Value: + + CLASSID - The CLASSID as maintained by the object's CLASS_DESCRIPTOR. + +Notes: + + This function used to be at the end of clasdesac.hxx with th following + note: + + This member functions is for OBJECT, not for CLASS_DESCRIPTOR. It is + defined here because it requires CLASS_DESCRIPTOR to be defined in + order to make it inline. + +--*/ + +{ + DebugPtrAssert( _ClassDescriptor ); + + return _ClassDescriptor->QueryClassId(); +} + + +INLINE +VOID +OBJECT::SetClassDescriptor ( + IN PCCLASS_DESCRIPTOR ClassDescriptor + ) + +/*++ + +Routine Description: + + Set the CLASS_DESCRIPTOR for a derived class. + +Arguments: + + ClassDescriptor - Supplies a pointer to the derived class' + CLASS_DECRIPTOR. + +Return Value: + + None. + +Notes: + + This function should only be called by the DEFINE_CONSTRUCTOR macro. + +--*/ + +{ +// DebugPtrAssert( ClassDescriptor ); + _ClassDescriptor = ClassDescriptor; +} + + + +INLINE +PCCLASS_DESCRIPTOR +OBJECT::GetClassDescriptor ( + ) CONST + +/*++ + +Routine Description: + + Gain access to the object's CLASS_DESCRIPTOR. + +Arguments: + + None. + +Return Value: + + PCCLASS_DESCRIPTOR - A pointer to the CLASS_DESCRIPTOR asscoicated with + this object. + +--*/ + +{ + DebugPtrAssert( _ClassDescriptor ); + + return( _ClassDescriptor ); +} + + +INLINE +BOOLEAN +OBJECT::IsSameClass ( + IN PCOBJECT Object + ) CONST + +/*++ + +Routine Description: + + Determine if this object and the supplied object are of the same class + by checking their CLASS_IDs for equality. + +Arguments: + + Object - Supplies the object to compare class types against. + +Return Value: + + BOOLEAN - Returns TRUE if this object and the supplied object are of + the same class. + +--*/ + +{ + DebugPtrAssert( Object ); + + return (BOOLEAN) (QueryClassId() == Object->QueryClassId()); +} + + +INLINE +BOOLEAN +OBJECT::IsSameObject ( + IN PCOBJECT Object + ) CONST + +/*++ + +Routine Description: + + Determine if this object and the supplied object are the same by checking + their CLASS_IDs and then their memory location. + +Arguments: + + Object - Supplies the object to compare for equivalence. + +Return Value: + + BOOLEAN - Returns TRUE if this object and the supplied object are + equivalent. + +--*/ + +{ + return (BOOLEAN) (this == Object); +} + + diff --git a/ulib/inc/path.hxx b/ulib/inc/path.hxx new file mode 100644 index 0000000..69706b4 --- /dev/null +++ b/ulib/inc/path.hxx @@ -0,0 +1,419 @@ +/*++ + +Module Name: + + path.hxx + +Abstract: + + The PATH class provides an interface to the complete + Win32 name space. Complete means that it will correctly + handle long, drive or UNC based, blank embedded, mixed case + names. It should eliminate the need for everyone to code + statements such as "is the second char a ':'" or "search for + the first '\' from the end of the name". That is, access to and + manipulation of path and file names should be performed soley + through the PATH member functions. This will eliminate + the recoding of standard name manipulation code and ensure + complete support of Win32 functionality, such as codepage and + DBCS support. + + +Notes: + + + To clarify terminology used here, the following describes a + canonicalized path (in butchered BNF/reg exp): + + {Canon} ::= {Prefix}"\"{Name} + {Prefix} ::= {Device}{Dirs} + {Dirs} ::= {"\"{Component}}* + {Device} ::= {Drive}|{Machine} + {Drive} ::= {Letter}":" + {Machine} ::= "\\"{Char}+ + {Letter} ::= valid drive letter [a-zA-Z] + {Char} ::= valid filename/directory char [~:\] + {Component} ::= {Char}+ + {Name} ::= {Base - excluding "."} | { {Base}"."{Ext} } + {Base} ::= {Char}+ + {Ext} ::= {Char - excluding "."}+ + + Legend: + ------- + {x}* - 0 or more x + {x}+ - 1 or more x + "x" - just x (not the quotes) + {x}|{y} - x or y (not both or none) + + + Examples: + --------- + # Canon + --- ----- + (1) x:\abc\def.ghi\jkl.mnop + (2) \\x\abc\def.ghi\jkl.mnop + (3) c:\config.sys + + (1) (2) (3) + + Prefix x:\abc\def.ghi \\x\abc\def.ghi c: + Device x: \\x c: + Dirs \abc \abc\def.ghi + Name jkl.mnop jkl.mnop config.sys + Base jkl jkl config + Ext mnop mnop sys + + Component numbers are 0-based. + + +--*/ + +#pragma once + +#include "wstring.hxx" +#include "array.hxx" + +// +// Forward references & declarations +// + +DECLARE_CLASS( PATH ); + + + +// +// PATHSTATE maintains the number of characters within each component that +// makes up a PATH +// +struct _PATHSTATE { + + // + // Prefix + // + CHNUM PrefixLen; + CHNUM DeviceLen; + CHNUM DirsLen; + CHNUM SeparatorLen; + + // + // Name + // + CHNUM NameLen; + CHNUM BaseLen; + CHNUM ExtLen; + +}; + +DEFINE_TYPE( struct _PATHSTATE, PATHSTATE ); + +typedef enum PATH_ANALYZE_CODE { + PATH_OK, + PATH_COULD_BE_FLOPPY, + PATH_OUT_OF_MEMORY, + PATH_INVALID_DRIVE_SPEC, + PATH_NO_MOUNT_POINT_FOR_VOLUME_NAME_PATH +}; + +class PATH : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( PATH ); + + DECLARE_CAST_MEMBER_FUNCTION( PATH ); + + + BOOLEAN + Initialize ( + IN PCWSTR InitialPath, + IN BOOLEAN Canonicalize DEFAULT FALSE + ); + + + BOOLEAN + Initialize ( + IN PCWSTRING InitialPath, + IN BOOLEAN Canonicalize DEFAULT FALSE + ); + + + + BOOLEAN + Initialize ( + IN PCPATH InitialPath, + IN BOOLEAN Canonicalize DEFAULT FALSE + ); + + VIRTUAL + + ~PATH ( + ); + + + + BOOLEAN + AppendBase ( + IN PCWSTRING Base, + IN BOOLEAN Absolute DEFAULT FALSE + ); + + + + BOOLEAN + EndsWithDelimiter ( + ) CONST; + + + PCWSTRING + GetPathString ( + ) CONST; + + + + + PWSTRING + QueryBase ( + ); + + + + + PWSTRING + QueryDevice ( + ); + + + + PWSTRING + QueryExt ( + ); + + + + PPATH + QueryFullPath ( + ) CONST; + + + + PWSTRING + QueryFullPathString ( + ) CONST; + + + PWSTRING + QueryName ( + ) CONST; + + + + PPATH + QueryPath ( + ) CONST; + + + PWSTRING + QueryPrefix ( + ); + + + + PWSTRING + QueryRoot ( + ); + + + + BOOLEAN + SetBase ( + IN PCWSTRING NewBase + ); + + + BOOLEAN + SetDevice ( + IN PCWSTRING NewDevice + ); + + + BOOLEAN + SetExt ( + IN PCWSTRING NewExt + ); + + + + BOOLEAN + TruncateBase ( + ); + + + + + private: + + + VOID + Construct ( + ); + + + BOOLEAN + ExpandWildCards( + IN OUT PWSTRING pStr1, + IN OUT PWSTRING pStr2 + ); + + BOOLEAN + Initialize ( + ); + + + CHNUM + QueryBaseStart ( + ) CONST; + + CHNUM + QueryDeviceLen( + IN PWSTRING pString + ) CONST; + + + CHNUM + QueryDeviceStart ( + ) CONST; + + + CHNUM + QueryExtStart ( + ) CONST; + + + CHNUM + QueryNameStart ( + ) CONST; + + + CHNUM + QueryPrefixStart ( + ) CONST; + + + VOID + SetPathState ( + ); + + + // + // path data + // + WCHAR _PathBuffer[MAX_PATH]; + FSTRING _PathString; + PATHSTATE _PathState; + +}; + +INLINE +CHNUM +PATH::QueryPrefixStart ( + ) CONST + +{ + return( 0 ); +} + +INLINE +CHNUM +PATH::QueryNameStart ( + ) CONST + +{ + // + // Increment past the '\' + // + return( QueryPrefixStart() + _PathState.PrefixLen + _PathState.SeparatorLen ); +} + +INLINE +CHNUM +PATH::QueryBaseStart ( + ) CONST + +{ + return( QueryNameStart() ); +} + +INLINE +CHNUM +PATH::QueryDeviceStart ( + ) CONST + +{ + return( 0 ); +} + +INLINE +CHNUM +PATH::QueryExtStart ( + ) CONST +{ + return( QueryNameStart() + _PathState.BaseLen + 1 ); +} + +INLINE +PCWSTRING +PATH::GetPathString ( + ) CONST + +{ + return( &_PathString ); +} + +INLINE +PWSTRING +PATH::QueryBase ( + ) + +{ + return( _PathState.BaseLen ? _PathString.QueryString( QueryBaseStart(), _PathState.BaseLen ) : NULL ); +} + + +INLINE +PWSTRING +PATH::QueryDevice ( + ) + +{ + return( _PathState.DeviceLen ? _PathString.QueryString( QueryDeviceStart(), _PathState.DeviceLen ) : NULL ); +} + +INLINE +PWSTRING +PATH::QueryExt ( + ) + +{ + return( _PathState.ExtLen ? _PathString.QueryString( QueryExtStart(), _PathState.ExtLen ) : NULL ); +} + +INLINE +PWSTRING +PATH::QueryName ( + ) CONST + +{ + return( _PathState.NameLen ? _PathString.QueryString( QueryNameStart(), _PathState.NameLen ) : NULL ); +} + +INLINE +PWSTRING +PATH::QueryPrefix ( + ) + +{ + return( _PathState.PrefixLen ? _PathString.QueryString( QueryPrefixStart(), _PathState.PrefixLen ) : NULL ); +} + + diff --git a/ulib/inc/seqcnt.hxx b/ulib/inc/seqcnt.hxx new file mode 100644 index 0000000..82559dd --- /dev/null +++ b/ulib/inc/seqcnt.hxx @@ -0,0 +1,69 @@ +/*++ + +Module Name: + + seqcnt.hxx + +Abstract: + + This module contains the declaration for the SEQUENTIAL_CONTAINER class. + SEQUENTIAL_CONTAINER is a fairly primitive class which augments the + CONTAINER class by adding the capability that the objects stored in the + container have some sort of sequenced relationship. This means that + OBJECTs can be queried from SEQUENTIAL_CONTAINERs by the use of an + ITERATOR and that the concepts first, last, next and previous have + meaning. + +--*/ + +#pragma once + +#include "contain.hxx" + +DECLARE_CLASS( SEQUENTIAL_CONTAINER ); +DECLARE_CLASS( ITERATOR ); + +class SEQUENTIAL_CONTAINER : public CONTAINER { + + FRIEND class ITERATOR; + + public: + + VIRTUAL + ~SEQUENTIAL_CONTAINER( + ); + + VIRTUAL + BOOLEAN + Put( + IN OUT POBJECT Member + ) PURE; + + VIRTUAL + ULONG + QueryMemberCount( + ) CONST PURE; + + VIRTUAL + BOOLEAN + DeleteAllMembers( + ); + + VIRTUAL + PITERATOR + QueryIterator( + ) CONST PURE; + + VIRTUAL + POBJECT + Remove( + IN OUT PITERATOR Position + ) PURE; + + protected: + + DECLARE_CONSTRUCTOR( SEQUENTIAL_CONTAINER ); + +}; + + diff --git a/ulib/inc/sortcnt.hxx b/ulib/inc/sortcnt.hxx new file mode 100644 index 0000000..18ac94c --- /dev/null +++ b/ulib/inc/sortcnt.hxx @@ -0,0 +1,60 @@ +/*++ + +Module Name: + + sortcnt.hxx + +Abstract: + + This module contains the declaration for the SORTABLE_CONTAINER class. + SORTABLE_CONTAINER is an abstract classe that is derived from the abstract + class SEQUENTIAL_CONTAINER. It not only assumes a sequence but also + assumes that the sequence can be changed by sorting it's contents. That + is the contents have a relative order independent from how they were + placed in the container. + +--*/ + +#pragma once + +#include "seqcnt.hxx" + +DECLARE_CLASS( SORTABLE_CONTAINER ); + +class SORTABLE_CONTAINER : public SEQUENTIAL_CONTAINER { + + public: + + VIRTUAL + ~SORTABLE_CONTAINER( + ); + + VIRTUAL + BOOLEAN + Put( + IN OUT POBJECT Member + ) PURE; + + VIRTUAL + ULONG + QueryMemberCount( + ) CONST PURE; + + VIRTUAL + PITERATOR + QueryIterator( + ) CONST PURE; + + VIRTUAL + POBJECT + Remove( + IN OUT PITERATOR Position + ) PURE; + + protected: + + DECLARE_CONSTRUCTOR( SORTABLE_CONTAINER ); + +}; + + diff --git a/ulib/inc/stack.hxx b/ulib/inc/stack.hxx new file mode 100644 index 0000000..c491afa --- /dev/null +++ b/ulib/inc/stack.hxx @@ -0,0 +1,220 @@ +/*++ + +Module Name: + + stack.hxx + +Abstract: + + This module contains the declaration for the concrete STACK class. + STACK is derived from the ORDERED_CONTAINER class. It implements a + standard FIFO stack data structure with the addition that it has a last + position which can be queried. STACKs are dynamic in that they will + grow rather than overflow. + +--*/ + +#pragma once + +#include "seqcnt.hxx" + +// +// Forward references +// + +DECLARE_CLASS( ARRAY ); +DECLARE_CLASS( ITERATOR ); +DECLARE_CLASS( STACK ); + + +// +// Default values for an ARRAY object. +// + +CONST ULONG DefaultCapacity = 50; +CONST ULONG DefaultCapacityIncrement = 25; + +class STACK : public SEQUENTIAL_CONTAINER { + + public: + + DECLARE_CONSTRUCTOR( STACK ); + + DECLARE_CAST_MEMBER_FUNCTION( STACK ); + + + VOID + Clear ( + ); + + VIRTUAL + PCOBJECT + GetFirst ( + ) CONST PURE; + + VIRTUAL + PCOBJECT + GetLast ( + ) CONST PURE; + + + BOOLEAN + Initialize ( + IN ULONG Capacity DEFAULT DefaultCapacity, + IN ULONG CapacityIncrement DEFAULT DefaultCapacityIncrement + ); + + + PCOBJECT + Pop ( + ); + + + BOOLEAN + Push ( + IN PCOBJECT Object + ); + + VIRTUAL + PCOBJECT + Put ( + IN PCOBJECT Member + ) PURE; + + VIRTUAL + PITERATOR + QueryIterator ( + ) CONST PURE; + + VIRTUAL + PCOBJECT + Remove ( + IN PCOBJECT Member + ) PURE; + + + PCOBJECT + Top ( + ) CONST; + + private: + + PARRAY _Stack; + ULONG _Top; +}; + +INLINE +PCOBJECT +STACK::GetFirst ( + ) CONST + +{ + return( Top( )); +} + +INLINE +PCOBJECT +STACK::GetLast ( + ) CONST + +{ + DebugPtrAssert( _Stack ); + if( _Stack != NULL ) { + return( _Stack->GetLast( )); + } else { + return( NULL ); + } +} + +INLINE +VOID +STACK::Clear ( + ) + +{ + _Top = 0; +} + +INLINE +PCOBJECT +STACK::Pop ( + ) + +{ + DebugPtrAssert( _Stack ); + if( ( _Stack != NULL ) && ( _Top != 0 )) { + return( _Stack->GetAt( _Top-- )); + } else { + return( NULL ); + } +} + +INLINE +BOOLEAN +STACK::Push ( + IN PCOBJECT Object + ) + +{ + DebugPtrAssert( _Stack ); + if( _Stack != NULL ) { + return( _Stack->PutAt( ++_Top )); + } else { + return( NULL ); + } +} + +INLINE +PCOBJECT +STACK::Put ( + IN PCOBJECT Member + ) + +{ + DebugPtrAssert( Member ); + return( Push( Member )); +} + +INLINE +PITERATOR +STACK::QueryIterator ( + ) CONST + +{ + DebugPtrAssert( _Stack ); + if( _Stack != NULL ) { + return( _Stack->QueryIterator( )); + } else { + return( NULL ); + } +} + +INLINE +PCOBJECT +STACK::Remove ( + IN PCOBJECT Member + ) PURE + +{ + DebugPtrAssert( Member ).IsEqual( Top( )); + if( ( Member != NULL ) && Member.IsEqual( Top( )) ) { + return( Pop( )); + } else { + retrun( NULL ); + } +} + +INLINE +PCOBJECT +STACK::Top ( + ) CONST + +{ + DebugPtrAssert( _Stack ); + if( ( _Stack != NULL ) && ( _Top != 0 )) { + return( _Stack->GetAt( _Top )); + } else { + return( NULL ); + } +} + diff --git a/ulib/inc/system.hxx b/ulib/inc/system.hxx new file mode 100644 index 0000000..637a90d --- /dev/null +++ b/ulib/inc/system.hxx @@ -0,0 +1,56 @@ +/*++ + +Module Name: + + system.hxx + +Abstract: + + This module contains the definition for the SYSTEM class. The SYSTEM + class is an abstract class which offers an interface for communicating + with the underlying operating system. + +--*/ + +#pragma once + +DECLARE_CLASS( WSTRING ); + +#include "message.hxx" +#include "path.hxx" +#include "ifsentry.hxx" + + +// +// Exit codes +// +#define EXIT_NORMAL 0 +#define EXIT_NO_FILES 1 +#define EXIT_TERMINATED 2 +#define EXIT_MISC_ERROR 4 +#define EXIT_READWRITE_ERROR 5 + + + +// +// Flags that can be specified to FSN_FILE::Copy() +// +#define FSN_FILE_COPY_OVERWRITE_READ_ONLY (0x0001) +#define FSN_FILE_COPY_RESET_READ_ONLY (0x0002) +#define FSN_FILE_COPY_RESTARTABLE (0x0004) +#define FSN_FILE_COPY_COPY_OWNER (0x0008) +#define FSN_FILE_COPY_COPY_ACL (0x0010) +#define FSN_FILE_COPY_ALLOW_DECRYPTED_DESTINATION (0x0020) + + +class SYSTEM : public OBJECT { + + public: + + STATIC + BOOLEAN + QueryCurrentDosDriveName( + OUT PWSTRING DosDriveName + ); + +}; diff --git a/ulib/inc/ulib.hxx b/ulib/inc/ulib.hxx new file mode 100644 index 0000000..54845a7 --- /dev/null +++ b/ulib/inc/ulib.hxx @@ -0,0 +1,64 @@ +/*++ + +Module Name: + + Ulib.hxx + +Abstract: + + +--*/ + +#pragma once + +#include "../../common.h" + + + + +// +// Intrinsic functions +// +#if DBG==0 + + #pragma intrinsic( memset, memcpy, memcmp ) + +#endif + +// +// Here's the scoop...ntdef.h defined NULL to be ( PVOID ) 0. +// Cfront barfs on this if you try and assign NULL to any other pointer type. +// This leaves two options (a) cast all NULL assignments or (b) define NULL +// to be zero which is what C++ expects. +// + +#if defined( NULL ) + + #undef NULL + +#endif +#define NULL ( 0 ) + +// +// Make sure const is not defined. +// +#if defined( const ) + #undef const +#endif + +#include "ulibdef.hxx" +#include "object.hxx" +#include "clasdesc.hxx" + + +DECLARE_CLASS( PATH ); + +extern +BOOLEAN +UlibDefineClassDescriptors(); + + +extern +BOOLEAN +UlibUndefineClassDescriptors(); + diff --git a/ulib/inc/ulibdef.hxx b/ulib/inc/ulibdef.hxx new file mode 100644 index 0000000..f16aada --- /dev/null +++ b/ulib/inc/ulibdef.hxx @@ -0,0 +1,249 @@ +/*++ + +Module Name: + + ulibdef.hxx + +Abstract: + + This module contains primitive support for the ULIB class hierarchy + and it's clients. This support includes: + + - type definitions + - manifest constants + - debugging support + - memory leakage support + - external references + +--*/ + +#pragma once + + +#pragma warning(disable:4091) // Symbol not defined + +// +// Macros for defining types: +// +// - pointers (Ptype) +// - pointers to constants (PCtype) +// - references (Rtype) +// - references to constants (RCtype) +// + +#define DEFINE_POINTER_TYPES( type ) \ + typedef type* P##type; \ + typedef const type* PC##type + +#define DEFINE_REFERENCE_TYPES( type ) \ + typedef type& R##type; \ + typedef const type& RC##type + +#define DEFINE_POINTER_AND_REFERENCE_TYPES( type ) \ + DEFINE_POINTER_TYPES( type ); \ + DEFINE_REFERENCE_TYPES( type ) + +#define DEFINE_TYPE( basetype, newtype ) \ + typedef basetype newtype; \ + DEFINE_POINTER_AND_REFERENCE_TYPES( newtype ) + +#define DECLARE_CLASS( c ) \ + class c; \ + DEFINE_POINTER_AND_REFERENCE_TYPES( c );\ + extern PCCLASS_DESCRIPTOR c##_cd + +// +// Primitive types. +// + +DEFINE_TYPE( unsigned char, UCHAR ); +DEFINE_TYPE( unsigned short,USHORT ); +DEFINE_TYPE( unsigned long, ULONG ); + +DEFINE_TYPE( char, CCHAR ); +DEFINE_TYPE( short, CSHORT ); +DEFINE_TYPE( ULONG, CLONG ); + +DEFINE_TYPE( short, SSHORT ); +DEFINE_TYPE( long, SLONG ); + +DEFINE_TYPE( UCHAR, BYTE ); +DEFINE_TYPE( char, STR ); +DEFINE_TYPE( UCHAR, BOOLEAN ); +DEFINE_TYPE( int, INT ); +DEFINE_TYPE( unsigned int, UINT ); + +#if !defined(_NATIVE_WCHAR_T_DEFINED) +DEFINE_TYPE(USHORT, WCHAR ); +#else +typedef wchar_t WCHAR; +#endif + +typedef WCHAR *LPWCH; // pwc +typedef WCHAR *LPWSTR; // pwsz, 0x0000 terminated UNICODE strings only + +DEFINE_POINTER_AND_REFERENCE_TYPES( WCHAR ); + +DEFINE_TYPE( WCHAR, WSTR ); +//DEFINE_TYPE( struct tagLC_ID, LC_ID ); + +// +// Augmented (beyond standard headers) VOID pointer types +// + +DEFINE_POINTER_TYPES( VOID ); + +// +// Ulib specific, primitive types +// + +DEFINE_TYPE( STR, CLASS_NAME ); +DEFINE_TYPE( ULONG_PTR, CLASS_ID ); + + +// +// Member and non-member function/data modifiers +// + +#define CONST const +#define PURE = 0 +#define STATIC static +#define VIRTUAL virtual +#define INLINE inline +#define FRIEND friend + +// +// Argument modifiers +// + +#define DEFAULT = +#define IN +#define OPTIONAL +#define OUT +#define INOUT +#define REGISTER register + +#if !defined(max) + #define max(a,b) (((a) > (b)) ? (a) : (b) ) +#endif + +#if !defined(min) + #define min(a,b) (((a) < (b)) ? (a) : (b) ) +#endif + + + +// +// Cast (beProtocol) support +// +// If the ID of the passed object is equal to the ID in this class' +// CLASS_DESCRIPTOR, then the object is of this type and the Cast succeeds +// (i.e. returns Object) otherwise the Cast fails (i.e. returns NULL) +// + + +#define DECLARE_CAST_MEMBER_FUNCTION( type ) \ + STATIC \ + P##type \ + Cast ( \ + PCOBJECT Object \ + ) + + +#define DEFINE_CAST_MEMBER_FUNCTION( type ) \ + P##type \ + type::Cast ( \ + PCOBJECT Object \ + ) \ + { \ + if( Object && ( Object->QueryClassId( ) == \ + type##_cd->QueryClassId( ))) { \ + return(( P##type ) Object ); \ + } else { \ + return NULL; \ + } \ + } + + +// +// Constructor support +// + +// +// All classes have CLASS_DESCRIPTORS which are static and named +// after the class appended with the suffix _cd. They are passed stored in +// OBJECT and are set by the SetClassDescriptor function. The Construct +// For debugging purposes the class' name is stored in the CLASS_DESCRIPTOR. +// The Construct member function gas a no-op implementation in OBJECT and +// could be overloaded as a private member in any derived class. +// + +#define DECLARE_CONSTRUCTOR( c ) \ + \ + c ( \ + ) + +#define DEFINE_CONSTRUCTOR( d, b ) \ + PCCLASS_DESCRIPTOR d##_cd; \ + d::d ( \ + ) : b( ) \ + { \ + SetClassDescriptor( ##d##_cd ); \ + Construct( ); \ + } + + + +// +// Debug support. +// +// Use the Debug macros to invoke the following debugging functions. +// +// DebugAbort( str ) - Print a message and abort. +// DebugAssert( exp ) - Assert that an expression is TRUE. Abort if FALSE. +// DebugChkHeap( ) - Validate the heap. Abort if invalid. +// DebugPrint( str ) - Print a string including location (file/line) +// DebugPrintTrace(fmt, ...) - Printf. +// + + +#define DebugAbort( str ) +#define DebugAssert( exp ) +#define DebugCheckHeap( ) +#define DebugPrint( str ) +#define DebugPrintTrace( M ) +#define DebugPtrAssert( ptr ) + + +// +// DELETE macro that NULLifizes the pointer to the deleted object. +// + +// Undo any previous definitions of DELETE. +#undef DELETE + +// #define DELETE(x) FREE(x), x = NULL; + +#define DELETE( x ) \ + delete x, x = NULL + +#define DELETE_ARRAY( x ) \ + delete [] x, x = NULL + +#define NEW new + +#define DumpStats + + + + +#define MALLOC(bytes) ((PVOID) LocalAlloc(0, bytes)) + +#define CALLOC(nitems, size) ((PVOID) LocalAlloc(LMEM_ZEROINIT, nitems*size)) + +#define REALLOC(x, size) ((PVOID) LocalReAlloc(x, size, LMEM_MOVEABLE)) + +#define FREE(x) ((x) ? (LocalFree(x), (x) = NULL) : 0) + + + diff --git a/ulib/inc/wstring.hxx b/ulib/inc/wstring.hxx new file mode 100644 index 0000000..f5c1c9c --- /dev/null +++ b/ulib/inc/wstring.hxx @@ -0,0 +1,1103 @@ +/*++ + +Module Name: + + wstring.hxx + +Abstract: + + This module defines the new WSTRING hierarchy: + + WSTRING + FSTRING + DSTRING + + WSTRING provides all of the desired methods on a string. + FSTRING provides an implementation of a WSTRING with fixed + size, user provided buffer. + DSTRING provides an implementation of a WSTRING with a + dynamic heap based buffer. + + + WSTRING is an abstract classes who's methods depend on the + implementation of two pure virtual methods: 'Resize' and 'NewBuf'. + A derived class must make use of the protected 'PutString' methods + in order to supply WSTRING with its string buffer. Use of + 'PutString' is constrained as follows: + + 1. Supplying just a PWSTR to 'PutString' implies that + the PWSTR is null-terminated. + 2. Supplying a PWSTR and length to 'PutString' implies that + the PWSTR points to a buffer of characters that is at + least one longer than the given length. + + + All implementations of 'Resize' and 'NewBuf' must: + + 1. Allocate an extra character for the NULL. + 2. NULL-terminate the buffer allocated. + 3. Always succeed if size <= current buffer size. + 4. Always work as soon as the derived class is initialized (i.e. + WSTRING::Initialize method need not be called.). + 5. Supply the buffer to WSTRING via 'PutString'. + + Additionally 'Resize' must: + + 1. Preserve the contents of the current buffer. + + All of the comparison operators supplied by WSTRING are + case insensitive. + +--*/ + +#if !defined(_WSTRING_DEFN_) + +#define _WSTRING_DEFN_ + + +// +// The type of the index used to access individual characters within +// a generic string. +// +DEFINE_TYPE( ULONG, CHNUM ); + + +// +// Magic constants +// +#define INVALID_CHAR ((WCHAR)-1) +#define INVALID_CHNUM ((CHNUM)-1) +#define TO_END INVALID_CHNUM + +// +// Prefixes used in various names. +// (This should really be in path.hxx.) +// +#define GUID_VOLNAME_PREFIX L"Volume{" +#define DOS_GUIDNAME_PREFIX L"\\\\?\\" +#define NT_NAME_PREFIX L"\\??\\" + +DECLARE_CLASS( WSTRING ); + +class WSTRING : public OBJECT { + + public: + + + BOOLEAN + Initialize( + IN PCWSTRING InitialString, + IN CHNUM Position DEFAULT 0, + IN CHNUM Length DEFAULT TO_END + ); + + + BOOLEAN + Initialize( + IN PCWSTR InitialString, + IN CHNUM StringLength DEFAULT TO_END + ); + + + BOOLEAN + Initialize( + IN PCSTR InitialString, + IN CHNUM StringLength DEFAULT TO_END + ); + + + BOOLEAN + Initialize( + ); + + + BOOLEAN + Initialize( + IN LONG Number + ); + + + PWSTRING + QueryString( + IN CHNUM Position DEFAULT 0, + IN CHNUM Length DEFAULT TO_END + ) CONST; + + + BOOLEAN + QueryNumber( + OUT PLONG Number, + IN CHNUM Position DEFAULT 0, + IN CHNUM Length DEFAULT TO_END + ) CONST; + + + CHNUM + QueryChCount( + ) CONST; + + + + CHNUM + SyncLength( + ); + + + WCHAR + QueryChAt( + IN CHNUM Position + ) CONST; + + + WCHAR + SetChAt( + IN WCHAR Char, + IN CHNUM Position + ); + + + VOID + DeleteChAt( + IN CHNUM Position, + IN CHNUM Length DEFAULT 1 + ); + + + BOOLEAN + InsertString( + IN CHNUM AtPosition, + IN PCWSTRING String, + IN CHNUM FromPosition DEFAULT 0, + IN CHNUM FromLength DEFAULT TO_END + ); + + + BOOLEAN + Replace( + IN CHNUM AtPosition, + IN CHNUM AtLength, + IN PCWSTRING String, + IN CHNUM FromPosition DEFAULT 0, + IN CHNUM FromLength DEFAULT TO_END + ); + + + BOOLEAN + ReplaceWithChars( + IN CHNUM AtPosition, + IN CHNUM AtLength, + IN WCHAR Character, + IN CHNUM FromLength + ); + + + PCWSTR + GetWSTR( + ) CONST; + + + PWSTR + QueryWSTR( + IN CHNUM Position DEFAULT 0, + IN CHNUM Length DEFAULT TO_END, + OUT PWSTR Buffer DEFAULT NULL, + IN CHNUM BufferLength DEFAULT 0, + IN BOOLEAN ForceNull DEFAULT TRUE + ) CONST; + + + PSTR + QuerySTR( + IN CHNUM Position DEFAULT 0, + IN CHNUM Length DEFAULT TO_END, + OUT PSTR Buffer DEFAULT NULL, + IN CHNUM BufferLength DEFAULT 0, + IN BOOLEAN ForceNull DEFAULT TRUE + ) CONST; + + + LONG + Strcmp( + IN PCWSTRING String + ) CONST; + + STATIC + INT + Strcmp( + IN PWSTR String1, + IN PWSTR String2 + ) ; + + STATIC + INT + Stricmp( + IN PWSTR String1, + IN PWSTR String2 + ) ; + + + LONG + Strcmp( + IN PCWSTRING String, + IN CHNUM LeftPosition + ) CONST; + + + LONG + Strcmp( + IN PCWSTRING String, + IN CHNUM LeftPosition, + IN CHNUM LeftLength, + IN CHNUM RightPosition DEFAULT 0, + IN CHNUM RightLength DEFAULT TO_END + ) CONST; + + + LONG + Stricmp( + IN PCWSTRING String + ) CONST; + + + LONG + Stricmp( + IN PCWSTRING String, + IN CHNUM LeftPosition + ) CONST; + + + STATIC + PWSTR + SkipWhite( + IN PWSTR p + ); + + + BOOLEAN + Strcat( + IN PCWSTRING String + ); + + + PWSTRING + Strupr( + IN CHNUM StartPosition, + IN CHNUM Length DEFAULT TO_END + ); + + + CHNUM + Strchr( + IN WCHAR Char, + IN CHNUM StartPosition DEFAULT 0 + ) CONST; + + + CHNUM + Strrchr( + IN WCHAR Char, + IN CHNUM StartPosition DEFAULT 0 + ) CONST; + + + CHNUM + Strstr( + IN PCWSTRING String + ) CONST; + + + CHNUM + Strspn( + IN PCWSTRING String, + IN CHNUM StartPosition DEFAULT 0 + ) CONST; + + + CHNUM + Strcspn( + IN PCWSTRING String, + IN CHNUM StartPosition DEFAULT 0 + ) CONST; + + + BOOLEAN + operator==( + IN RCWSTRING String + ) CONST; + + + BOOLEAN + operator!=( + IN RCWSTRING String + ) CONST; + + + BOOLEAN + operator<( + IN RCWSTRING String + ) CONST; + + + BOOLEAN + operator>( + IN RCWSTRING String + ) CONST; + + + BOOLEAN + operator<=( + IN RCWSTRING String + ) CONST; + + + BOOLEAN + operator>=( + IN RCWSTRING String + ) CONST; + + VIRTUAL + BOOLEAN + Resize( + IN CHNUM NewStringLength + ) PURE; + + VIRTUAL + BOOLEAN + NewBuf( + IN CHNUM NewStringLength + ) PURE; + + + CHNUM + Truncate( + IN CHNUM Position DEFAULT 0 + ); + + + protected: + + DECLARE_CONSTRUCTOR( WSTRING ); + + + VOID + Construct( + ); + + + VOID + PutString( + IN OUT PWSTR String + ); + + + VOID + PutString( + IN OUT PWSTR String, + IN CHNUM Length + ); + + private: + + + STATIC + BOOLEAN + ConvertOemToUnicodeN( + PWSTR UnicodeString, + ULONG MaxBytesInUnicodeString, + PULONG BytesInUnicodeString, + PCHAR OemString, + ULONG BytesInOemString + ); + + STATIC + BOOLEAN + ConvertUnicodeToOemN( + PCHAR OemString, + ULONG MaxBytesInOemString, + PULONG BytesInOemString, + PWSTR UnicodeString, + ULONG BytesInUnicodeString + ); + + PWSTR _s; // Beginning of string. + CHNUM _l; // Strlen of string. + +}; + + +INLINE +VOID +WSTRING::PutString( + IN OUT PWSTR String + ) +/*++ + +Routine Description: + + This routine initializes this string with the given null + terminated buffer. + +Arguments: + + String - Supplies the buffer to initialize the string with. + +Return Value: + + None. + +--*/ +{ + _s = String; + _l = (CHNUM) wcslen(_s); +} + + +INLINE +VOID +WSTRING::PutString( + IN OUT PWSTR String, + IN CHNUM Length + ) +/*++ + +Routine Description: + + This routine initializes this string with the given buffer + and string length. + +Arguments: + + String - Supplies the buffer to initialize the string with. + Length - Supplies the length of the string. + +Return Value: + + None. + +--*/ +{ + _s = String; + _l = Length; + _s[_l] = 0; +} + + +INLINE +BOOLEAN +WSTRING::Initialize( + ) +/*++ + +Routine Description: + + This routine initializes this string to an empty null-terminated + string. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + return Resize(0); +} + + +INLINE +CHNUM +WSTRING::QueryChCount( + ) CONST +/*++ + +Routine Description: + + This routine returns the number of characters in the string. + +Arguments: + + None. + +Return Value: + + The number of characters in this string. + +--*/ +{ + return _l; +} + + +INLINE +CHNUM +WSTRING::SyncLength( + ) +/*++ + +Routine Description: + + This routine recalculates the correct length of the string. + +Arguments: + + None. + +Return Value: + + The recomputed length of the string. + +--*/ +{ + return _l = (CHNUM) wcslen(_s); +} + + +INLINE +WCHAR +WSTRING::QueryChAt( + IN CHNUM Position + ) CONST +/*++ + +Routine Description: + + This routine returns the character at the given position. + The position is a zero-based index into the string. + The position must be in the range of the string. + +Arguments: + + Position - Supplies an index into the string. + +Return Value: + + The character at the given position. + +--*/ +{ + return (Position < _l) ? _s[Position] : INVALID_CHAR; +} + + +INLINE +WCHAR +WSTRING::SetChAt( + IN WCHAR Char, + IN CHNUM Position + ) +/*++ + +Routine Description: + + This routine sets the given character at the given position in + the string. + +Arguments: + + Char - Supplies the character to set into the string. + Position - Supplies the position at which to set the character. + +Return Value: + + The character that was set. + +--*/ +{ + DebugAssert(Position < _l); + return _s[Position] = Char; +} + + +INLINE +PCWSTR +WSTRING::GetWSTR( + ) CONST +/*++ + +Routine Description: + + This routine returns this string internal buffer. + +Arguments: + + None. + +Return Value: + + A pointer to the strings buffer. + +--*/ +{ + return _s; +} + + +INLINE +LONG +WSTRING::Strcmp( + IN PCWSTRING String + ) CONST +/*++ + +Routine Description: + + This routine compares two strings. + +Arguments: + + String - Supplies the string to compare to. + +Return Value: + + < 0 - This string is less than the given string. + 0 - This string is equal to the given string. + > 0 - This string is greater than the given string. + +--*/ +{ + return wcscmp(_s, String->_s); +} + + +INLINE +INT +WSTRING::Strcmp( + IN PWSTR String1, + IN PWSTR String2 + ) +{ + return wcscmp( String1, String2 ); +} + +INLINE +INT +WSTRING::Stricmp( + IN PWSTR String1, + IN PWSTR String2 + ) +{ + return _wcsicmp( String1, String2 ); +} + +INLINE +LONG +WSTRING::Strcmp( + IN PCWSTRING String, + IN CHNUM LeftPosition + ) CONST +/*++ + +Routine Description: + + This routine compares two strings. It starts comparing the + current string at the given position. + +Arguments: + + String - Supplies the string to compare to. + LeftPosition - Supplies the starting position to start comparison + on the current string. + +Return Value: + + < 0 - This string is less than the given string. + 0 - This string is equal to the given string. + > 0 - This string is greater than the given string. + +--*/ +{ + return wcscmp(_s + LeftPosition, String->_s); +} + + +INLINE +LONG +WSTRING::Stricmp( + IN PCWSTRING String + ) CONST +/*++ + +Routine Description: + + This routine compares two strings insensitive of case. + +Arguments: + + String - Supplies the string to compare to. + +Return Value: + + < 0 - This string is less than the given string. + 0 - This string is equal to the given string. + > 0 - This string is greater than the given string. + +--*/ +{ + return _wcsicmp(_s, String->_s); +} + + +INLINE +LONG +WSTRING::Stricmp( + IN PCWSTRING String, + IN CHNUM LeftPosition + ) CONST +/*++ + +Routine Description: + + This routine compares two strings insensitive of case. + +Arguments: + + String - Supplies the string to compare to. + LeftPosition - Supplies the position in this string to start + comparison. + +Return Value: + + < 0 - This string is less than the given string. + 0 - This string is equal to the given string. + > 0 - This string is greater than the given string. + +--*/ +{ + return _wcsicmp(_s + LeftPosition, String->_s); +} + + +INLINE +CHNUM +WSTRING::Strchr( + IN WCHAR Char, + IN CHNUM StartPosition + ) CONST +/*++ + +Routine Description: + + This routine returns the position of the first occurance of + the given character. + +Arguments: + + Char - Supplies the character to find. + +Return Value: + + The position of the given character or INVALID_CHNUM. + +--*/ +{ + PWSTR p; + + DebugAssert(StartPosition <= _l); + p = wcschr(_s + StartPosition, Char); + return p ? (CHNUM)(p - _s) : INVALID_CHNUM; +} + + +INLINE +CHNUM +WSTRING::Strrchr( + IN WCHAR Char, + IN CHNUM StartPosition + ) CONST +/*++ + +Routine Description: + + This routine returns the position of the last occurance of + the given character. + +Arguments: + + Char - Supplies the character to find. + +Return Value: + + The position of the given character or INVALID_CHNUM. + +--*/ +{ + PWSTR p; + + p = wcsrchr(_s + StartPosition, Char); + return p ? (CHNUM)(p - _s) : INVALID_CHNUM; +} + + +INLINE +CHNUM +WSTRING::Strstr( + IN PCWSTRING String + ) CONST +/*++ + +Routine Description: + + This routine finds the given string withing this string. + +Arguments: + + String - Supplies the string to find. + +Return Value: + + The position of the given string in this string or INVALID_CHNUM. + +--*/ +{ + PWSTR p; + + p = wcsstr(_s, String->_s); + return p ? (CHNUM)(p - _s) : INVALID_CHNUM; +} + + +INLINE +CHNUM +WSTRING::Strspn( + IN PCWSTRING String, + IN CHNUM StartPosition + ) CONST +/*++ + +Routine Description: + + This routine returns the position of the first character in this + string that does not belong to the set of characters in the given + string. + +Arguments: + + String - Supplies the list of characters to search for. + +Return Value: + + The position of the first character found that does not belong + to the given string. + +--*/ +{ + CHNUM r; + + DebugAssert(StartPosition <= _l); + r = (CHNUM)wcsspn(_s + StartPosition, String->_s) + StartPosition; + return r < _l ? r : INVALID_CHNUM; +} + + +INLINE +CHNUM +WSTRING::Strcspn( + IN PCWSTRING String, + IN CHNUM StartPosition + ) CONST +/*++ + +Routine Description: + + This routine returns the position of the first character in this + string that belongs to the set of characters in the given + string. + +Arguments: + + String - Supplies the list of characters to search for. + +Return Value: + + Returns the position of the first character in this string + belonging to the given string or INVALID_CHNUM. + +--*/ +{ + CHNUM r; + + DebugAssert(StartPosition <= _l); + r = (CHNUM)wcscspn(_s + StartPosition, String->_s) + StartPosition; + return r < _l ? r : INVALID_CHNUM; +} + + +INLINE +BOOLEAN +WSTRING::operator==( + IN RCWSTRING String + ) CONST +{ + return Stricmp(&String) == 0; +} + + +INLINE +BOOLEAN +WSTRING::operator!=( + IN RCWSTRING String + ) CONST +{ + return Stricmp(&String) != 0; +} + + +INLINE +BOOLEAN +WSTRING::operator<( + IN RCWSTRING String + ) CONST +{ + return Stricmp(&String) < 0; +} + + +INLINE +BOOLEAN +WSTRING::operator>( + IN RCWSTRING String + ) CONST +{ + return Stricmp(&String) > 0; +} + + +INLINE +BOOLEAN +WSTRING::operator<=( + IN RCWSTRING String + ) CONST +{ + return Stricmp(&String) <= 0; +} + + +INLINE +BOOLEAN +WSTRING::operator>=( + IN RCWSTRING String + ) CONST +{ + return Stricmp(&String) >= 0; +} + + +INLINE +CHNUM +WSTRING::Truncate( + IN CHNUM Position + ) +{ + DebugAssert(Position <= _l); + Resize(Position); + return _l; +} + + +DECLARE_CLASS( FSTRING ); + +class FSTRING : public WSTRING { + + public: + + DECLARE_CONSTRUCTOR( FSTRING ); + + + PWSTRING + Initialize( + IN OUT PWSTR InitialString, + IN CHNUM BufferLength DEFAULT TO_END + ); + + VIRTUAL + BOOLEAN + Resize( + IN CHNUM NewStringLength + ); + + VIRTUAL + BOOLEAN + NewBuf( + IN CHNUM NewStringLength + ); + + private: + + CHNUM _buffer_length; + +}; + + +INLINE +PWSTRING +FSTRING::Initialize( + IN OUT PWSTR InitialString, + IN CHNUM BufferLength + ) +/*++ + +Routine Description: + + This routine initializes this class with a null-terminated + unicode string. This routine does not make a copy of the string + but uses it as is. + +Arguments: + + NullTerminatedString - Supplies a null-terminated unicode string. + BufferLength - Supplies the buffer length. + +Return Value: + + A pointer to this class. + +--*/ +{ + PutString(InitialString); + _buffer_length = ((BufferLength == TO_END) ? + (QueryChCount() + 1) : BufferLength); + return this; +} + + +DECLARE_CLASS( DSTRING ); + +class DSTRING : public WSTRING { + + public: + + DECLARE_CONSTRUCTOR( DSTRING ); + + VIRTUAL + ~DSTRING( + ); + + VIRTUAL + BOOLEAN + Resize( + IN CHNUM NewStringLength + ); + + VIRTUAL + BOOLEAN + NewBuf( + IN CHNUM NewStringLength + ); + + private: + + VOID + Construct( + ); + + PWSTR _buf; // String buffer. + CHNUM _length; // Number of characters in buffer. + +}; + + +#endif // _WSTRING_DEFN_ diff --git a/ulib/src/array.cxx b/ulib/src/array.cxx new file mode 100644 index 0000000..9d2f131 --- /dev/null +++ b/ulib/src/array.cxx @@ -0,0 +1,664 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + array.cxx + +Abstract: + + This module contains the definition for the ARRAY class. ARRAY is a + concrete implementation of a SORTABLE_CONTAINER. It extends the interface + to allow for easy access uswing a simple ULONG as an index. It is + dynamically growable and supports bases other than zero. + +--*/ + + +#include "ulib.hxx" +#include "array.hxx" +#include "arrayit.hxx" + + +DEFINE_CONSTRUCTOR( ARRAY, SORTABLE_CONTAINER ); +DEFINE_CAST_MEMBER_FUNCTION( ARRAY ); + +VOID +ARRAY::Construct ( + ) + +/*++ + +Routine Description: + + Construct an ARRAY by setting the initial value of of the OBJECT array + pointer to NULL. + +Arguments: + + None. + +Return Value: + + None. + +--*/ + +{ + _ObjectArray = NULL; +} + + + + + + +ARRAY::~ARRAY ( + ) + +/*++ + +Routine Description: + + Destroy an ARRAY by freeing it's internal storage. Note that this + deletes the array, not the objects themselves. + +Arguments: + + None. + +Return Value: + + None. + +--*/ + +{ + if ( _ObjectArray ) { + FREE( _ObjectArray ); + } +} + + + + +BOOLEAN +ARRAY::Initialize ( + IN ULONG Capacity, + IN ULONG CapacityIncrement + ) + +/*++ + +Routine Description: + + Initialize an ARRAY object by setting it's internal state to supplied + or default values. In addition allocate an initial chunk of memory for + the actual storage of POBJECTs. + +Arguments: + + Capacity - Supplies the total number of OBJECTs the ARRAY + can contain + CapacityIncrement - Supplies the number of OBJECTs to make room for + when growing the ARRAY + +Return Value: + + BOOLEAN - TRUE if the ARRAY is successfully initialized. + +--*/ + +{ + DebugAssert( Capacity != 0 ); + + // + // If re-initializing, se reuse the current array + // + if ( _ObjectArray ) { + _Capacity = SetArrayCapacity( Capacity ); + } else { + _ObjectArray = (PPOBJECT)CALLOC( (UINT)Capacity, + sizeof( POBJECT ) ); + _Capacity = Capacity; + } + + _CapacityIncrement = CapacityIncrement; + _PutIndex = 0; + +#if DBG==1 + _IteratorCount = 0; +#endif + + if ( _ObjectArray ) { + DebugCheckHeap(); + return TRUE; + } else { + return FALSE; + } +} + + +BOOLEAN +ARRAY::DeleteAllMembers ( + ) + +/*++ + +Routine Description: + + Deletes all the members of the array + +Arguments: + + None + +Return Value: + + BOOLEAN - TRUE if all members deleted + +--*/ + +{ + PPOBJECT PObject; + + if ( _PutIndex > 0 ) { + +#if 0 // Bogus assert due to compiler error. Put it back in when compiler fixed +#if DBG==1 + DebugAssert( _IteratorCount == 0 ); +#endif +#endif + + PObject = &_ObjectArray[ _PutIndex - 1 ]; + + while ( PObject >= _ObjectArray ) { + DELETE( *PObject ); + PObject--; + } + + _PutIndex = 0; + } + + return TRUE; +} + + + + +POBJECT +ARRAY::GetAt ( + IN ULONG Index + ) CONST + +/*++ + +Routine Description: + + Retrieves the OBJECT at the specified Index. + +Arguments: + + Index - Supplies the index of the OBJECT in question. + +Return Value: + + POBJECT - A constant pointer to the requested OBJECT. + +--*/ + +{ + DebugPtrAssert( _ObjectArray ); + + if ( (_PutIndex > 0) && (Index < _PutIndex) ) { + return _ObjectArray[ Index ]; + } else { + return NULL; + } +} + + + +ULONG +ARRAY::GetMemberIndex ( + IN POBJECT Object + ) CONST + +/*++ + +Routine Description: + + Returns the position (index) of an object in the array. + +Arguments: + + POBJECT - Pointer to the OBJECT in question. + +Return Value: + + ULONG - The position of the OBJECT in the array. If the OBJECT is not + in the array, returns INVALID_INDEX. + +--*/ + +{ + ULONG Index; + + DebugPtrAssert( _ObjectArray ); + DebugPtrAssert( Object ); + + if( Object == NULL ) { + return( INVALID_INDEX ); + } + + Index = 0; + while( ( Index < QueryMemberCount() ) && + ( _ObjectArray[ Index ] != Object ) ) { + Index++; + } + return( ( Index < QueryMemberCount() )? Index : INVALID_INDEX ); +} + + + + +BOOLEAN +ARRAY::PutAt ( + IN OUT POBJECT Member, + IN ULONG Index + ) + +/*++ + +Routine Description: + + Puts an OBJECT at a particular location in the ARRAY. + The new object has to replace an existing object, i.e. the + index has to be smaller than the member count. + +Arguments: + + Member - Supplies the OBJECT to place in the ARRAY + Index - Supplies the index where the member is to be put + +Return Value: + + BOOLEAN - TRUE if member put, FALSE otherwise + + +--*/ + +{ + DebugPtrAssert( Member ); + DebugPtrAssert( Index < _PutIndex ); + + if ( Index < _PutIndex ) { + _ObjectArray[ Index ] = Member; + return TRUE; + } + + return FALSE; +} + + + +PITERATOR +ARRAY::QueryIterator ( + ) CONST + +/*++ + +Routine Description: + + Create an ARRAY_ITERATOR object for this ARRAY. + +Arguments: + + None. + +Return Value: + + PITERATOR - Pointer to an ITERATOR object. + +--*/ + +{ + PARRAY_ITERATOR Iterator; + + // + // Create new iterator + // + if ( Iterator = NEW ARRAY_ITERATOR ) { + + // + // Initialize the iterator + // + if ( !Iterator->Initialize( (PARRAY)this ) ) { + DELETE( Iterator ); + } + } + + return Iterator; +} + + + +ULONG +ARRAY::QueryMemberCount ( + ) CONST + +/*++ + +Routine Description: + + Obtains the number of elements in the array + +Arguments: + + None + +Return Value: + + ULONG - The number of members in the array + + +--*/ + +{ + return _PutIndex; +} + + + +POBJECT +ARRAY::Remove ( + IN OUT PITERATOR Position + ) + +/*++ + +Routine Description: + + Removes a member from the array + +Arguments: + + Position - Supplies an iterator whose currency is to be removed + +Return Value: + + POBJECT - The object removed + + +--*/ + +{ + PARRAY_ITERATOR Iterator; + + DebugPtrAssert( Position ); + DebugPtrAssert( ARRAY_ITERATOR::Cast( Position )); + + Iterator = (PARRAY_ITERATOR)Position; + + return RemoveAt( Iterator->QueryCurrentIndex() ); +} + + +POBJECT +ARRAY::RemoveAt ( + IN ULONG Index + ) + +/*++ + +Routine Description: + + Removes a member from the array + +Arguments: + + Index - Supplies the index of the member to be removed + +Return Value: + + POBJECT - The object removed + + +--*/ + +{ + POBJECT Object = NULL; + + if ( Index < _PutIndex ) { + + // DebugAssert( _IteratorCount <= 1 ); + + // + // Get the object + // + Object = (POBJECT)_ObjectArray[ Index ]; + + // + // Shift the rest of the array + // + memmove ( &_ObjectArray[ Index ], + &_ObjectArray[ Index + 1 ], + (UINT)(_PutIndex - Index - 1) * sizeof( POBJECT ) ); + + // + // Update the _PutIndex + // + _PutIndex--; + + } + + return Object; +} + + + +ULONG +ARRAY::SetCapacity ( + IN ULONG Capacity + ) + +/*++ + +Routine Description: + + Sets the capacity of the array. Will not shrink the array if the + capacity indicated is less than the number of members in the array. + +Arguments: + + Capacity - New capacity of the array + +Return Value: + + ULONG - The new capacity of the array + + +--*/ + +{ + if ( Capacity >= _PutIndex ) { + + SetArrayCapacity( Capacity ); + + } + + return _Capacity; +} + + +BOOLEAN +ARRAY::Insert( + IN OUT POBJECT Member, + IN ULONG Index + ) +/*++ + +Routine Description: + + Inserts an element in the array at the specified position, shifting + elements to the right if necessary. + +Arguments: + + Member - Supplies pointer to object to be inserted in the array + + Index - Supplies the index where the element is to be put + +Return Value: + + BOOLEAN - TRUE if new element inserted, FALSE otherwise + + +--*/ + +{ + DebugPtrAssert( Member ); + DebugPtrAssert( Index <= _PutIndex ); + + // + // Make sure that there will be enough space in the array for the + // new element + // + if ( _PutIndex >= _Capacity ) { + + if ( _PutIndex >= SetArrayCapacity( _Capacity + _CapacityIncrement ) ) { + // + // Could not grow the array + // + return FALSE; + } + } + + // + // If required, shift the array to the right to make space for the + // new element. + // + if ( Index < _PutIndex ) { + + memmove ( &_ObjectArray[ Index + 1 ], + &_ObjectArray[ Index ], + (UINT)( _PutIndex - Index ) * sizeof( POBJECT ) ); + } + + // + // Insert the element + // + _ObjectArray[ Index ] = Member; + + // + // Increment the number of elements in the array + // + _PutIndex++; + + return TRUE; +} + + + +ULONG +ARRAY::SetArrayCapacity ( + IN ULONG NumberOfElements + ) + +/*++ + +Routine Description: + + Sets the capacity of the array. Allways reallocs the array. + +Arguments: + + NewSize - New capacity of the array + +Return Value: + + ULONG - The new capacity of the array + + +--*/ + +{ + PPOBJECT Tmp; + + Tmp = (PPOBJECT)REALLOC( _ObjectArray, + (UINT)NumberOfElements * sizeof(POBJECT) ); + + if ( Tmp ) { + _ObjectArray = Tmp; + _Capacity = NumberOfElements; + } + + return _Capacity; +} + + +int __cdecl +ARRAY::CompareAscending ( + IN const void * Object1, + IN const void * Object2 + ) + +/*++ + +Routine Description: + + Compares two objects. + +Arguments: + + Object1 - Supplies pointer to first object + Object2 - Supplies pointer to second object + +Return Value: + + Returns: + + <0 if Object1 is less that Object2 + 0 if Object1 is equal to Object2 + >0 if Object1 is greater than Object2 + + +--*/ + +{ + return (*(POBJECT *)Object1)->Compare( *(POBJECT *)Object2 ); +} + + + +int __cdecl +ARRAY::CompareDescending ( + IN const void * Object1, + IN const void * Object2 + ) + +/*++ + +Routine Description: + + Compares two objects + +Arguments: + + Object1 - Supplies pointer to first object + Object2 - Supplies pointer to second object + +Return Value: + + Returns: + + <0 if Object2 is less that Object1 + 0 if Object2 is equal to Object1 + >0 if Object2 is greater than Object1 + +--*/ + +{ + return (*(POBJECT *)Object2)->Compare( *(POBJECT *)Object1 ); +} diff --git a/ulib/src/arrayit.cxx b/ulib/src/arrayit.cxx new file mode 100644 index 0000000..4cf9eee --- /dev/null +++ b/ulib/src/arrayit.cxx @@ -0,0 +1,250 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + arrayit.cxx + +Abstract: + + This file contains the definitions for the ARRAY_ITERATOR class. + ARRAY_ITERATOR is a concrete implementation of the abstract ITERATOR + class. + +--*/ + + +#include "ulib.hxx" +#include "array.hxx" +#include "arrayit.hxx" + + +DEFINE_CAST_MEMBER_FUNCTION( ARRAY_ITERATOR ); + +DEFINE_CONSTRUCTOR( ARRAY_ITERATOR, ITERATOR ); + + +VOID +ARRAY_ITERATOR::Construct ( + ) + +/*++ + +Routine Description: + + Construct an ARRAY_ITERATOR by setting it's current index to 0 and it's + associated ARRAY to NULL. + +Arguments: + + None. + +Return Value: + + None. + +--*/ + +{ + _Array = NULL; +} + + + +ARRAY_ITERATOR::~ARRAY_ITERATOR ( + ) +/*++ + +Routine Description: + + Destructor for the ARRAY_ITERATOR class + +Arguments: + + None + +Return Value: + + None + +--*/ + +{ +#if DBG==1 + if ( _Array ) { + _Array->_IteratorCount--; + } +#endif +} + + + + +VOID +ARRAY_ITERATOR::Reset( + ) + +/*++ + +Routine Description: + + Resets the iterator + +Arguments: + + None + +Return Value: + + None + +--*/ + +{ + _CurrentIndex = INVALID_INDEX; +} + + + +POBJECT +ARRAY_ITERATOR::GetCurrent( + ) +/*++ + +Routine Description: + + Gets current member + +Arguments: + + None + +Return Value: + + POBJECT - Pointer to current member in the array + +--*/ + +{ + if ( _CurrentIndex == INVALID_INDEX ) { + return NULL; + } else { + return _Array->GetAt( _CurrentIndex ); + } +} + + + + +POBJECT +ARRAY_ITERATOR::GetNext( + ) +/*++ + +Routine Description: + + Gets next member in the array + +Arguments: + + None + +Return Value: + + POBJECT - Pointer to next member in the array + +--*/ + +{ + // + // Wrap if necessary. Note that this assumes that INVALID_INDEX + 1 == 0 + // + _CurrentIndex++; + + if ( _CurrentIndex >= _Array->QueryMemberCount() ) { + _CurrentIndex = INVALID_INDEX; + } + + // + // Get next + // + return _Array->GetAt( _CurrentIndex ); +} + + +POBJECT +ARRAY_ITERATOR::GetPrevious( + ) +/*++ + +Routine Description: + + Gets previous member in the array + +Arguments: + + None + +Return Value: + + POBJECT - Pointer to previous member in the array + +--*/ + +{ + // + // Wrap if necessary. Note that this assumes that 0 - 1 == INVALID_INDEX + // + + if ( _CurrentIndex == INVALID_INDEX ) { + _CurrentIndex = _Array->QueryMemberCount() - 1; + } else { + _CurrentIndex--; + } + + // + // Get next + // + return _Array->GetAt( _CurrentIndex ); +} + + + + +BOOLEAN +ARRAY_ITERATOR::Initialize ( + IN PARRAY Array + ) + +/*++ + +Routine Description: + + Associate an ARRAY with this ARRAY_ITERATOR and reset the current index + +Arguments: + + Array - Supplies pointer to the array object + +Return Value: + + BOOLEAN - Returns TRUE if the initialization was succesful. + +--*/ + +{ + DebugPtrAssert( Array ); + +#if DBG==1 + if ( _Array ) { + _Array->_IteratorCount--; + } + Array->_IteratorCount++; +#endif + _Array = Array; + _CurrentIndex = INVALID_INDEX; + + + return TRUE; +} diff --git a/ulib/src/bitvect.cxx b/ulib/src/bitvect.cxx new file mode 100644 index 0000000..d45d6ae --- /dev/null +++ b/ulib/src/bitvect.cxx @@ -0,0 +1,572 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + bitvect.cxx + +Abstract: + + This module contains the definition for thje BITVECTOR class. + +--*/ + + +#include "ulib.hxx" +#include "bitvect.hxx" +#include + +// +// Static member data. +// + +// +// Bits per byte value table e.g. 27->4 bits +// +// Algorithm: +// +// _BitsSetLookUp[0] = 0; +// +// For the ranges [1,1],[2,3],[4,7],[8,15],...,[128,255]. +// +// for (n = (( PT ) 1 ); n <= 8; n++) { +// +// +// Compute range for loop. +// +// r = (( PT ) 1 ) << (n - (( PT ) 1 )); +// +// +// [r, 2*r - 1 ] = [0, r - 1] + 1; +// +// for (i = 0; i < r; i++) { +// _BitsSetLookUp[i + r] = _BitsSetLookUp[i] + (( PT ) 1 ); +// } +// } +// } +// + +CONST BYTE BITVECTOR::_BitsSetLookUp[ 256 ] = { + + 0, 1, 1, 2, 1, 2, 2, 3, + 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, + 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, + 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, + 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, + 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, + 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, + 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, + 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, + 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, + 5, 6, 6, 7, 6, 7, 7, 8 +}; + + +DEFINE_CONSTRUCTOR( BITVECTOR, OBJECT ); + +DEFINE_CAST_MEMBER_FUNCTION( BITVECTOR ); + +VOID +BITVECTOR::Construct ( + ) + +/*++ + +Routine Description: + + Construct a BITVECTOR. + +Arguments: + + None. + +Return Value: + + None. + +--*/ + +{ + REGISTER PT pt; + + // + // Find the number of bits per PTs + // + + _BitsPerPT = sizeof( PT ) * CHAR_BIT; + + // + // Set the smallest number of PTs needed + // + + _PTCount = (( PT ) 1 ); + + // + // Create the mask used to separate the array index from the bit index + // + + _BitPositionMask = _BitsPerPT - (( PT ) 1 ); + + // + // Count the number of bits required to make the shift count for + // accessing the Primitive Type. + // + + for( _IndexShiftCount = 0, pt = _BitPositionMask; pt; + pt >>= (( PT ) 1 ), _IndexShiftCount++ ); + + // + // Initialize BITVECTOR state. + // + + _BitVector = NULL; + _PTCount = 0; + _FreeBitVector = FALSE; +} + + +BOOLEAN +BITVECTOR::Initialize ( + IN PT Size, + IN BIT InitialValue, + IN PPT Memory + ) + +/*++ + +Routine Description: + + Construct a BITVECTOR with at least the size specified and + initialize all bits to SET or RESET. + +Arguments: + + Size - Supplies the number of bits in the vector + InitialValue - Supplies the initial value for the bits + Memory - Supplies a memory buffer to use for the vector + +Return Value: + + BOOLEAN - Returns TRUE if the BITVECTOR was succesfully initialized. + +Notes: + + Minimum and default BITVECTOR size is the number of bits in + one PT. Default initializer is RESET. The size of a BITVECTOR + is rounded up to the nearest whole multiple of (_BitsPerPT * CHAR_BIT). + + If the client supplies the buffer it is the client's responsibility + to ensure that Size and the size of the buffer are in sync. + Also SetSize will not change the size of a client supplied + buffer. + +--*/ + +{ + // + // Destroy the internals of a previous BITVECTOR. + // + + Destroy( ); + + // + // Find the number of PTs that will be required for this BITVECTOR + // (handles smallest size case (Size = 0) ). + // + + _PTCount = Size ? (( Size + _BitsPerPT - (( PT ) 1 )) / _BitsPerPT ) : (( PT ) 1 ); + + // + // If Memory was supplied use that for the vector else allocate + // the vector. + // + + if( Memory ) { + + _BitVector = Memory; + + } else { + _FreeBitVector = TRUE; + if( !( _BitVector = ( PT* ) MALLOC(( size_t ) ( _PTCount * sizeof( PT ))))) { + + return FALSE; + } + } + + // + // Set the bitvector to the supplied value ( SET | RESET ) + // + + ( InitialValue == SET ) ? SetAll( ) : ResetAll( ); + + return TRUE; +} + + +BITVECTOR::~BITVECTOR ( + ) + +/*++ + +Routine Description: + + Destroy a BITVECTOR by calling it's Destroy function. + +Arguments: + + None. + +Return Value: + + None. + +--*/ + +{ + Destroy( ); +} + +VOID +BITVECTOR::Destroy ( + ) + +/*++ + +Routine Description: + + Destroy a BITVECTOR by possibly freeing it's internal storage. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + if( _FreeBitVector ) { + + DebugAssert( _BitVector != NULL ); + FREE( _BitVector ); + } +} + + +PT +BITVECTOR::SetSize ( + IN PT Size, + IN BIT InitialValue + ) + +/*++ + +Routine Description: + + Set the number of bits in the vector + +Arguments: + + Size - Supplies the number of bits to set the vector size to + InitialValue- Supplies the initial value for the bits + +Return Value: + + PT - Returns the new size of this BITVECTOR in bits. + +Notes: + + SetSize will merrily truncate the vector with no warning. + + Minimum and default BITVECTOR size is the number of bits in + one PT. Default initializer is RESET. The size of a BITVECTOR + is rounded up to the nearest whole multiple of (_BitsPerPT * CHAR_BIT). + + If the client supplied the buffer refuse to change it's size + +--*/ + +{ + REGISTER PT PTCountNew; + PT cbitsNew; + PT cbitsOld; + + // + // Check that the bitvector was created... + // + + DebugPtrAssert( _BitVector ); + if( _BitVector == NULL ) { + return( 0 ); + } + + // + // If the client supplied the buffer, refuse to change it's size. + // + + if( ! _FreeBitVector ) { + return( _PTCount * _BitsPerPT ); + } + + + // + // Compute the number of PTs and bits required for the new size + // + + PTCountNew = Size ? (( Size + _BitsPerPT - (( PT ) 1 ) ) / _BitsPerPT ) : (( PT ) 1 ); + cbitsNew = PTCountNew * _BitsPerPT; + + if( PTCountNew != _PTCount ) { + + // + // The new size requires a different number of PTs then the old + // + + if( !( _BitVector = ( PT* ) REALLOC(( VOID* ) _BitVector, + ( size_t ) ( PTCountNew * sizeof( PT ))))) { + + return( 0 ); + } + } + + // + // If the new size contains more bits, initialize them to the supplied + // value + // + + cbitsOld = _PTCount * _BitsPerPT; + _PTCount = PTCountNew; + + if( cbitsNew > cbitsOld ) { + if( InitialValue == SET ) { + SetBit( cbitsOld, cbitsNew - cbitsOld ); + } else { + ResetBit( cbitsOld, cbitsNew - cbitsOld ); + } + } + + return( _PTCount * _BitsPerPT ); +} + + +VOID +BITVECTOR::SetBit ( + IN PT Index, + IN PT Count + ) + +/*++ + +Routine Description: + + SET the supplied range of bits + +Arguments: + + Index - Supplies the index at which to start setting bits. + Count - Supplies the number of bits to set. + +Return Value: + + None. + +Notes: + + It may be faster to compute masks for setting sub-ranges. + +--*/ + +{ + REGISTER PT ptCurBit; + + DebugAssert( _BitVector != NULL ); + DebugAssert(( Index + Count ) <= ( _PTCount * _BitsPerPT )); + + // Set count to be the max instead. + Count += Index; + + for (ptCurBit = Index; (ptCurBit < Count) && + (ptCurBit & _BitPositionMask); ptCurBit++) { + _BitVector[ptCurBit >> _IndexShiftCount] |= + (1 << (ptCurBit & _BitPositionMask)); + } + + for (; ptCurBit + 8*sizeof(PT) <= Count; ptCurBit += 8*sizeof(PT)) { + _BitVector[ptCurBit >> _IndexShiftCount] = 0xffffffff; + } + + for (; ptCurBit < Count; ptCurBit++) { + _BitVector[ptCurBit >> _IndexShiftCount] |= + (1 << (ptCurBit & _BitPositionMask)); + } +} + + +VOID +BITVECTOR::ResetBit ( + IN PT Index, + IN PT Count + ) + +/*++ + +Routine Description: + + RESET the supplied range of bits + +Arguments: + + Index - Supplies the index at which to start resetting bits. + Count - Supplies the number of bits to reset. + +Return Value: + + None. + +Notes: + + It may be faster to compute masks for resetting sub-ranges. + +--*/ + +{ + REGISTER PT ptCurBit; + + DebugAssert( _BitVector != NULL ); + DebugAssert(( Index + Count ) <= ( _PTCount * _BitsPerPT )); + + // Set count to be the max instead. + Count += Index; + + for (ptCurBit = Index; (ptCurBit < Count) && + (ptCurBit & _BitPositionMask); ptCurBit++) { + _BitVector[ptCurBit >> _IndexShiftCount] &= + ~(1 << (ptCurBit & _BitPositionMask)); + } + + for (; ptCurBit + 8*sizeof(PT) <= Count; ptCurBit += 8*sizeof(PT)) { + _BitVector[ptCurBit >> _IndexShiftCount] = 0; + } + + for (; ptCurBit < Count; ptCurBit++) { + _BitVector[ptCurBit >> _IndexShiftCount] &= + ~(1 << (ptCurBit & _BitPositionMask)); + } +} + +VOID +BITVECTOR::ToggleBit ( + IN PT Index, + IN PT Count + ) + +/*++ + +Routine Description: + + Toggle the supplied range of bits. + +Arguments: + + Index - Supplies the index at which to start toggling bits. + Count - Supplies the number of bits to toggle. + +Return Value: + + None. + +--*/ + +{ + REGISTER PT ptCurBit; + + DebugAssert( _BitVector != NULL ); + DebugAssert( Index + Count <= _PTCount * _BitsPerPT); + + while( Count-- ) { + ptCurBit = Index + Count; + if( IsBitSet( ptCurBit )) { + ResetBit( ptCurBit ); + } else { + SetBit( ptCurBit ); + } + } +} + + +PT +BITVECTOR::ComputeCountSet( + ) CONST + +/*++ + +Routine Description: + + Compute the number of bits that are set in the bitvector using a table + look up. + +Arguments: + + None. + +Return Value: + + PT - Returns the number of set bits. + +--*/ + +{ + REGISTER PCBYTE pbBV; + REGISTER PT i; + REGISTER PT BitsSet; + + // + // Cast the bitvector into a string of bytes. + // + + pbBV = ( PCBYTE ) _BitVector; + + // + // Initialize the count to zero. + // + + BitsSet = 0; + + // + // For all of the bytes in the bitvector. + // + + for (i = 0; i < _PTCount * sizeof( PT ); i++) { + + // + // Add the number of bits set in this byte to the total. + // + + BitsSet += _BitsSetLookUp[pbBV[ i ]]; + } + + return( BitsSet ); +} diff --git a/ulib/src/clasdesc.cxx b/ulib/src/clasdesc.cxx new file mode 100644 index 0000000..9025793 --- /dev/null +++ b/ulib/src/clasdesc.cxx @@ -0,0 +1,60 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + clasdesc.cxx + +Abstract: + + This module contains the definition for the CLASS_DESCRIPTOR class. + CLASS_DESCRIPTOR classes are special concrete classes derived from + OBJECT. They are special in that a single staic object of this class + exists for every other concrete class in the Ulib hierarchy. + CLASS_DESCRIPTORs allocate and maintain information that can be used + at run-time to determine the actual type of an object. + +Notes: + + The definitions for all concrete class' CLASS_DESCRIPTORs can be found + in the file ulib.cxx. + + See the Cast member function in ulibdef.hxx to see how dynamic casting + and CLASS_DESCRIPTORs work. + +--*/ + + +#include "ulib.hxx" + + +CLASS_DESCRIPTOR::CLASS_DESCRIPTOR ( + ) +{ +} + + +BOOLEAN +CLASS_DESCRIPTOR::Initialize ( + ) + +/*++ + +Routine Description: + + Initialize a CLASS_DESCRIPTOR object by initializing the class id. + +Arguments: + +Return Value: + + None. + +--*/ + +{ + _ClassID = ( ULONG_PTR ) &_ClassID; + return( TRUE ); +} + diff --git a/ulib/src/contain.cxx b/ulib/src/contain.cxx new file mode 100644 index 0000000..21b9d51 --- /dev/null +++ b/ulib/src/contain.cxx @@ -0,0 +1,27 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + contain.cxx + +Abstract: + + This module contains the definition for the CONTAINER class, the most + primitive, abstract class in the container sub-hierarchy. Given it's + abstract, prmitive nature there is minimal implementation at this point + in the hierarchy. + +--*/ + +#include "ulib.hxx" +#include "contain.hxx" + + +DEFINE_CONSTRUCTOR( CONTAINER, OBJECT ); + +CONTAINER::~CONTAINER( + ) +{ +} diff --git a/ulib/src/hmem.cxx b/ulib/src/hmem.cxx new file mode 100644 index 0000000..0de6838 --- /dev/null +++ b/ulib/src/hmem.cxx @@ -0,0 +1,258 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + hmem.cxx + +Abstract: + + Implementation of HMEM class. + +--*/ + + +#include "ulib.hxx" +#include "hmem.hxx" + + +DEFINE_CONSTRUCTOR( HMEM, MEM ); + +VOID +HMEM::Construct ( + ) +/*++ + +Routine Description: + + The is the contructor of HMEM. It initializes the private data + to reasonable default values. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _size = 0; + _real_buf = NULL; + _buf = NULL; +} + + + +HMEM::~HMEM( + ) +/*++ + +Routine Description: + + Destructor for HMEM. Frees up any memory used. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + + +BOOLEAN +HMEM::Initialize( + ) +/*++ + +Routine Description: + + This routine initializes HMEM to an initial state. All pointers or + information previously aquired from this object will become invalid. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + Destroy(); + + return TRUE; +} + + + +PVOID +HMEM::Acquire( + IN ULONG Size, + IN ULONG AlignmentMask + ) +/*++ + +Routine Description: + + This routine acquires the memory resources of this object for + the client to use. 'Size' bytes of data are guaranteed by + this routine or this routine will return NULL. After one call + of 'Acquire' has succeeded, all subsequent calls will return the + same memory, provided that the Size requested is within bounds + specified in the first successful call. The first successful call + will dictate the size and location of the memory which will be + available by calls to 'QuerySize' and 'GetBuf' respectively. + + A call to Initialize will invalidate all memory previously granted + by this object and enable the next call to Acquire to specify + any size. + +Arguments: + + Size - The number of bytes of memory expected. + AlignmentMask - Supplies the alignment required on the memory. + +Return Value: + + A valid pointer to 'Size' bytes of memory or NULL. + +--*/ +{ + if (_buf) { + if (Size <= _size && !(((ULONG_PTR) _buf)&AlignmentMask)) { + return _buf; + } else { + return NULL; + } + } + + _size = Size; + + if (!(_real_buf = MALLOC((UINT) (_size + AlignmentMask)))) { + return NULL; + } + + _buf = (PVOID) ((ULONG_PTR) ((PCHAR) _real_buf + AlignmentMask) & + (~(ULONG_PTR)AlignmentMask)); + + return _buf; +} + + + +BOOLEAN +HMEM::Resize( + IN ULONG NewSize, + IN ULONG AlignmentMask + ) +/*++ + +Routine Description: + + This method reallocates the object's buffer to a larger + chunk of memory. + +Arguments: + + NewSize -- supplies the new size of the buffer. + AlignmentMask -- supplies the alignment requirements on the memory. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method allocates a new buffer, copies the appropriate + amount of data to it, and then frees the old buffer. Clients + who use Resize must avoid caching pointers to the memory, but + must use GetBuf to find out where the memory is. + + If NewSize is smaller than the current buffer size, we keep + the current buffer. + + If this method fails, the object is left unchanged. + +--*/ +{ + PVOID NewBuffer; + PVOID NewRealBuffer; + + // First, check to see if our current buffer is big enough + // and has the correct alignment + // to satisfy the client. + + if( _buf && + NewSize <= _size && + !(((ULONG_PTR) _buf)&AlignmentMask)) { + + return TRUE; + } + + // We need to allocate a new chunk of memory. + + if( (NewRealBuffer = MALLOC((UINT) (NewSize + AlignmentMask))) == NULL ) { + + return FALSE; + } + + NewBuffer = (PVOID) ((ULONG_PTR) ((PCHAR) NewRealBuffer + AlignmentMask) & + (~(ULONG_PTR)AlignmentMask)); + + // Copy data from the old buffer to the new. Since we know + // that NewSize is greater than _size, we copy _size bytes. + + memset( NewBuffer, 0, (UINT) NewSize ); + memcpy( NewBuffer, _buf, (UINT) min(_size, NewSize) ); + + // Free the old buffer and set the object's private variables. + + FREE( _real_buf ); + _real_buf = NewRealBuffer; + _buf = NewBuffer; + _size = NewSize; + + return TRUE; +} + + +VOID +HMEM::Destroy( + ) +/*++ + +Routine Description: + + This routine frees the memory of a previous call to Acquire thus + invalidating all pointers to that memory and enabling future + Acquires to succeed. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _size = 0; + if (_real_buf) { + FREE(_real_buf); + _real_buf = NULL; + } + _buf = NULL; +} diff --git a/ulib/src/iterator.cxx b/ulib/src/iterator.cxx new file mode 100644 index 0000000..7c50c78 --- /dev/null +++ b/ulib/src/iterator.cxx @@ -0,0 +1,33 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + iterator.cxx + +Abstract: + + This contains the definitions for the non-inline member functions + for the abstract ITERATOR class. The only interesting aspect of this + implementation is that the destructor decrements the iterator count in + it's associated CONTAINER. This count, increment by the CONTAINER when + the ITERATOR is constructed, allows the associated CONTAINER to watch + for outstanding ITERATORs when it is destroyed - a situation which is + dangerous and surely a bug. + +--*/ + + + +#include "ulib.hxx" +#include "iterator.hxx" + + +DEFINE_CONSTRUCTOR( ITERATOR, OBJECT ); + +ITERATOR::~ITERATOR( + ) +{ +} + diff --git a/ulib/src/list.cxx b/ulib/src/list.cxx new file mode 100644 index 0000000..367064b --- /dev/null +++ b/ulib/src/list.cxx @@ -0,0 +1,336 @@ +#include "stdafx.h" + + +#include "ulib.hxx" +#include "list.hxx" +#include "listit.hxx" + + +DEFINE_CONSTRUCTOR( LIST, SEQUENTIAL_CONTAINER ); + +VOID +LIST::Construct( + ) +/*++ + +Routine Description: + + This routine initializes the object to a default state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _head = NULL; + _tail = NULL; + _count = 0; +} + + +VOID +LIST::Destroy( + ) +/*++ + +Routine Description: + + This routine returns the object to a default state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _head = NULL; + _tail = NULL; + _count = 0; +} + + + +LIST::~LIST( + ) +/*++ + +Routine Description: + + Destructor for LIST. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + + +BOOLEAN +LIST::Initialize( + ) +/*++ + +Routine Description: + + This routine initializes the object to a valid initial state. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + Destroy(); + + if (!_mem_block_mgr.Initialize(sizeof(OBJECT_LIST_NODE))) { + Destroy(); + return FALSE; + } + + return TRUE; +} + + +ULONG +LIST::QueryMemberCount( + ) CONST +/*++ + +Routine Description: + + This routine computes the number of members in the list. + +Arguments: + + None. + +Return Value: + + The number of members in the list. + +--*/ +{ + return _count; +} + + + +BOOLEAN +LIST::Put( + IN POBJECT Member + ) +/*++ + +Routine Description: + + This routine adds a new member to the end of the list. + +Arguments: + + Member - Supplies the element to add to the list. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + if (!_tail) { + + if (!(_head = _tail = (POBJECT_LIST_NODE) _mem_block_mgr.Alloc())) { + return FALSE; + } + + _head->next = _head->prev = NULL; + _head->data = Member; + + _count++; + return TRUE; + } + + if (!(_tail->next = (POBJECT_LIST_NODE) _mem_block_mgr.Alloc())) { + return FALSE; + } + + _tail->next->prev = _tail; + _tail = _tail->next; + + _tail->next = NULL; + _tail->data = Member; + + _count++; + return TRUE; +} + + +POBJECT +LIST::Remove( + IN OUT PITERATOR Position + ) +/*++ + +Routine Description: + + This routine removes the element at the specified position from the + list. The iterator is left pointing at the following element in + the list. + +Arguments: + + Position - Supplies a pointer to the element to remove. + +Return Value: + + A pointer to the element removed. + +--*/ +{ + POBJECT_LIST_NODE p; + PLIST_ITERATOR piter; + POBJECT pobj; + + DebugAssert(LIST_ITERATOR::Cast(Position)); + + if (!(piter = (PLIST_ITERATOR) Position) || !(p = piter->_current)) { + return NULL; + } + + if (p->next) { + p->next->prev = p->prev; + } + + if (p->prev) { + p->prev->next = p->next; + } + + if (_head == p) { + _head = p->next; + } + + if (_tail == p) { + _tail = p->prev; + } + + piter->_current = p->next; + + pobj = p->data; + + _mem_block_mgr.Free(p); + + _count--; + + return pobj; +} + + + +PITERATOR +LIST::QueryIterator( + ) CONST +/*++ + +Routine Description: + + This routine returns an iterator for this list. + +Arguments: + + None. + +Return Value: + + A valid iterator. + +--*/ +{ + PLIST_ITERATOR p; + + if (!(p = NEW LIST_ITERATOR)) { + return NULL; + } + + p->Initialize(this); + + return p; +} + + + +BOOLEAN +LIST::Insert( + IN OUT POBJECT Member, + IN OUT PITERATOR Position + ) +/*++ + +Routine Description: + + This routine inserts a new element before the specified position. + The 'Position' continues to point to the same element. + +Arguments: + + Member - Supplies the element to insert. + Position - Supplies the point at which to insert this member. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + POBJECT_LIST_NODE p, current; + + DebugAssert(LIST_ITERATOR::Cast(Position)); + + current = ((PLIST_ITERATOR) Position)->_current; + + if (!current) { + return Put(Member); + } + + if (!(p = (POBJECT_LIST_NODE) _mem_block_mgr.Alloc())) { + return FALSE; + } + + _count++; + p->data = Member; + + if (current == _head) { + _head = p; + } + + p->next = current; + p->prev = current->prev; + current->prev = p; + + if (p->prev) { + p->prev->next = p; + } + + return TRUE; +} diff --git a/ulib/src/listit.cxx b/ulib/src/listit.cxx new file mode 100644 index 0000000..27ce70d --- /dev/null +++ b/ulib/src/listit.cxx @@ -0,0 +1,37 @@ +#include "stdafx.h" + + +#include "ulib.hxx" +#include "listit.hxx" + + +DEFINE_CONSTRUCTOR(LIST_ITERATOR, ITERATOR); + + +DEFINE_CAST_MEMBER_FUNCTION(LIST_ITERATOR); + + +VOID LIST_ITERATOR::Reset() +{ + _current = NULL; +} + + +POBJECT LIST_ITERATOR::GetCurrent() +{ + return _current ? _current->data : NULL; +} + + +POBJECT LIST_ITERATOR::GetNext() +{ + _current = _current ? _current->next : _list->_head; + return _current ? _current->data : NULL; +} + + +POBJECT LIST_ITERATOR::GetPrevious() +{ + _current = _current ? _current->prev : _list->_tail; + return _current ? _current->data : NULL; +} diff --git a/ulib/src/mem.cxx b/ulib/src/mem.cxx new file mode 100644 index 0000000..a503eb4 --- /dev/null +++ b/ulib/src/mem.cxx @@ -0,0 +1,8 @@ +#include "stdafx.h" + + +#include "ulib.hxx" +#include "mem.hxx" + + +DEFINE_CONSTRUCTOR(MEM, OBJECT); diff --git a/ulib/src/membmgr.cxx b/ulib/src/membmgr.cxx new file mode 100644 index 0000000..39bf883 --- /dev/null +++ b/ulib/src/membmgr.cxx @@ -0,0 +1,411 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + membmgr.cxx + +--*/ + + + + + +#include "ulib.hxx" +#include "membmgr.hxx" +#include "iterator.hxx" + + +DEFINE_CONSTRUCTOR( STATIC_MEM_BLOCK_MGR, OBJECT ); + + +STATIC_MEM_BLOCK_MGR::~STATIC_MEM_BLOCK_MGR( + ) +/*++ + +Routine Description: + + Destructor for STATIC_MEM_BLOCK_MGR. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + +VOID +STATIC_MEM_BLOCK_MGR::Construct( + ) +/*++ + +Routine Description: + + This routine initializes the object to a default initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _heap = NULL; + _num_blocks = 0; + _block_size = 0; + _num_allocated = 0; + _next_alloc = 0; +} + + +VOID +STATIC_MEM_BLOCK_MGR::Destroy( + ) +/*++ + +Routine Description: + + This routine returns the object to a default initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + FREE(_heap); + _num_blocks = 0; + _block_size = 0; + _num_allocated = 0; + _next_alloc = 0; +} + + +BOOLEAN +STATIC_MEM_BLOCK_MGR::Initialize( + IN ULONG MemBlockSize, + IN ULONG NumBlocks + ) +/*++ + +Routine Description: + + This routine initializes this object to a usable initial state. + +Arguments: + + MemBlockSize - Supplies the number of bytes per mem block. + NumBlocks - Supplies the number of mem blocks to be + contained by this object. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + Destroy(); + + DebugAssert(MemBlockSize); + + if (!(_heap = (PCHAR) MALLOC(NumBlocks*MemBlockSize)) || + !_bitvector.Initialize(NumBlocks)) { + + Destroy(); + return FALSE; + } + + _num_blocks = NumBlocks; + _block_size = MemBlockSize; + + return TRUE; +} + + +PVOID +STATIC_MEM_BLOCK_MGR::Alloc( + ) +/*++ + +Routine Description: + + This routine allocates a single memory block and returns its + pointer. + +Arguments: + + None. + +Return Value: + + A pointer to a mem block. + +--*/ +{ + if (_num_allocated == _num_blocks) { + return NULL; + } + + for (;;) { + + if (!_bitvector.IsBitSet(_next_alloc)) { + + _bitvector.SetBit(_next_alloc); + _num_allocated++; + return &_heap[_next_alloc*_block_size]; + } + + _next_alloc = (_next_alloc + 1) % _num_blocks; + } +} + + +BOOLEAN +STATIC_MEM_BLOCK_MGR::Free( + OUT PVOID MemBlock + ) +/*++ + +Routine Description: + + This routine frees the given mem block for use by other clients. + +Arguments: + + MemBlock - Supplies a pointer to the mem block to free. + +Return Value: + + FALSE - The mem block was not freed. + TRUE - Success. + +--*/ +{ + ULONG i; + + if (!MemBlock) { + return TRUE; + } + + i = (ULONG)((PCHAR) MemBlock - _heap)/_block_size; + if (i >= _num_blocks) { + return FALSE; + } + + DebugAssert(((PCHAR) MemBlock - _heap)%_block_size == 0); + + _bitvector.ResetBit(i); + _num_allocated--; + _next_alloc = i; + return TRUE; +} + + +DEFINE_CONSTRUCTOR( MEM_BLOCK_MGR, OBJECT ); + + +VOID +MEM_BLOCK_MGR::Construct( + ) +/*++ + +Routine Description: + + This routine initializes the object to a default initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + memset(_static_mem_list, 0, 32*sizeof(PVOID)); +} + + +VOID +MEM_BLOCK_MGR::Destroy( + ) +/*++ + +Routine Description: + + This routine returns the object to a default initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + ULONG i; + + for (i = 0; _static_mem_list[i]; i++) { + DELETE(_static_mem_list[i]); + } +} + + + +MEM_BLOCK_MGR::~MEM_BLOCK_MGR( + ) +/*++ + +Routine Description: + + Destructor for MEM_BLOCK_MGR. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + + +BOOLEAN +MEM_BLOCK_MGR::Initialize( + IN ULONG MemBlockSize, + IN ULONG InitialNumBlocks + ) +/*++ + +Routine Description: + + This routine initializes the class to a valid initial state. + +Arguments: + + MemBlockSize - Specifies the size of the memory blocks to + be allocated from this object. + InitialNumBlocks - Specifies the initial number of blocks + to be allocated by this object. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + Destroy(); + + if (!(_static_mem_list[0] = NEW STATIC_MEM_BLOCK_MGR) || + !_static_mem_list[0]->Initialize(MemBlockSize, InitialNumBlocks)) { + + Destroy(); + return FALSE; + } + + return TRUE; +} + + + +PVOID +MEM_BLOCK_MGR::Alloc( + ) +/*++ + +Routine Description: + + This routine allocates a mem blocks and returns its pointer. + +Arguments: + + None. + +Return Value: + + A pointer to a mem block. + +--*/ +{ + ULONG i; + PVOID r; + + for (i = 0; _static_mem_list[i]; i++) { + if (r = _static_mem_list[i]->Alloc()) { + return r; + } + } + + // At this point all of the current buffers are full so + // start another one. + + if (!(_static_mem_list[i] = NEW STATIC_MEM_BLOCK_MGR) || + !_static_mem_list[i]->Initialize( + _static_mem_list[i - 1]->QueryBlockSize(), + 2*_static_mem_list[i - 1]->QueryNumBlocks())) { + + DELETE(_static_mem_list[i]); + return NULL; + } + + return _static_mem_list[i]->Alloc(); +} + + + +BOOLEAN +MEM_BLOCK_MGR::Free( + IN OUT PVOID MemPtr + ) +/*++ + +Routine Description: + + This routine frees the given memory block. + +Arguments: + + MemPtr - Supplies a pointer to the buffer to free. + +Return Value: + + This function returns TRUE if the memory was successfully + freed. + +--*/ +{ + ULONG i; + + for (i = 0; _static_mem_list[i]; i++) { + if (_static_mem_list[i]->Free(MemPtr)) { + return TRUE; + } + } + + return FALSE; +} diff --git a/ulib/src/membmgr2.cxx b/ulib/src/membmgr2.cxx new file mode 100644 index 0000000..18bd410 --- /dev/null +++ b/ulib/src/membmgr2.cxx @@ -0,0 +1,224 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + membmgr2.cxx + +--*/ + + +#include "ulib.hxx" +#include "membmgr2.hxx" + + +DEFINE_CONSTRUCTOR( MEM_ALLOCATOR, OBJECT ); + +VOID +MEM_ALLOCATOR::Construct( + ) +/*++ + +Routine Description: + + This method is the worker function for object construction. + +Arguments: + + N/A + +Returns: + + N/A + +--*/ +{ + _head_ptr = _next_free_ptr = NULL; + _incremental_size = _free_space_in_current_block = 0; + _max_mem_use = 0; + _mem_use = 0; +} + +VOID +MEM_ALLOCATOR::Destroy( + ) +/*++ + +Routine Description: + + This method cleans up the object in preparation for destruction + or reinitialization. + +Arguments: + + N/A + +Returns: + + N/A + +--*/ +{ + PVOID p; + + if (_head_ptr) { + DebugAssert(_incremental_size != 0); + } + + // free up the entire linked memory block + + while (_head_ptr) { + p = ((PUCHAR)_head_ptr) + _incremental_size - sizeof(PVOID *); + p = (*(PVOID *)p); + FREE(_head_ptr); + _head_ptr = p; + } + + _next_free_ptr = NULL; + _incremental_size = _free_space_in_current_block = 0; + _max_mem_use = 0; + _mem_use = 0; +} + +MEM_ALLOCATOR::~MEM_ALLOCATOR( + ) +/*++ + +Routine Description: + + This method un-initialize the class object. + +Arguments: + + N/A + +Returns: + + N/A + +--*/ +{ + Destroy(); +} + +BOOLEAN +MEM_ALLOCATOR::Initialize( + IN ULONG64 MaximumMemoryToUse, + IN ULONG IncrementalBlockSize + ) +/*++ + +Routine Description: + + This method initialize this class object. + +Arguments: + + IncrementalBlockSize + -- Supplies the block size to allocate + whenever there is a need to grow the memory. + +Returns: + + TRUE if successful; otherwise, FALSE. + +--*/ +{ + Destroy(); + + if (IncrementalBlockSize && MaximumMemoryToUse > 0) { + if (MaximumMemoryToUse >= (((ULONG64)-1)-sizeof(PVOID*))) + _incremental_size = IncrementalBlockSize; + else if (IncrementalBlockSize > (MaximumMemoryToUse+sizeof(PVOID*))) + _incremental_size = (ULONG)(MaximumMemoryToUse+sizeof(PVOID*)); + else + _incremental_size = IncrementalBlockSize; + _max_mem_use = MaximumMemoryToUse; + return TRUE; + } else { + return FALSE; + } +} + +PVOID +MEM_ALLOCATOR::Allocate( + IN ULONG SizeInBytes + ) +/*++ + +Routine Description: + + This method allocates a chunk of memory from the memory block. + +Arguments: + + Size -- Supplies the size of the buffer needed + +Returns: + + Pointer to the block if successful + NULL if failure + +--*/ +{ + PVOID p; + + // + // make sure request buffer is smaller than the max block size possible + // + + if ((SizeInBytes + sizeof(PVOID *)) > _incremental_size) + return NULL; + + if (_head_ptr == NULL) { + + DebugAssert(_mem_use == 0); + + // + // first time, so allocate a buffer and initializes all class variables + // + _head_ptr = MALLOC(_incremental_size); + if (_head_ptr == NULL) + return NULL; + _mem_use = _incremental_size; + _free_space_in_current_block = _incremental_size - sizeof(PVOID *); + *(PVOID *)(((PUCHAR)_head_ptr) + _free_space_in_current_block) = NULL; + _free_space_in_current_block -= SizeInBytes; + _next_free_ptr = ((PUCHAR)_head_ptr + SizeInBytes); + return _head_ptr; + + } else { + // + // Check to see if there is enough space left + // + if (SizeInBytes <= _free_space_in_current_block) { + // + // Enough space from current block + // + p = _next_free_ptr; + _free_space_in_current_block -= SizeInBytes; + _next_free_ptr = ((PUCHAR)_next_free_ptr) + SizeInBytes; + return p; + } else { + + if (_mem_use >= _max_mem_use) + return NULL; // reached the limit + + p = MALLOC(_incremental_size); + if (p == NULL) + return NULL; + + _mem_use = _mem_use - _free_space_in_current_block + _incremental_size; + _next_free_ptr = ((PUCHAR)_next_free_ptr) + _free_space_in_current_block; + *(PVOID *)_next_free_ptr = p; + _free_space_in_current_block = _incremental_size - sizeof(PVOID *); + *(PVOID *)(((PUCHAR)_head_ptr) + _free_space_in_current_block) = NULL; + _free_space_in_current_block -= SizeInBytes; + _next_free_ptr = ((PUCHAR)p + SizeInBytes); + return p; + } + } + DebugAssert(FALSE); + return NULL; // should never get here +} diff --git a/ulib/src/message.cxx b/ulib/src/message.cxx new file mode 100644 index 0000000..1aad402 --- /dev/null +++ b/ulib/src/message.cxx @@ -0,0 +1,134 @@ +#include "stdafx.h" + +#include // std::cout + +#include "ulib.hxx" +#include "message.hxx" + +#include "hmem.hxx" + +DEFINE_CONSTRUCTOR(MESSAGE, OBJECT ); + + +MESSAGE::~MESSAGE( + ) +/*++ + +Routine Description: + + Destructor for MESSAGE. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + +VOID +MESSAGE::Construct( + ) +/*++ + +Routine Description: + + This routine initializes the object to a default initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ +} + + +VOID +MESSAGE::Destroy( + ) +/*++ + +Routine Description: + + This routine returns the object to a default initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ +} + + + +BOOLEAN +MESSAGE::Initialize( + ) +/*++ + +Routine Description: + + This routine initializes the class to a valid initial state. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + Destroy(); + return TRUE; +} + + +void MESSAGE::Out(const char* str) +{ + std::cout << str << "\n"; +} + +void MESSAGE::Out(const char* str1, const char* str2) +{ + std::cout << str1 << str2 << "\n"; +} +void MESSAGE::Out(const char* str1, const char* str2, const char* str3) +{ + std::cout << str1 << str2 << str3 << "\n"; +} + +void MESSAGE::Out(const char* str, LONGLONG number) +{ + std::cout << str << number << "\n"; +} + +void MESSAGE::Out(const char* str1, LONGLONG number, const char* str2) +{ + std::cout << str1 << number << str2 << "\n"; +} + +void MESSAGE::Out(const char* str1, LONGLONG number1, const char* str2, LONGLONG number2, const char* str3) +{ + std::cout << str1 << number1 << str2 << number2 << str3 << "\n"; +} + +void MESSAGE::Out(const char* str1, LONGLONG number1, const char* str2, LONGLONG number2, const char* str3, LONGLONG number3, const char* str4) +{ + std::cout << str1 << number1 << str2 << number2 << str3 << number3 << str4 << "\n"; +} + diff --git a/ulib/src/object.cxx b/ulib/src/object.cxx new file mode 100644 index 0000000..498f09c --- /dev/null +++ b/ulib/src/object.cxx @@ -0,0 +1,76 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + object.cxx + +Abstract: + + This module contains the definitions for the non-inline member functions + for the class OBJECT, the root of the Ulib hierarchy. OBJECT's + constructor merely initializes it's internal CLASS_DESCRIPTOR to point + to the static descriptor for the class at the beginning of this + construction chain. + +--*/ + +#include "ulib.hxx" + +OBJECT::OBJECT( + ) +{ +} + +OBJECT::~OBJECT( + ) +{ +} + + +LONG +OBJECT::Compare ( + IN PCOBJECT Object + ) CONST + +/*++ + +Routine Description: + + Compare two objects based on their CLASS_ID's + +Arguments: + + Object - Supplies the object to compare with. + +Return Value: + + LONG < 0 - supplied OBJECT has a higher CLASS_ID + == 0 - supplied object has same CLASS_ID + > 0 - supplied OBJECT has a lower CLASS_ID + +Notes: + + It is expected that derived classes will overload this method and supply + an implementation that is more meaningful (i.e. class specific). This + implementation is ofeered as a default but is fairly meaningless as + CLASS_IDs are allocated randomly (but uniquely) at run-time by + CLASS_DESCRIPTORs. Therefore comparing two CLASS_IDs is not very + interesting (e.g. it will help if an ORDERED_CONTAINER of homogenous + objects is sorted). + +--*/ + +{ + LONG r; + + DebugPtrAssert( Object ); + + r = (LONG)(QueryClassId() - Object->QueryClassId()); + + return r ? r : (LONG)(this - Object); +} + + +DEFINE_OBJECT_DBG_FUNCTIONS; diff --git a/ulib/src/path.cxx b/ulib/src/path.cxx new file mode 100644 index 0000000..3229cf0 --- /dev/null +++ b/ulib/src/path.cxx @@ -0,0 +1,958 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + path.cxx + +Abstract: + + This contains the implementation for all methods handling file + path names. These are needed for use with an file i/o or the + FILE and DIR objects. + +--*/ + +extern "C" { + #include +} +#include "ulib.hxx" +#include "wstring.hxx" +#include "path.hxx" +#include "system.hxx" + + +typedef enum _SPECIAL_DEVICES { + LPT, + COM, + CON, + PRN, + AUX, + LAST_SPECIAL_DEVICE +} SPECIAL_DEVICES; + + +// +// Static member data. +// + +STATIC PWSTRING _SlashString; +STATIC PWSTRING _SpecialDevices[ LAST_SPECIAL_DEVICE ]; + +#define DELIMITER_STRING "\\" +#define DELIMITER_CHAR ((WCHAR)'\\') + + +BOOLEAN +PATH::Initialize ( + ) + +/*++ + +Routine Description: + + Perform global initialization of the PATH class. + +Arguments: + + None. + +Return Value: + + BOOLEAN - Returns TRUE if global initialization was succesful + +Notes: + + Global initialization should be interpreted as class (rather than object) + initialization. This routine should be called by ALL other + PATH::Initialize member functions. + + Current this routine: + + - constructs and initializes _SlashString, a WSTRING that + contains a '\' + +--*/ + +{ + STATIC BOOLEAN fInit = FALSE; + + if (!fInit) + { + if ( (( _SlashString = NEW DSTRING) != NULL) && + ((_SpecialDevices[LPT] = NEW DSTRING) != NULL) && + ((_SpecialDevices[COM] = NEW DSTRING) != NULL) && + ((_SpecialDevices[CON] = NEW DSTRING) != NULL) && + ((_SpecialDevices[PRN] = NEW DSTRING) != NULL) && + ((_SpecialDevices[AUX] = NEW DSTRING) != NULL) && + _SlashString->Initialize( DELIMITER_STRING ) && + _SpecialDevices[LPT]->Initialize( "LPT" ) && + _SpecialDevices[COM]->Initialize( "COM" ) && + _SpecialDevices[CON]->Initialize( "CON" ) && + _SpecialDevices[PRN]->Initialize( "PRN" ) && + _SpecialDevices[AUX]->Initialize( "AUX" ) + ) + { + return fInit = TRUE; + } + } + + return fInit; +} + + +BOOLEAN +PATH::EndsWithDelimiter ( + ) CONST + +/*++ + +Routine Description: + + Returns TRUE if the path ends with slash + +Arguments: + + None. + +Return Value: + + BOOLEAN - Returns TRUE if the path ends with a slash + +--*/ +{ + + return ( _PathString.QueryChAt( _PathString.QueryChCount() - 1 ) == DELIMITER_CHAR ); + +} + + +CHNUM +PATH::QueryDeviceLen( + IN PWSTRING pString + ) CONST + +/*++ + +Routine Description: + + Find length in character of drive section + +Arguments: + + pString - Supplies the string to determine drive size. + +Return Value: + + CHNUM - Number of characters making up drive section. If no + drive section then the length is 0. + +--*/ +{ + CHNUM Position = 0; + CHNUM Position1; + SPECIAL_DEVICES Index; + ULONG tmp; + INT Pos; + LONG Number; + + + UNREFERENCED_PARAMETER( (void)this ); + + DebugPtrAssert( pString ); + + if ( pString->QueryChCount() > 0 ) { + + // + // Check for special device + // + Pos = (INT)pString->QueryChCount() - 1; + + while ( (Pos >= 0) && (pString->QueryChAt( (CHNUM)Pos ) != DELIMITER_CHAR) ) { + Pos --; + } + + Pos++; + + for (Index = LPT; Index < LAST_SPECIAL_DEVICE; + (tmp = (ULONG) Index, tmp++, Index = (SPECIAL_DEVICES) tmp) ) { + + if ( !pString->Stricmp( _SpecialDevices[Index], + (CHNUM)Pos ) ) { + + Position = (CHNUM)Pos + _SpecialDevices[Index]->QueryChCount(); + + // + // LPT && COM must be followed by a number; + // + if ( (Index == LPT) || (Index == COM) ) { + if ( Position >= pString->QueryChCount()) { + continue; + } + while ( (Position < pString->QueryChCount()) && + pString->QueryNumber( &Number, Position, 1 ) ) { + + Position++; + } + } + + if (Position >= pString->QueryChCount()) { + return Position; + } else if (pString->QueryChAt( Position ) == (WCHAR)':') { + return Position+1; + } + } + } + // + // Look for ':' + // + if ((Position = pString->Strchr((WCHAR)':')) != INVALID_CHNUM) { + return Position + 1; + } + + // + // check for leading "\\" + // + if ( pString->QueryChCount() > 1 && + pString->QueryChAt(0) == DELIMITER_CHAR && + pString->QueryChAt(1) == DELIMITER_CHAR) { + + // + // the device is a machine name - find the second backslash + // (start search after first double backsl). Note that this + // means that the device names if formed by the machine name + // and the sharepoint. + // + if ( ((Position = pString->Strchr( DELIMITER_CHAR, 2 )) != INVALID_CHNUM )) { + + Position1 = pString->Strchr( DELIMITER_CHAR, Position+1 ); + if ( Position1 == INVALID_CHNUM ) { + return pString->QueryChCount(); + } + return Position1; + + } + + // + // No backslash found, this is an invalid device + // + DebugAbort( "Invalid Device name" ); + + } + + + } + + return 0; +} + +VOID +PATH::SetPathState( + ) + +/*++ + +Routine Description: + + Sets the state information for the Path + +Arguments: + + None. + +Return Value: + + None. + +--*/ + +{ + + CHNUM chnLastSlash; + CHNUM chnLastDot; + CHNUM chnAcum; + CHNUM FirstSeparator; + + // + // Find the number of characters in the device name + // + chnAcum = _PathState.DeviceLen = QueryDeviceLen( &_PathString ); + + // + // Find the number of characters in the dirs portion of the path + // by searching for the last '\' + // + if ( _PathString.QueryChAt( chnAcum ) == DELIMITER_CHAR ) { + // + // Skip over the slash after the device name + // + FirstSeparator = 1; + chnAcum++; + + } else { + + FirstSeparator = 0; + } + + if ( chnAcum < _PathString.QueryChCount() ) { + + if (( chnLastSlash = _PathString.Strrchr( DELIMITER_CHAR, chnAcum )) != INVALID_CHNUM ) { + + // + // The dirs length is that character position less the length + // of the device + // + _PathState.DirsLen = chnLastSlash - _PathState.DeviceLen; + _PathState.SeparatorLen = 1; + + chnAcum += _PathState.DirsLen; + if ( FirstSeparator == 0 ) { + chnAcum++; + } + } else { + // + // There is no dirs portion of this path, but there is a name. + // + _PathState.DirsLen = FirstSeparator; + _PathState.SeparatorLen = 0; + + } + } else { + + // + // There is no name portion in this path, and the dirs portion + // might be empty (or consist solely of the delimiter ). + // + _PathState.DirsLen = FirstSeparator; + _PathState.SeparatorLen = 0; + } + + if ( chnAcum < _PathString.QueryChCount() ) { + + // + // Find the number of characters in the name portion of the path + // by searching for the last '.' + // + if (( chnLastDot = _PathString.Strrchr( ( WCHAR )'.', + chnAcum )) != INVALID_CHNUM ) { + + _PathState.BaseLen = chnLastDot - chnAcum; + + chnAcum += _PathState.BaseLen + 1; + + _PathState.ExtLen = _PathString.QueryChCount() - chnAcum; + + _PathState.NameLen = _PathState.BaseLen + _PathState.ExtLen + 1; + + + } else { + + // + // There is no last '.' so the name length is the length of the + // component from the last '\' to the end of the path (adjusted + // for zero base) and there is no extension. + // + + _PathState.NameLen = _PathString.QueryChCount() - chnAcum; + _PathState.BaseLen = _PathState.NameLen; + _PathState.ExtLen = 0; + + } + } else { + + // + // There is no name part + // + _PathState.NameLen = 0; + _PathState.BaseLen = 0; + _PathState.ExtLen = 0; + + } + + + // + // The prefix length is the sum of the device and dirs + // + _PathState.PrefixLen = _PathState.DeviceLen + _PathState.DirsLen; + + // + // If The device refers to a drive, uppercase it. (Done for + // compatibility with some DOS apps ). + // + if ( _PathState.DeviceLen == 2 ) { + _PathString.Strupr( 0, 1 ); + } + +} + +DEFINE_CONSTRUCTOR( PATH, OBJECT ); + +DEFINE_CAST_MEMBER_FUNCTION( PATH ); + +VOID +PATH::Construct ( + ) + +{ + _PathState.BaseLen = 0; + _PathState.DeviceLen= 0; + _PathState.DirsLen = 0; + _PathState.ExtLen = 0; + _PathState.NameLen = 0; + _PathState.PrefixLen= 0; + + _PathBuffer[0] = 0; + _PathString.Initialize(_PathBuffer, MAX_PATH); + +#if DBG==1 + _Signature = PATH_SIGNATURE; +#endif + +} + + +BOOLEAN +PATH::Initialize( + IN PCWSTR InitialPath, + IN BOOLEAN Canonicalize + ) + +/*++ + +Routine Description: + + Initialize a PATH object with the supplied string. No validation + on the given path is performed unless 'Canonicalize' is set to TRUE. + +Arguments: + + InitialPath - Supplies a zero terminated string + Canonicalize- Supplies a flag, which if TRUE indicates that the PATH + should be canoicalized at initialization time (i.e. now) + +Return Value: + + BOOLEAN - Returns TRUE if the PATH was succesfully initialized. + +--*/ + +{ + PWSTR filepart; + DWORD CharsInPath; + + + DebugPtrAssert( InitialPath ); + + // + // Perform global (class) initialization + // + if (!Initialize()) { + DebugAbort( "Class initialization failed" ); + return FALSE; + } + + // Avoid copies during Strcat by making this a reasonable size. + if (!_PathString.NewBuf(MAX_PATH - 1)) { + return FALSE; + } + + if ( Canonicalize ) { + + if (!_PathString.NewBuf(MAX_PATH - 1) || + !(CharsInPath = GetFullPathName((LPWSTR) InitialPath, + MAX_PATH, + (LPWSTR) _PathString.GetWSTR(), + &filepart)) || + CharsInPath > MAX_PATH) { + + return FALSE; + } + + _PathString.SyncLength(); + + SetPathState( ); +#if DBG==1 + _Initialized = TRUE; +#endif + return TRUE; + + } else if( ((PWSTRING) &_PathString)->Initialize( InitialPath )) { + + SetPathState( ); +#if DBG==1 + _Initialized = TRUE; +#endif + return TRUE; + } + + return FALSE; +} + + +BOOLEAN +PATH::Initialize( + IN PCWSTRING InitialPath, + IN BOOLEAN Canonicalize + + ) +{ + DebugPtrAssert( InitialPath ); + + return Initialize( InitialPath->GetWSTR(), Canonicalize ); +} + + +BOOLEAN +PATH::Initialize ( + IN PCPATH InitialPath, + IN BOOLEAN Canonicalize + ) + +{ + DebugPtrAssert( InitialPath ); + + return Initialize( InitialPath->GetPathString()->GetWSTR(), Canonicalize ); +} + + +PATH::~PATH ( + ) + +{ +} + + +BOOLEAN +PATH::AppendBase ( + IN PCWSTRING Base, + IN BOOLEAN Absolute + ) + +/*++ + +Routine Description: + + Append the supplied name to the end of this PATH. + +Arguments: + + Base - Supplies the string to be appended. + Absolute - Supplies a flag which if TRUE means that the path must + be absolute. + +Return Value: + + BOOLEAN - Returns TRUE if the '\' and the supplied string was succesfully + appended. + +--*/ + +{ + BOOLEAN AppendedSlash = FALSE; + + DebugPtrAssert( Base ); + DebugAssert( _Initialized ); + + // + // If the path does not consist of only a drive letter followed by a + // colon, we might need to add a '\' + // + if ( _PathString.QueryChCount() > 0 ) { + if ( !(( _PathState.DeviceLen == _PathString.QueryChCount()) && + ( _PathString.QueryChAt( _PathState.DeviceLen - 1) == (WCHAR)':')) || + Absolute ) { + + + if ( _PathString.QueryChAt( _PathString.QueryChCount() - 1 ) != (WCHAR)'\\' ) { + + if ( !_PathString.Strcat( _SlashString )) { + return FALSE; + } + + AppendedSlash = TRUE; + } + } + } + + // + // Append the base + // + if ( _PathString.Strcat( Base )) { + SetPathState(); + return TRUE; + } + + // + // Could not append base, remove the slash if we appended it + // + if ( AppendedSlash ) { + TruncateBase(); + } + + return FALSE; +} + + +PPATH +PATH::QueryFullPath( + ) CONST + +/*++ + +Routine Description: + +Arguments: + + None. + +Return Value: + + PPATH + +--*/ + +{ + + + REGISTER PPATH pFullPath; + REGISTER PWSTRING pFullPathString; + + DebugAssert( _Initialized ); + + // + // If the full path name string for this PATH can not be queried + // or a new PATH, representing the full path, can not be constructed + // return NULL. + // + + if ((( pFullPathString = QueryFullPathString( )) == NULL ) || + (( pFullPath = NEW PATH ) == NULL )) { + + return NULL; + } + + // + // If the new, full path, can not be initialized, delete it. + // + + if( ! ( pFullPath->Initialize( pFullPathString ))) { + + DELETE( pFullPath ); + } + + // + // Delete the full path string and return a pointer to the new, full path + // (note that the pointer may be NULL). + // + + DELETE( pFullPathString ); + + return pFullPath ; +} + + +PWSTRING +PATH::QueryFullPathString ( + ) CONST +{ + + LPWSTR pszName; + + PWSTRING pwcFullPathString; + WSTR szBufferSrc[ MAX_PATH ]; + WSTR szBufferTrg[ MAX_PATH ]; + + DebugAssert( _Initialized ); + + if( (pwcFullPathString = NEW DSTRING ()) != NULL ) { + + if ( _PathString.QueryWSTR( 0, TO_END, szBufferSrc, MAX_PATH ) ) { + + if (GetFullPathName( szBufferSrc,MAX_PATH,szBufferTrg,&pszName)) { + + if (pwcFullPathString->Initialize(szBufferTrg)) { + + return pwcFullPathString; + + } + } + } + } + + DELETE( pwcFullPathString ); + + return NULL; + +} + + +BOOLEAN +PATH::SetDevice ( + IN PCWSTRING NewDevice + ) +{ + DebugAssert( _Initialized ); + DebugPtrAssert( NewDevice ); + + if (_PathState.DeviceLen) { + if (!_PathString.Replace(QueryDeviceStart(), _PathState.DeviceLen, + NewDevice)) { + + return FALSE; + } + } else { + if (!_PathString.Strcat(NewDevice)) { + return FALSE; + } + } + + SetPathState(); + + return TRUE; +} + + +BOOLEAN +PATH::SetBase ( + IN PCWSTRING NewBase + ) +{ + DebugAssert( _Initialized ); + DebugPtrAssert( NewBase ); + + if (_PathState.BaseLen) { + + if (!_PathString.Replace(QueryBaseStart(), _PathState.BaseLen, + NewBase)) { + + return FALSE; + } + + } else { + + if (!_PathString.Strcat(NewBase)) { + return FALSE; + } + } + + SetPathState(); + + return TRUE; +} + +BOOLEAN +PATH::SetExt ( + IN PCWSTRING NewExt + ) +{ + DebugAssert( _Initialized ); + DebugPtrAssert( NewExt ); + + if (_PathState.ExtLen) { + + if (!_PathString.Replace(QueryExtStart(), _PathState.ExtLen, + NewExt)) { + + return FALSE; + } + + } else { + + if (!_PathString.Strcat(NewExt)) { + return FALSE; + } + } + + SetPathState(); + + return TRUE; +} + + +BOOLEAN +PATH::TruncateBase ( + ) + +/*++ + +Routine Description: + + This routine truncates the path after the prefix portion. + +Arguments: + + None. + +Return Value: + + BOOLEAN - Returns TRUE if the base existed and was succesfully removed. + +--*/ + +{ + DebugAssert( _Initialized ); + + // If this is the root then the prefix len will include the \. + // If not, then it won't. Either way. Truncate this string to + // the prefix length. + + _PathString.Truncate( _PathState.PrefixLen ); + + SetPathState(); + + return TRUE; +} + + + + +PPATH +PATH::QueryPath( + ) CONST + +/*++ + +Routine Description: + +Arguments: + + None. + +Return Value: + + PPATH + +--*/ + +{ + + + REGISTER PPATH pPath; + + DebugAssert( _Initialized ); + + if (( pPath = NEW PATH ) == NULL ) { + return NULL; + } + + // + // If the new path can not be initialized, delete it. + // + if( ! ( pPath->Initialize( GetPathString()->GetWSTR(), FALSE ))) { + + DELETE( pPath ); + } + + return pPath ; +} + + +BOOLEAN +PATH::ExpandWildCards( + IN OUT PWSTRING pStr1, + IN OUT PWSTRING pStr2 + ) +/*++ + +Routine Description: + + Expands any wildcards in string 2 to match the equivalent characters in + string 1. Used by QueryWildCardExpansion(). + +Arguments: + + Str1 - A pointer to the 'base' string + Str2 - A pointer to the string to be expanded + +Return Value: + + TRUE if expansion was successful. + +--*/ +{ + CHNUM idx; + + + UNREFERENCED_PARAMETER( (void)this); + + // Deal with the * wild card first... + // + // Note: This method will ignore, even remove, any characters after the + // '*' in string 2. This is to comform with the behavior of Dos... + // + if( ( idx = pStr2->Strchr( '*' ) ) != INVALID_CHNUM ) { + if( idx > pStr1->QueryChCount() ) { + return( FALSE ); + } + if( idx == pStr1->QueryChCount() ) { + pStr2->Truncate( idx ); + } else { + pStr2->Replace( idx, TO_END, pStr1, idx, TO_END ); + } + } + + // Now convert any '?' in the base + while( ( idx = pStr2->Strchr( '?' ) ) != INVALID_CHNUM ) { + // Make sure that the wild card is within the limits of the + // base string... + if( idx >= pStr1->QueryChCount() ) { + return( FALSE ); + } + pStr2->SetChAt( pStr1->QueryChAt( idx ), idx ); + } + return( TRUE ); +} + + + +PWSTRING +PATH::QueryRoot ( + ) + +/*++ + +Routine Description: + + Returns a string that contains the canonicalized name of the root + directory (device name followed by "\"). + + QueryRoot returns NULL if there is no device component part of + this path. In other words it may be necessary to canonicalize + the path before having access to the Root. + +Arguments: + + none + +Return Value: + + Pointer to a WSTRING that contains the root directory in its + canonicalized form. + +--*/ + +{ + PWSTRING Root; + + if( _PathState.DeviceLen == 0 ) { + return( NULL ); + } + Root = NEW( DSTRING ); + + if (Root == NULL) { + DebugPrint("ULIB: Out of memory\n"); + return NULL; + } + + if( !Root->Initialize( &_PathString, 0, _PathState.DeviceLen ) ) { + DELETE( Root ); + return( NULL ); + } + + Root->Strcat( _SlashString ); + return( Root ); +} + + + diff --git a/ulib/src/seqcnt.cxx b/ulib/src/seqcnt.cxx new file mode 100644 index 0000000..991b162 --- /dev/null +++ b/ulib/src/seqcnt.cxx @@ -0,0 +1,69 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + seqcnt.cxx + +Abstract: + + This module contains the definition for the SEQUENTIAL_CONTAINER class. + There exists no implementation, merely a constructor that acts as a link + between derived classes as SEQUENTIAL_CONTAINERs base class CONTAINER. + +--*/ + + +#include "ulib.hxx" +#include "iterator.hxx" +#include "seqcnt.hxx" + + +DEFINE_CONSTRUCTOR( SEQUENTIAL_CONTAINER, CONTAINER ); + +SEQUENTIAL_CONTAINER::~SEQUENTIAL_CONTAINER( + ) +{ +} + + +BOOLEAN +SEQUENTIAL_CONTAINER::DeleteAllMembers( + ) +/*++ + +Routine Description: + + This routine not only removes all members from the container + class, but also deletes all the objects themselves. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + PITERATOR iter; + POBJECT pobj; + + if (!(iter = QueryIterator())) + { + return FALSE; + } + + iter->GetNext(); + while (iter->GetCurrent()) + { + pobj = Remove(iter); + DELETE(pobj); + } + DELETE(iter); + + return TRUE; +} diff --git a/ulib/src/sortcnt.cxx b/ulib/src/sortcnt.cxx new file mode 100644 index 0000000..b490835 --- /dev/null +++ b/ulib/src/sortcnt.cxx @@ -0,0 +1,27 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + sortcnt.cxx + +Abstract: + + This module contains the definition for the SORTABLE_CONTAINER class. + +--*/ + + +#include "ulib.hxx" +#include "iterator.hxx" +#include "sortcnt.hxx" + + +DEFINE_CONSTRUCTOR( SORTABLE_CONTAINER, SEQUENTIAL_CONTAINER ); + +SORTABLE_CONTAINER::~SORTABLE_CONTAINER( + ) +{ +} + diff --git a/ulib/src/system.cxx b/ulib/src/system.cxx new file mode 100644 index 0000000..f1203e2 --- /dev/null +++ b/ulib/src/system.cxx @@ -0,0 +1,76 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + system.cxx + +Abstract: + + This contains the implementation for all methods communicating + with the operating system. + +--*/ + + + + +#include "ulib.hxx" +#include "system.hxx" + +extern "C" { + #include + #include + //#include "winbasep.h" +} + +#include "path.hxx" +#include "wstring.hxx" + + + +BOOLEAN +SYSTEM::QueryCurrentDosDriveName( + OUT PWSTRING DosDriveName + ) +/*++ + +Routine Description: + + This routine returns the name of the current drive. + +Arguments: + + DosDriveName - Returns the name of the current drive. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + PATH path; + PWSTRING p; + + if (!path.Initialize( (LPWSTR)L"foo", TRUE)) { + return FALSE; + } + + if (!(p = path.QueryDevice())) { + return FALSE; + } + + if (!DosDriveName->Initialize(p)) { + return FALSE; + } + + DELETE(p); + + return TRUE; +} + + + + diff --git a/ulib/src/ulib.cxx b/ulib/src/ulib.cxx new file mode 100644 index 0000000..2ab2f65 --- /dev/null +++ b/ulib/src/ulib.cxx @@ -0,0 +1,179 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + ulib.cxx + +Abstract: + + This module contains run-time, global support for the ULIB class library. + This support includes: + + - creation of CLASS_DESCRIPTORs + - Global objects + - Ulib to Win32 API mapping functions + +--*/ + + + +#include "ulib.hxx" + + +#include "system.hxx" +#include "array.hxx" +#include "arrayit.hxx" +#include "bitvect.hxx" +#include "message.hxx" +#include "wstring.hxx" +#include "path.hxx" + +#include + + + +// +// Declare class descriptors for all classes. +// + +DECLARE_CLASS( CLASS_DESCRIPTOR ); +DECLARE_CLASS( ARRAY ); +DECLARE_CLASS( ARRAY_ITERATOR ); +DECLARE_CLASS( BITVECTOR ); +DECLARE_CLASS( CONTAINER ); +DECLARE_CLASS( DSTRING ); +DECLARE_CLASS( FSTRING ); +DECLARE_CLASS( HMEM ); +DECLARE_CLASS( ITERATOR ); +DECLARE_CLASS( LIST ); +DECLARE_CLASS( LIST_ITERATOR ); +DECLARE_CLASS( MEM ); +DECLARE_CLASS( MESSAGE ); +DECLARE_CLASS( OBJECT ); +DECLARE_CLASS( PATH ); +DECLARE_CLASS( SEQUENTIAL_CONTAINER ); +DECLARE_CLASS( SORTABLE_CONTAINER ); +DECLARE_CLASS( WSTRING ); +DECLARE_CLASS( STATIC_MEM_BLOCK_MGR ); +DECLARE_CLASS( MEM_ALLOCATOR ); +DECLARE_CLASS( MEM_BLOCK_MGR ); + + + +BOOLEAN +UlibDefineClassDescriptors( +) + +/*++ + +Routine Description: + + Defines all the class descriptors used by ULIB + +Arguments: + + None. + +Return Value: + + BOOLEAN - Returns TRUE if all class descriptors were succesfully + constructed and initialized. + +--*/ + +{ + BOOLEAN Success = TRUE; + + if (Success && + DEFINE_CLASS_DESCRIPTOR(ARRAY) && + DEFINE_CLASS_DESCRIPTOR(ARRAY_ITERATOR) && + DEFINE_CLASS_DESCRIPTOR(BITVECTOR) && + DEFINE_CLASS_DESCRIPTOR(CONTAINER) && + DEFINE_CLASS_DESCRIPTOR(DSTRING) && + DEFINE_CLASS_DESCRIPTOR(ITERATOR) && + DEFINE_CLASS_DESCRIPTOR(LIST) && + DEFINE_CLASS_DESCRIPTOR(LIST_ITERATOR) && + DEFINE_CLASS_DESCRIPTOR(PATH) && + DEFINE_CLASS_DESCRIPTOR(SEQUENTIAL_CONTAINER) && + DEFINE_CLASS_DESCRIPTOR(SORTABLE_CONTAINER) && + DEFINE_CLASS_DESCRIPTOR(WSTRING) && + DEFINE_CLASS_DESCRIPTOR(MESSAGE) ) { + } + else { + Success = FALSE; + } + + if (Success && + DEFINE_CLASS_DESCRIPTOR(FSTRING) && + DEFINE_CLASS_DESCRIPTOR(HMEM) && + DEFINE_CLASS_DESCRIPTOR(STATIC_MEM_BLOCK_MGR) && + DEFINE_CLASS_DESCRIPTOR(MEM_ALLOCATOR) && + DEFINE_CLASS_DESCRIPTOR(MEM_BLOCK_MGR) ) { + } + else { + Success = FALSE; + } + + if (Success && + DEFINE_CLASS_DESCRIPTOR(MEM) ) { + } + else { + Success = FALSE; + } + + + if (!Success) { + DebugPrint("Could not initialize class descriptors!"); + } + return Success; + +} + +BOOLEAN +UlibUndefineClassDescriptors( +) + +/*++ + +Routine Description: + + Defines all the class descriptors used by ULIB + +Arguments: + + None. + +Return Value: + + BOOLEAN - Returns TRUE if all class descriptors were successfully + constructed and initialized. + +--*/ + +{ + UNDEFINE_CLASS_DESCRIPTOR(ARRAY); + UNDEFINE_CLASS_DESCRIPTOR(ARRAY_ITERATOR); + UNDEFINE_CLASS_DESCRIPTOR(BITVECTOR); + UNDEFINE_CLASS_DESCRIPTOR(CONTAINER); + UNDEFINE_CLASS_DESCRIPTOR(DSTRING); + UNDEFINE_CLASS_DESCRIPTOR(ITERATOR); + UNDEFINE_CLASS_DESCRIPTOR(LIST); + UNDEFINE_CLASS_DESCRIPTOR(LIST_ITERATOR); + UNDEFINE_CLASS_DESCRIPTOR(PATH); + UNDEFINE_CLASS_DESCRIPTOR(SEQUENTIAL_CONTAINER); + UNDEFINE_CLASS_DESCRIPTOR(SORTABLE_CONTAINER); + UNDEFINE_CLASS_DESCRIPTOR(WSTRING); + UNDEFINE_CLASS_DESCRIPTOR(MESSAGE); + UNDEFINE_CLASS_DESCRIPTOR(FSTRING); + UNDEFINE_CLASS_DESCRIPTOR(HMEM); + UNDEFINE_CLASS_DESCRIPTOR(STATIC_MEM_BLOCK_MGR); + UNDEFINE_CLASS_DESCRIPTOR(MEM_ALLOCATOR); + UNDEFINE_CLASS_DESCRIPTOR(MEM_BLOCK_MGR); + UNDEFINE_CLASS_DESCRIPTOR(MEM); + + return TRUE; + +} + diff --git a/ulib/src/wstring.cxx b/ulib/src/wstring.cxx new file mode 100644 index 0000000..278bd03 --- /dev/null +++ b/ulib/src/wstring.cxx @@ -0,0 +1,1083 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + wstring.cxx + +--*/ + + + +#include "ulib.hxx" +#include "wstring.hxx" + +#include +#include + + +// Helper functions for OEM/Unicode conversion. Note that these +// are abstracted to private functions to make it easier to set +// them up for various environments. +// + + +INLINE +BOOLEAN +WSTRING::ConvertOemToUnicodeN( + PWSTR UnicodeString, + ULONG MaxBytesInUnicodeString, + PULONG BytesInUnicodeString, + PCHAR OemString, + ULONG BytesInOemString + ) +{ + return + NT_SUCCESS(RtlOemToUnicodeN( UnicodeString, + MaxBytesInUnicodeString, + BytesInUnicodeString, + OemString, + BytesInOemString )); +} + +INLINE +BOOLEAN +WSTRING::ConvertUnicodeToOemN( + PCHAR OemString, + ULONG MaxBytesInOemString, + PULONG BytesInOemString, + PWSTR UnicodeString, + ULONG BytesInUnicodeString + ) +{ + return + NT_SUCCESS(RtlUnicodeToOemN( OemString, + MaxBytesInOemString, + BytesInOemString, + UnicodeString, + BytesInUnicodeString )); +} + + +INLINE +VOID +WSTRING::Construct( + ) +{ + _s = NULL; + _l = 0; +} + + +DEFINE_CONSTRUCTOR( WSTRING, OBJECT ); + + +BOOLEAN +WSTRING::Initialize( + IN PCWSTRING InitialString, + IN CHNUM Position, + IN CHNUM Length + ) +/*++ + +Routine Description: + + This routine initializes the current string by copying the contents + of the given string. + +Arguments: + + InitialString - Supplies the initial string. + Position - Supplies the position in the given string to start at. + Length - Supplies the length of the string. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + DebugAssert(Position <= InitialString->_l); + + Length = min(Length, InitialString->_l - Position); + + if (!NewBuf(Length)) { + return FALSE; + } + + memcpy(_s, InitialString->_s + Position, (UINT) Length*sizeof(WCHAR)); + + return TRUE; +} + + +BOOLEAN +WSTRING::Initialize( + IN PCWSTR InitialString, + IN CHNUM StringLength + ) +/*++ + +Routine Description: + + This routine initializes the current string by copying the contents + of the given string. + +Arguments: + + InitialString - Supplies the initial string. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + if (StringLength == TO_END) { + StringLength = (ULONG) wcslen(InitialString); + } + + if (!NewBuf(StringLength)) { + return FALSE; + } + + memcpy(_s, InitialString, (UINT) StringLength*sizeof(WCHAR)); + + return TRUE; +} + + +BOOLEAN +WSTRING::Initialize( + IN PCSTR InitialString, + IN CHNUM StringLength + ) +/*++ + +Routine Description: + + This routine initializes the current string by copying the contents + of the given string. + +Arguments: + + InitialString - Supplies the initial string. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + CHNUM length; + BOOLEAN status; + + if (StringLength == TO_END) { + StringLength = (ULONG) strlen(InitialString); + } + + if (!StringLength) { + return Resize(0); + } + + + // We want to avoid making two calls to RtlOemToUnicodeN so + // try to guess an adequate size for the buffer. + + if (!NewBuf(StringLength)) { + return FALSE; + } + + status = ConvertOemToUnicodeN(_s, _l*sizeof(WCHAR), + &length, (PSTR) InitialString, + StringLength); + length /= sizeof(WCHAR); + + if (status) { + return Resize(length); + } + + + // We didn't manage to make in one try so ask exactly how much + // we need and then make the call. + + status = ConvertOemToUnicodeN(NULL, 0, &length, (PSTR) InitialString, + StringLength); + length /= sizeof(WCHAR); + + if (!status || !NewBuf(length)) { + return FALSE; + } + + status = ConvertOemToUnicodeN(_s, _l*sizeof(WCHAR), + &length, (PSTR) InitialString, StringLength); + + if (!status) { + return FALSE; + } + + DebugAssert(length == _l*sizeof(WCHAR)); + + return TRUE; +} + + +BOOLEAN +WSTRING::Initialize( + IN LONG Number + ) +/*++ + +Routine Description: + + This routine initializes the current string by copying the contents + of the given string. + +Arguments: + + Number - Supplies the number to initialize the string to. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + CHAR tmp[64]; + + sprintf(tmp, "%d", Number); + return Initialize(tmp); +} + + + +PWSTRING +WSTRING::QueryString( + IN CHNUM Position, + IN CHNUM Length + ) CONST +/*++ + +Routine Description: + + This routine returns a copy of this string from the specified + coordinates. + +Arguments: + + Position - Supplies the initialize position of the string. + Length - Supplies the length of the string. + +Return Value: + + A pointer to a string or NULL. + +--*/ +{ + PWSTRING p; + + if (!(p = NEW DSTRING) || + !p->Initialize(this, Position, Length)) { + + DELETE(p); + } + + return p; +} + + +BOOLEAN +WSTRING::QueryNumber( + OUT PLONG Number, + IN CHNUM Position, + IN CHNUM Length + ) CONST +/*++ + +Routine Description: + + This routine queries a number from the string. + +Arguments: + + Number - Returns the number parsed out of the string. + Position - Supplies the position of the number. + Length - Supplies the length of the number. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + FSTRING String; + PSTR p; + CHNUM spn; + __int64 BigNumber; + + if (Position >= _l) { + return FALSE; + } + + Length = min(Length, _l - Position); + + // + // Note that 123+123 will be a number! + // + String.Initialize((PWSTR) L"1234567890+-"); + + spn = Strspn(&String, Position); + + if ((spn == INVALID_CHNUM || spn >= Position + Length) && + (p = QuerySTR(Position, Length))) { + + *Number = atol(p); + BigNumber = _atoi64(p); + + DELETE(p); + + // Ensure that there is no overflow of long + if (BigNumber == *Number) + return TRUE; + } + + return FALSE; +} + + +VOID +WSTRING::DeleteChAt( + IN CHNUM Position, + IN CHNUM Length + ) +/*++ + +Routine Description: + + This routine removes the character at the given position. + +Arguments: + + Position - Supplies the position of the character to remove. + Length - Supplies the number of characters to remove. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + DebugAssert(Position <= _l); + + Length = min(Length, _l - Position); + + memmove(_s + Position, _s + Position + Length, + (UINT) (_l - Position - Length)*sizeof(WCHAR)); + + Resize(_l - Length); +} + + + +BOOLEAN +WSTRING::InsertString( + IN CHNUM AtPosition, + IN PCWSTRING String, + IN CHNUM FromPosition, + IN CHNUM FromLength + ) +/*++ + +Routine Description: + + This routine inserts the given string at the given position in + this string. + +Arguments: + + AtPosition - Supplies the position at which to insert the string. + String - Supplies the string to insert. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + CHNUM old_length; + + DebugAssert(AtPosition <= _l); + DebugAssert(FromPosition <= String->_l); + + FromLength = min(FromLength, String->_l - FromPosition); + + old_length = _l; + if (!Resize(_l + FromLength)) { + return FALSE; + } + + memmove(_s + AtPosition + FromLength, _s + AtPosition, + (UINT) (old_length - AtPosition)*sizeof(WCHAR)); + + memcpy(_s + AtPosition, String->_s + FromPosition, + (UINT) FromLength*sizeof(WCHAR)); + + return TRUE; +} + + + +BOOLEAN +WSTRING::Replace( + IN CHNUM AtPosition, + IN CHNUM AtLength, + IN PCWSTRING String, + IN CHNUM FromPosition, + IN CHNUM FromLength + ) +/*++ + +Routine Description: + + This routine replaces the contents of this string from + 'Position' to 'Length' with the contents of 'String2' + from 'Position2' to 'Length2'. + +Arguments: + + AtPosition - Supplies the position to replace at. + AtLength - Supplies the length to replace at. + String - Supplies the string to replace with. + FromPosition - Supplies the position to replace from in String2. + FromLength - Supplies the position to replace from in String2. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + CHNUM old_length; + + DebugAssert(AtPosition <= _l); + DebugAssert(FromPosition <= String->_l); + + AtLength = min(AtLength, _l - AtPosition); + FromLength = min(FromLength, String->_l - FromPosition); + + // Make sure up front that we have the room but don't disturb + // the string. + + if (FromLength > AtLength) { + old_length = _l; + if (!Resize(_l + FromLength - AtLength)) { + return FALSE; + } + Resize(old_length); + } + + DeleteChAt(AtPosition, AtLength); + if (!InsertString(AtPosition, String, FromPosition, FromLength)) { + DebugAbort("This absolutely can never happen\n"); + return FALSE; + } + + return TRUE; +} + + + +BOOLEAN +WSTRING::ReplaceWithChars( + IN CHNUM AtPosition, + IN CHNUM AtLength, + IN WCHAR Character, + IN CHNUM FromLength + ) +/*++ + +Routine Description: + + This routine replaces the contents of this string from + AtPosition of AtLength with the string formed by Character + of FromLength. + +Arguments: + + AtPosition - Supplies the position to replace at. + AtLength - Supplies the length to replace at. + Character - Supplies the character to replace with. + FromLength - Supplies the total number of new characters to replace the old one with. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + CHNUM old_length; + PWCHAR currptr, endptr; + + DebugAssert(AtPosition <= _l); + + AtLength = min(AtLength, _l - AtPosition); + + // Make sure up front that we have the room but don't disturb + // the string. + + if (FromLength > AtLength) { + old_length = _l; + if (!Resize(_l + FromLength - AtLength)) { + return FALSE; + } + Resize(old_length); + } + + DeleteChAt(AtPosition, AtLength); + old_length = _l; + + if (!Resize(_l + FromLength)) { + DebugPrint("This should not fail\n"); + return FALSE; + } + + memmove(_s + AtPosition + FromLength, _s + AtPosition, + (UINT) (old_length - AtPosition)*sizeof(WCHAR)); + + for (currptr = _s + AtPosition, endptr = currptr + FromLength; + currptr < endptr; + currptr++) { + *currptr = Character; + } + + return TRUE; +} + + +PWSTR +WSTRING::QueryWSTR( + IN CHNUM Position, + IN CHNUM Length, + OUT PWSTR Buffer, + IN CHNUM BufferLength, + IN BOOLEAN ForceNull + ) CONST +/*++ + +Routine Description: + + This routine makes a copy of this string into the provided + buffer. If this string is not provided then a buffer is + allocated on the heap. + +Arguments: + + Position - Supplies the position within this string. + Length - Supplies the length of this string to take. + Buffer - Supplies the buffer to copy to. + BufferLength - Supplies the number of characters in the buffer. + ForceNull - Specifies whether or not to force the final character + of the buffer to be NULL in the case when there + isn't enough room for the whole string including + the NULL. + +Return Value: + + A pointer to a NULL terminated string. + +--*/ +{ + DebugAssert(Position <= _l); + + Length = min(Length, _l - Position); + + if (!Buffer) { + BufferLength = Length + 1; + if (!(Buffer = (PWCHAR) MALLOC(BufferLength*sizeof(WCHAR)))) { + return NULL; + } + } + + if (BufferLength > Length) { + memcpy(Buffer, _s + Position, (UINT) Length*sizeof(WCHAR)); + Buffer[Length] = 0; + } else { + memcpy(Buffer, _s + Position, (UINT) BufferLength*sizeof(WCHAR)); + if (ForceNull) { + Buffer[BufferLength - 1] = 0; + } + } + + return Buffer; +} + + +PSTR +WSTRING::QuerySTR( + IN CHNUM Position, + IN CHNUM Length, + OUT PSTR Buffer, + IN CHNUM BufferLength, + IN BOOLEAN ForceNull + ) CONST +/*++ + +Routine Description: + + This routine computes a multi-byte version of the current + unicode string. If the buffer is not supplied then it + will be allocated by this routine. + +Arguments: + + Position - Supplies the position within this string. + Length - Supplies the length of this string to take. + Buffer - Supplies the buffer to convert into. + BufferLength - Supplies the number of characters in this buffer. + ForceNull - Specifies whether or not to force a NULL even + when the buffer is too small for the string. + +Return Value: + + A pointer to a NULL terminated multi byte string. + +--*/ +{ + ULONG ansi_length; + + DebugAssert(Position <= _l); + + Length = min(Length, _l - Position); + + + // First special case the empty result. + + if (!Length) { + + if (!Buffer) { + if (!(Buffer = (PSTR) MALLOC(1))) { + return NULL; + } + } else if (!BufferLength) { + return NULL; + } + + Buffer[0] = 0; + return Buffer; + } + + + // Next case is that the buffer is not provided and thus + // we have to figure out what size it should be. + + if (!Buffer) { + + // We want to avoid too many calls to RtlUnicodeToOemN + // so we'll estimate a correct size for the buffer and + // hope that that works. + + BufferLength = 2*Length + 1; + if (!(Buffer = (PSTR) MALLOC(BufferLength))) { + return NULL; + } + + if (ConvertUnicodeToOemN(Buffer, BufferLength - 1, + &ansi_length, _s + Position, + Length*sizeof(WCHAR))) { + Buffer[ansi_length] = 0; + return Buffer; + } + + + // We failed to estimate the necessary size of the buffer. + // So ask the correct size and try again. + + FREE(Buffer); + + if (!ConvertUnicodeToOemN(NULL, 0, &ansi_length, + _s + Position, Length*sizeof(WCHAR))) { + return NULL; + } + + BufferLength = ansi_length + 1; + if (!(Buffer = (PSTR) MALLOC(BufferLength))) { + return NULL; + } + } + + if (!ConvertUnicodeToOemN(Buffer, BufferLength, &ansi_length, + _s + Position, Length*sizeof(WCHAR))) { + return NULL; + } + + if (BufferLength > ansi_length) { + Buffer[ansi_length] = 0; + } else { + if (ForceNull) { + Buffer[BufferLength - 1] = 0; + } + } + + return Buffer; +} + + +BOOLEAN +WSTRING::Strcat( + IN PCWSTRING String + ) +/*++ + +Routine Description: + + This routine concatenates the given string onto this one. + +Arguments: + + String - Supplies the string to concatenate to this one. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + CHNUM old_length; + + old_length = _l; + if (!Resize(_l + String->_l)) { + return FALSE; + } + + memcpy(_s + old_length, String->_s, (UINT) String->_l*sizeof(WCHAR)); + + return TRUE; +} + + + +PWSTRING +WSTRING::Strupr( + IN CHNUM StartPosition, + IN CHNUM Length + ) +/*++ + +Routine Description: + + This routine upcases a portion of this string. + +Arguments: + + StartPosition - Supplies the start position of the substring to upcase. + Length - Supplies the length of the substring to upscase. + +Return Value: + + A pointer to this string. + +--*/ +{ + WCHAR c; + + DebugAssert(StartPosition <= _l); + + Length = min(Length, _l - StartPosition); + + c = _s[StartPosition + Length]; + _s[StartPosition + Length] = 0; + + _wcsupr(_s + StartPosition); + + _s[StartPosition + Length] = c; + + return this; +} + + +LONG +WSTRING::Strcmp( + IN PCWSTRING String, + IN CHNUM LeftPosition, + IN CHNUM LeftLength, + IN CHNUM RightPosition, + IN CHNUM RightLength + ) CONST +/*++ + +Routine Description: + + This routine compares two substrings. + +Arguments: + + String - Supplies the string to compare this one to. + LeftPosition - Supplies the postion for the left substring. + LeftLength - Supplies the length of the left substring. + LeftPosition - Supplies the postion for the left substring. + LeftLength - Supplies the length of the left substring. + +Return Value: + + <0 - Left substring is less than right substring. + 0 - Left and Right substrings are equal + >0 - Left substring is greater than right substring. + +--*/ +{ + WCHAR c, d; + LONG r; + + DebugAssert(LeftPosition <= _l); + DebugAssert(RightPosition <= String->_l); + + LeftLength = min(LeftLength, _l - LeftPosition); + RightLength = min(RightLength, String->_l - RightPosition); + + c = _s[LeftPosition + LeftLength]; + d = String->_s[RightPosition + RightLength]; + _s[LeftPosition + LeftLength] = 0; + String->_s[RightPosition + RightLength] = 0; + + r = wcscmp(_s + LeftPosition, String->_s + RightPosition); + + _s[LeftPosition + LeftLength] = c; + String->_s[RightPosition + RightLength] = d; + + return r; +} + + +PWSTR +WSTRING::SkipWhite( + IN PWSTR p + ) +{ + + while (iswspace(*p)) { + p++; + } + + return p; + +} + + + + +DEFINE_CONSTRUCTOR( FSTRING, WSTRING ); + + +BOOLEAN +FSTRING::Resize( + IN CHNUM NewStringLength + ) +/*++ + +Routine Description: + + This routine implements the WSTRING Resize routine by using + the buffer supplied at initialization time. + +Arguments: + + NewStringLength - Supplies the new length of the string. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + return NewBuf(NewStringLength); +} + + +BOOLEAN +FSTRING::NewBuf( + IN CHNUM NewStringLength + ) +/*++ + +Routine Description: + + This routine implements the WSTRING NewBuf routine by using + the buffer supplied at initialization time. + +Arguments: + + NewStringLength - Supplies the new length of the string. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + if (NewStringLength >= _buffer_length) { + return FALSE; + } + + PutString((PWSTR) GetWSTR(), NewStringLength); + + return TRUE; +} + + +INLINE +VOID +DSTRING::Construct( + ) +/*++ + +Routine Description: + + This routine initializes the string to a valid initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _buf = NULL; + _length = 0; +} + + +DEFINE_CONSTRUCTOR( DSTRING, WSTRING ); + + +DSTRING::~DSTRING( + ) +/*++ + +Routine Description: + + Destructor for DSTRING. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + FREE(_buf); +} + + +BOOLEAN +DSTRING::Resize( + IN CHNUM NewStringLength + ) +/*++ + +Routine Description: + + This routine resizes this string to the specified new size. + +Arguments: + + NewStringLength - Supplies the new length of the string. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + PWSTR new_buf; + + if (NewStringLength >= _length) { + + if (_buf) { + if (!(new_buf = (PWSTR) + REALLOC(_buf, (NewStringLength + 1)*sizeof(WCHAR)))) { + + return FALSE; + } + } else { + if (!(new_buf = (PWSTR) + MALLOC((NewStringLength + 1)*sizeof(WCHAR)))) { + + return FALSE; + } + } + + _buf = new_buf; + _length = NewStringLength + 1; + } + + PutString(_buf, NewStringLength); + + return TRUE; +} + + +BOOLEAN +DSTRING::NewBuf( + IN CHNUM NewStringLength + ) +/*++ + +Routine Description: + + This routine resizes this string to the specified new size. + +Arguments: + + NewStringLength - Supplies the new length of the string. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + PWSTR new_buf; + + if (NewStringLength >= _length) { + + if (!(new_buf = (PWSTR) + MALLOC((NewStringLength + 1)*sizeof(WCHAR)))) { + + return FALSE; + } + + if (_buf) { + FREE(_buf); + } + _buf = new_buf; + _length = NewStringLength + 1; + } + + PutString(_buf, NewStringLength); + + return TRUE; +} + + + diff --git a/untfs/inc/attrib.hxx b/untfs/inc/attrib.hxx new file mode 100644 index 0000000..11604c9 --- /dev/null +++ b/untfs/inc/attrib.hxx @@ -0,0 +1,1046 @@ +/*++ + +Module Name: + + attrib.hxx + +Abstract: + + This module contains the declarations for NTFS_ATTRIBUTE, + which models an NTFS attribute instance. + + An attribute instance models the user's view of an attribute, + as a [type, name, value] triple. (The name is optional.) It + does not correspond to any specific disk structure. + + An attribute instance may exist in: + + 1) a single resident attribute record, + + 2) a single non-resident attribute record, or + + 3) multiple, non-overlapping non-resident attribute records. + + Case (3) is rare, so we don't need to optimize for it, but we + do need to handle it. + + + The client may initialize an Attribute object by supplying the + value directly [resident]; by supplying a description of the + value's location on disk [non-resident]; or by supplying an + attribute record or list of attribute records for the attribute. + + + On disk, an attribute may be resident (with the data contained + in the attribute record) or it may be nonresident (with the + data residing on disk, outside the File Record Segment). This + is reflected in the attribute object by having the value stored + directly (in space allocated by the attribute object and copied + at initialization) or stored on disk (with the attribute object + keeping an Extent List which describes this disk storage). I + chose not to reflect this difference with different classes for + three reasons: first, an attribute may wish to transform itself + from one form to another; second, this would introduce many + virtual methods; and third, I want to have special attribute + classes derive from the attribute class, and dividing attributes + into resident and nonresident would force me to use multiple + inheritance. + + Generally, the difference between resident and nonresident + attributes is not visible to clients of this class. The + most important exception is GetResidentValue; this method + is provided for performance reasons--Read will supply the + same effect, but requires copying the data. + +--*/ + +#pragma once + +#include "extents.hxx" +#include "wstring.hxx" +#include "drive.hxx" + +DECLARE_CLASS( LOG_IO_DP_DRIVE ); +DECLARE_CLASS( NTFS_FILE_RECORD_SEGMENT ); +DECLARE_CLASS( NTFS_EXTENT_LIST ); +DECLARE_CLASS( NTFS_ATTRIBUTE_RECORD ); +DECLARE_CLASS( NTFS_ATTRIBUTE_RECORD_LIST ); +DECLARE_CLASS( NTFS_BITMAP ); +DECLARE_CLASS( NTFS_ATTRIBUTE ); +DECLARE_CLASS( NTFS_BAD_CLUSTER_FILE ); +DECLARE_CLASS( NUMBER_SET ); +DECLARE_CLASS( NTFS_ATTRIBUTE ); + +class NTFS_ATTRIBUTE : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( NTFS_ATTRIBUTE ); + + VIRTUAL + + ~NTFS_ATTRIBUTE ( + ); + + + + BOOLEAN + Initialize ( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN PCVOID Value, + IN ULONG ValueLength, + IN ATTRIBUTE_TYPE_CODE TypeCode, + IN PCWSTRING Name DEFAULT NULL, + IN USHORT Flags DEFAULT 0 + ); + + + + BOOLEAN + Initialize ( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN PCNTFS_EXTENT_LIST Extents, + IN BIG_INT ValueLength, + IN BIG_INT ValidLength, + IN ATTRIBUTE_TYPE_CODE TypeCode, + IN PCWSTRING Name DEFAULT NULL, + IN USHORT Flags DEFAULT 0 + ); + + + BOOLEAN + Initialize ( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN PCNTFS_ATTRIBUTE_RECORD AttributeRecord + ); + + + BOOLEAN + AddAttributeRecord ( + IN PCNTFS_ATTRIBUTE_RECORD AttributeRecord, + IN OUT PNTFS_EXTENT_LIST *BackupExtent + ); + + + + ATTRIBUTE_TYPE_CODE + QueryTypeCode ( + ) CONST; + + + PCWSTRING + GetName ( + ) CONST; + + + VOID + QueryValueLength ( + OUT PBIG_INT ValueLength, + OUT PBIG_INT AllocatedLength DEFAULT NULL, + OUT PBIG_INT ValidLength DEFAULT NULL + ) CONST; + + + BIG_INT + QueryValueLength( + ) CONST; + + + BIG_INT + QueryValidDataLength( + ) CONST; + + + BIG_INT + QueryAllocatedLength( + ) CONST; + + + BIG_INT + QueryClustersAllocated( + ) CONST; + + + BOOLEAN + IsResident ( + ) CONST; + + + BOOLEAN + IsIndexed ( + ) CONST; + + + VOID + SetIsIndexed( + IN BOOLEAN State DEFAULT TRUE + ); + + + PCVOID + GetResidentValue ( + ) CONST; + + + BOOLEAN + QueryLcnFromVcn ( + IN VCN Vcn, + OUT PLCN Lcn, + OUT PBIG_INT RunLength DEFAULT NULL + ) CONST; + + VIRTUAL + + BOOLEAN + InsertIntoFile ( + IN OUT PNTFS_FILE_RECORD_SEGMENT BaseFileRecordSegment, + IN OUT PNTFS_BITMAP Bitmap OPTIONAL + ); + + VIRTUAL + + BOOLEAN + MakeNonresident ( + IN OUT PNTFS_BITMAP Bitmap + ); + + VIRTUAL + + BOOLEAN + Resize ( + IN BIG_INT NewSize, + IN OUT PNTFS_BITMAP Bitmap OPTIONAL + ); + + VIRTUAL + + BOOLEAN + SetSparse ( + IN BIG_INT NewSize, + IN OUT PNTFS_BITMAP Bitmap + ); + + + BOOLEAN + AddExtent ( + IN VCN Vcn, + IN LCN Lcn, + IN BIG_INT RunLength + ); + + + + BOOLEAN + Read ( + OUT PVOID Data, + IN BIG_INT ByteOffset, + IN ULONG BytesToRead, + OUT PULONG BytesRead + ); + + + VOID + PrimeCache ( + IN BIG_INT ByteOffset, + IN ULONG BytesToRead + ); + + VIRTUAL + + BOOLEAN + Write ( + IN PCVOID Data, + IN BIG_INT ByteOffset, + IN ULONG BytesToWrite, + OUT PULONG BytesWritten, + IN OUT PNTFS_BITMAP Bitmap OPTIONAL + ); + + + BOOLEAN + Fill ( + IN BIG_INT Offset, + IN CHAR FillCharacter + ); + + + BOOLEAN + Fill ( + IN BIG_INT Offset, + IN CHAR FillCharacter, + IN ULONG NumberOfBytes + ); + + + BOOLEAN + IsStorageModified ( + ) CONST; + + + PLOG_IO_DP_DRIVE + GetDrive( + ); + + + BOOLEAN + RecoverAttribute( + IN OUT PNTFS_BITMAP VolumeBitmap, + IN OUT PNUMBER_SET BadClusters, + OUT PBIG_INT BytesRecovered DEFAULT NULL + ); + + + USHORT + QueryFlags( + ) CONST; + + + VOID + SetFlags( + IN USHORT Flags + ); + + + UCHAR + QueryResidentFlags( + ) CONST; + + + BOOLEAN + MarkAsAllocated( + IN OUT PNTFS_BITMAP VolumeBitmap + ) CONST; + + + ULONG + QueryCompressionUnit( + ) CONST; + + + BOOLEAN + IsCompressed( + ) CONST; + + + BOOLEAN + IsSparse( + ) CONST; + + + BOOLEAN + Hotfix( + IN VCN Vcn, + IN BIG_INT RunLength, + IN OUT PNTFS_BITMAP VolumeBitmap, + IN OUT PNUMBER_SET BadClusters, + IN BOOLEAN Contiguous DEFAULT FALSE + ); + + + BOOLEAN + ReplaceVcns( + IN VCN StartingVcn, + IN LCN NewLcn, + IN BIG_INT NumberOfClusters + ); + + + PCNTFS_EXTENT_LIST + GetExtentList( + ) CONST; + + + BOOLEAN + IsAllocationZeroed( + OUT PBOOLEAN Error DEFAULT NULL + ); + + + BOOLEAN + GetNextAllocationOffset( + IN OUT PBIG_INT ByteOffset, + IN OUT PBIG_INT Length + ); + + FRIEND + BOOLEAN + operator==( + IN RCNTFS_ATTRIBUTE Left, + IN RCNTFS_ATTRIBUTE Right + ); + + protected: + + + VOID + SetStorageModified ( + ); + + + VOID + ResetStorageModified ( + ); + + + ULONG + QueryClusterFactor( + ) CONST; + + private: + + + VOID + Construct ( + ); + + + VOID + Destroy ( + ); + + + BOOLEAN + InsertMftDataIntoFile ( + IN OUT PNTFS_FILE_RECORD_SEGMENT BaseFileRecordSegment, + IN OUT PNTFS_BITMAP Bitmap OPTIONAL, + IN BOOLEAN BeConservative + ); + + + BOOLEAN + ReadCompressed ( + OUT PVOID Data, + IN BIG_INT ByteOffset, + IN ULONG BytesToRead, + OUT PULONG BytesRead + ); + + + BOOLEAN + WriteCompressed ( + IN PCVOID Data, + IN BIG_INT ByteOffset, + IN ULONG BytesToWrite, + OUT PULONG BytesWritten, + IN OUT PNTFS_BITMAP Bitmap OPTIONAL + ); + + + BOOLEAN + RecoverCompressedAttribute( + IN OUT PNTFS_BITMAP VolumeBitmap, + IN OUT PNUMBER_SET BadClusters, + OUT PBIG_INT BytesRecovered DEFAULT NULL + ); + + + PLOG_IO_DP_DRIVE _Drive; + ULONG _ClusterFactor; + + ATTRIBUTE_TYPE_CODE _Type; + DSTRING _Name; + USHORT _Flags; + UCHAR _FormCode; + UCHAR _CompressionUnit; + + BIG_INT _ValueLength; + BIG_INT _ValidDataLength; + + PVOID _ResidentData; + PNTFS_EXTENT_LIST _ExtentList; + + UCHAR _ResidentFlags; + + BOOLEAN _StorageModified; + +}; + + + +INLINE +BOOLEAN +NTFS_ATTRIBUTE::IsResident( + ) CONST +/*++ + +Routine Description: + + This method returns whether the attribute value is resident. + +Arguments: + + None. + +Return Value: + + TRUE if the value is resident; FALSE if it is non-resident. + +--*/ +{ + return( _ResidentData != NULL ); +} + + +INLINE +BIG_INT +NTFS_ATTRIBUTE::QueryValueLength( + ) CONST +/*++ + +Routine Description: + + This routine returns the length of the attribute value in bytes. + +Arguments: + + None. + +Return Value: + + The length of the attribute value in bytes. + +--*/ +{ + return _ValueLength; +} + + +INLINE +BIG_INT +NTFS_ATTRIBUTE::QueryValidDataLength( + ) CONST +/*++ + +Routine Description: + + This routine returns the valid data length. + +Arguments: + + None. + +Return Value: + + The valid data length. + +--*/ +{ + return _ValidDataLength; +} + + + +INLINE +BIG_INT +NTFS_ATTRIBUTE::QueryAllocatedLength( + ) CONST +/*++ + +Routine Description: + + This routine return the number of bytes allocated for this attribute + on disk. + +Arguments: + + None. + +Return Value: + + The amount of disk space allocated for this attribute. + +--*/ +{ + BIG_INT Result; + + if( IsResident() ) { + + Result = QuadAlign( _ValueLength.GetLowPart() ); + + } else { + + Result = (_ExtentList->QueryNextVcn()*_ClusterFactor* + _Drive->QuerySectorSize()); + } + + return Result; +} + + + +INLINE +ATTRIBUTE_TYPE_CODE +NTFS_ATTRIBUTE::QueryTypeCode( + ) CONST +/*++ + +Routine Description: + + Returns the attribute's type code. + +Arguments: + + None. + +Return Value: + + The attribute's type code. + +--*/ +{ + return _Type; +} + + +INLINE +PCWSTRING +NTFS_ATTRIBUTE::GetName( + ) CONST +/*++ + +Routine Description: + + Returns the attribute's name. (Note that this returns a pointer + to the attribute's private copy of its name.) + +Arguments: + + None. + +Return Value: + + A pointer to the attribute's name, if it has one; NULL if it + has none. + +--*/ +{ + return &_Name; +} + + + +INLINE +VOID +NTFS_ATTRIBUTE::QueryValueLength( + OUT PBIG_INT ValueLength, + OUT PBIG_INT AllocatedLength, + OUT PBIG_INT ValidLength + ) CONST +/*++ + +Routine Description: + + Returns the attribute's value lengths (actual, allocated + and valid length). + +Arguments: + + ValueLength -- receives the attribute value's length + AllocatedLength -- receives the attribute value's allocated length + (ignored if NULL) + ValidLength -- receives the attribute value's valid length + (ignored if NULL ); + +Return Value: + + None. + +--*/ +{ + *ValueLength = _ValueLength; + + if( AllocatedLength != NULL ) { + + *AllocatedLength = QueryAllocatedLength(); + } + + if( ValidLength != NULL ) { + + *ValidLength = _ValidDataLength; + } +} + + +INLINE +BOOLEAN +NTFS_ATTRIBUTE::IsIndexed( + ) CONST +/*++ + +Routine Description: + + This method returns whether the attribute is indexed. + +Arguments: + + None. + +Return Value: + + TRUE if the attribute is indexed; FALSE if not. + +--*/ +{ + return( (_ResidentData == NULL) ? + FALSE : + _ResidentFlags & RESIDENT_FORM_INDEXED ); +} + + +INLINE +VOID +NTFS_ATTRIBUTE::SetIsIndexed( + IN BOOLEAN State + ) +/*++ + +Routine Description: + + This method marks the attribute as indexed. It has no effect if + the attribute is nonresident. + +Arguments: + + State -- supplies a value indicating whether the attribute + is indexed (TRUE) or not indexed (FALSE). +Return Value: + + None. + +--*/ +{ + if( _ResidentData != NULL ) { + + if( State ) { + + _ResidentFlags |= RESIDENT_FORM_INDEXED; + + } else { + + _ResidentFlags &= ~RESIDENT_FORM_INDEXED; + + } + } +} + + +INLINE +PCVOID +NTFS_ATTRIBUTE::GetResidentValue( + ) CONST +/*++ + +Routine Description: + + Returns a pointer to the attribute's value. + +Arguments: + + None. + +Return Value: + + If the attribute value is resident, returns a pointer to the + value. If it is nonresident, returns NULL. + +Notes: + + This method is provided for clients who know that the value is + resident and who want to inspect it without copying it; if the + client doesn't know whether the value is resident, Read is + a better way to get it. + +--*/ +{ + return _ResidentData; +} + + +INLINE +BOOLEAN +NTFS_ATTRIBUTE::QueryLcnFromVcn ( + IN VCN Vcn, + OUT PLCN Lcn, + OUT PBIG_INT RunLength + ) CONST +/*++ + +Routine Description: + + This method converts a VCN within the attribute into an LCN. + (Note that it only applies to nonresident attributes.) + +Arguments: + + Vcn -- Supplies the VCN to be converted. + Lcn -- Receives the corresponding LCN. + RunLength -- Receives the remaining length in the current run + starting at this LCN. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + if( _ExtentList == NULL ) { + + return FALSE; + + } else { + + return _ExtentList->QueryLcnFromVcn( Vcn, Lcn, RunLength ); + } +} + + +INLINE +BOOLEAN +NTFS_ATTRIBUTE::IsStorageModified( + ) CONST +/*++ + +Routine Description: + + Query whether the attribute's storage (i.e. anything that + would go into an attribute record) has changed + +Arguments: + + None. + +Return Value: + + Returns TRUE if the attribute's storage has been modified since + the last time we got it from or put it into a File Record Segment. + +--*/ +{ + return _StorageModified; +} + + +INLINE +VOID +NTFS_ATTRIBUTE::SetStorageModified ( + ) +/*++ + +Routine Description: + + This routine sets the 'IsStorageModified' flag. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _StorageModified = TRUE; +} + + +INLINE +VOID +NTFS_ATTRIBUTE::ResetStorageModified ( + ) +/*++ + +Routine Description: + + This routine resets the 'IsStorageModified' flag. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _StorageModified = FALSE; +} + + +INLINE +PLOG_IO_DP_DRIVE +NTFS_ATTRIBUTE::GetDrive( + ) +/*++ + +Routine Description: + + This method returns the drive on which the Attribute resides. + +Arguments: + + None. + +Return Value: + + The drive on which the Attribute resides. + +--*/ +{ + return _Drive; +} + + +INLINE +USHORT +NTFS_ATTRIBUTE::QueryFlags( + ) CONST +/*++ + +Routine Description: + + This routine returns this attribute's flags. + +Arguments: + + None. + +Return Value: + + This attribute's flags. + +--*/ +{ + return _Flags; +} + + +INLINE +VOID +NTFS_ATTRIBUTE::SetFlags( + IN USHORT Flags + ) +/*++ + +Routine Description: + + This routine returns this attribute's flags. + +Arguments: + + None. + +Return Value: + + This attribute's flags. + +--*/ +{ + _Flags = Flags; + SetStorageModified(); +} + +INLINE +UCHAR +NTFS_ATTRIBUTE::QueryResidentFlags( + ) CONST +/*++ + +Routine Description: + + This routine returns this attribute's resident flags. + +Arguments: + + None. + +Return Value: + + This attribute's resident flags. + +--*/ +{ + return _ResidentFlags; +} + +INLINE +ULONG +NTFS_ATTRIBUTE::QueryCompressionUnit( + ) CONST +{ + return _CompressionUnit; +} + + +INLINE +BOOLEAN +NTFS_ATTRIBUTE::IsCompressed( + ) CONST +{ + return ((_Flags & ATTRIBUTE_FLAG_COMPRESSION_MASK) != 0); +} + + +INLINE +BOOLEAN +NTFS_ATTRIBUTE::IsSparse( + ) CONST +{ + return ((_Flags & ATTRIBUTE_FLAG_SPARSE) != 0); +} + + +INLINE +ULONG +NTFS_ATTRIBUTE::QueryClusterFactor( + ) CONST +/*++ + +Routine Description: + + This method returns the cluster factor. + +Arguments: + + None. + +Return Value: + + The cluster factor. + +--*/ +{ + return _ClusterFactor; +} + + +INLINE +PCNTFS_EXTENT_LIST +NTFS_ATTRIBUTE::GetExtentList( + ) CONST +/*++ + +Routine Description: + + This routine returns a pointer to this object's extent list. + +Arguments: + + None. + +Return Value: + + A pointer to the extent list. + +--*/ +{ + return _ExtentList; +} + + + diff --git a/untfs/inc/attrlist.hxx b/untfs/inc/attrlist.hxx new file mode 100644 index 0000000..6980309 --- /dev/null +++ b/untfs/inc/attrlist.hxx @@ -0,0 +1,251 @@ +/*++ + +Module Name: + + attrlist.hxx + +Abstract: + + This module contains the declarations for NTFS_ATTRIBUTE_LIST, + which models an ATTRIBUTE_LIST Attribute in an NTFS File Record + Segment. + + If a file has any external attributes (i.e. if it has more than + one File Record Segment), then it will have an ATTRIBUTE_LIST + attribute. This attribute's value consists of a series of + Attribute List Entries, which describe the attribute records + in the file's File Record Segments. There is an entry for each + attribute record attached to the file, including the attribute + records in the base File Record Segment, and in particular + including the attribute records which describe the ATTRIBUTE_LIST + attribute itself. + + An entry in the Attribute List gives the type code and name (if any) + of the attribute, along with the LowestVcn of the attribute record + (zero if the attribute record is Resident) and a segment reference + (which combines an MFT VCN with a sequence number) showing where + the attribute record may be found. + + The entries in the Attribute List are sorted first by attribute + type code and then by name. Note that two attributes can have the + same type code and name only if they can be distinguished by + value. + +--*/ + +#pragma once + +#include "attrib.hxx" +#include "hmem.hxx" +#include "volume.hxx" + +DECLARE_CLASS( LOG_IO_DP_DRIVE ); +DECLARE_CLASS( WSTRING ); +DECLARE_CLASS( NTFS_ATTRIBUTE_RECORD ); +DECLARE_CLASS( NTFS_ATTRIBUTE_RECORD_LIST ); +DECLARE_CLASS( NTFS_BITMAP ); +DECLARE_CLASS( NTFS_UPCASE_TABLE ); +DECLARE_CLASS( NTFS_ATTRIBUTE_LIST ); + +// This macro produces a pointer to the wide-character name of an attribute +// list entry from a pointer to an attribute list entry. + +#define NameFromEntry( x ) ((PWSTR)((PBYTE)(x)+(x)->AttributeNameOffset)) + +// This macro produces a pointer to the attribute list entry +// following x + +#define NextEntry( x ) \ + ((PATTRIBUTE_LIST_ENTRY)((PBYTE)(x) + (x)->RecordLength)) + +typedef struct _ATTR_LIST_CURR_ENTRY { + PATTRIBUTE_LIST_ENTRY CurrentEntry; + ULONG CurrentOffset; +}; + +DEFINE_TYPE( _ATTR_LIST_CURR_ENTRY, ATTR_LIST_CURR_ENTRY ); + +class NTFS_ATTRIBUTE_LIST : public NTFS_ATTRIBUTE { + + public: + + DECLARE_CONSTRUCTOR( NTFS_ATTRIBUTE_LIST ); + + VIRTUAL + ~NTFS_ATTRIBUTE_LIST( + ); + + + BOOLEAN + Initialize ( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN PNTFS_UPCASE_TABLE UpcaseTable + ); + + + BOOLEAN + Initialize ( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN PCNTFS_ATTRIBUTE_RECORD AttributeRecord, + IN PNTFS_UPCASE_TABLE UpcaseTable + ); + + + BOOLEAN + AddEntry( + IN ATTRIBUTE_TYPE_CODE Type, + IN VCN LowestVcn, + IN PCMFT_SEGMENT_REFERENCE SegmentReference, + IN USHORT InstanceTag, + IN PCWSTRING Name DEFAULT NULL + ); + + + BOOLEAN + DeleteEntry( + IN ULONG EntryIndex + ); + + + BOOLEAN + DeleteCurrentEntry( + IN PATTR_LIST_CURR_ENTRY Entry + ); + + + BOOLEAN + DeleteEntries( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name DEFAULT NULL + ); + + + BOOLEAN + IsInList( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name DEFAULT NULL + ) CONST; + + + + BOOLEAN + QueryNextEntry( + IN OUT PATTR_LIST_CURR_ENTRY CurrEntry, + OUT PATTRIBUTE_TYPE_CODE Type, + OUT PVCN LowestVcn, + OUT PMFT_SEGMENT_REFERENCE SegmentReference, + OUT PUSHORT InstanceTag, + OUT PWSTRING Name + ) CONST; + + + BOOLEAN + QueryEntry( + IN MFT_SEGMENT_REFERENCE SegmentReference, + IN USHORT InstanceTag, + OUT PATTRIBUTE_TYPE_CODE Type, + OUT PVCN LowestVcn, + OUT PWSTRING Name + ) CONST; + + + PCATTRIBUTE_LIST_ENTRY + GetNextAttributeListEntry( + IN PCATTRIBUTE_LIST_ENTRY CurrentEntry + ) CONST; + + + BOOLEAN + QueryExternalReference( + IN ATTRIBUTE_TYPE_CODE Type, + OUT PMFT_SEGMENT_REFERENCE SegmentReference, + OUT PULONG EntryIndex, + IN PCWSTRING Name DEFAULT NULL, + IN PVCN DesiredVcn DEFAULT NULL, + OUT PVCN StartingVcn DEFAULT NULL + ) CONST; + + + BOOLEAN + QueryNextAttribute( + IN OUT PATTRIBUTE_TYPE_CODE TypeCode, + IN OUT PWSTRING Name + ) CONST; + + + BOOLEAN + ReadList( + ); + + + BOOLEAN + WriteList( + PNTFS_BITMAP VolumeBitmap + ); + + + private: + + + VOID + Construct ( + ); + + + VOID + Destroy( + ); + + + PATTRIBUTE_LIST_ENTRY + FindEntry( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name, + IN VCN LowestVcn, + OUT PULONG EntryOffset DEFAULT NULL, + OUT PULONG EntryIndex DEFAULT NULL + ) CONST; + + + HMEM _Mem; + ULONG _LengthOfList; + PNTFS_UPCASE_TABLE _UpcaseTable; + +}; + + + +INLINE +BOOLEAN +NTFS_ATTRIBUTE_LIST::WriteList( + IN OUT PNTFS_BITMAP VolumeBitmap + ) +/*++ + +Routine Description: + + This method writes the list to disk. + +Arguments: + + None. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + ULONG BytesWritten; + + return( Resize( _LengthOfList, VolumeBitmap ) && + Write( _Mem.GetBuf(), + 0, + _LengthOfList, + &BytesWritten, + VolumeBitmap ) && + BytesWritten == _LengthOfList ); +} + diff --git a/untfs/inc/attrrec.hxx b/untfs/inc/attrrec.hxx new file mode 100644 index 0000000..7616d27 --- /dev/null +++ b/untfs/inc/attrrec.hxx @@ -0,0 +1,764 @@ +/*++ + +Module Name: + + attrrec.hxx + +Abstract: + + This module contains the declarations for NTFS_ATTRIBUTE_RECORD, + which models NTFS attribute records. + + An Attribute Record may be a template laid over a chunk of + memory; in that case, it does not own the memory. It may + also be told, upon initialization, to allocate its own memory + and copy the supplied data. In that case, it is also responsible + for freeing that memory. + + Attribute Records are passed between Attributes and File + Record Segments. A File Record Segment can initialize + an Attribute with a list of Attribute Records; when an + Attribute is Set into a File Record Segment, it packages + itself up into Attribute Records and inserts them into + the File Record Segment. + + File Record Segments also use Attribute Records to scan + through their list of attribute records, and to shuffle + them around. + +--*/ + +#pragma once + +#include "badfile.hxx" + +DECLARE_CLASS(WSTRING); +DECLARE_CLASS( NTFS_EXTENT_LIST ); +DECLARE_CLASS( NTFS_ATTRIBUTE_RECORD ); +DECLARE_CLASS( NTFS_UPCASE_TABLE ); + +// This function is used to compare attribute records, in order +// to determine their ordering in the base FRS. Its definition +// appears in attrrec.cxx. +// +LONG +CompareAttributeRecords( + IN PCNTFS_ATTRIBUTE_RECORD Left, + IN PCNTFS_ATTRIBUTE_RECORD Right, + IN PCNTFS_UPCASE_TABLE UpcaseTable + ); + +class NTFS_ATTRIBUTE_RECORD : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( NTFS_ATTRIBUTE_RECORD ); + + VIRTUAL + + ~NTFS_ATTRIBUTE_RECORD( + ); + + + BOOLEAN + Initialize( + IN PIO_DP_DRIVE Drive, + IN OUT PVOID Data, + IN ULONG MaximumLength, + IN BOOLEAN MakeCopy DEFAULT FALSE + ); + + + + BOOLEAN + Initialize( + IN PIO_DP_DRIVE Drive, + IN OUT PVOID Data + ); + + + BOOLEAN + CreateResidentRecord( + IN PCVOID Value, + IN ULONG ValueLength, + IN ATTRIBUTE_TYPE_CODE TypeCode, + IN PCWSTRING Name DEFAULT NULL, + IN USHORT Flags DEFAULT 0, + IN UCHAR ResidentFlags DEFAULT 0 + ); + + + BOOLEAN + CreateNonresidentRecord( + IN PCNTFS_EXTENT_LIST Extents, + IN BIG_INT AllocatedLength, + IN BIG_INT ActualLength, + IN BIG_INT ValidDataLength, + IN ATTRIBUTE_TYPE_CODE TypeCode, + IN PCWSTRING Name DEFAULT NULL, + IN USHORT Flags DEFAULT 0, + IN USHORT CompressionUnit DEFAULT 0, + IN ULONG ClusterSize DEFAULT 0 + ); + + + + BOOLEAN + UseClusters( + IN OUT PNTFS_BITMAP VolumeBitmap, + OUT PBIG_INT ClusterCount + ) CONST; + + + + ULONG + QueryRecordLength( + ) CONST; + + + ATTRIBUTE_TYPE_CODE + QueryTypeCode( + ) CONST; + + + USHORT + QueryFlags( + ) CONST; + + + UCHAR + QueryResidentFlags( + ) CONST; + + + ULONG + QueryCompressionUnit( + ) CONST; + + + BOOLEAN + IsResident( + ) CONST; + + + BOOLEAN + IsIndexed( + ) CONST; + + + VCN + QueryLowestVcn( + ) CONST; + + + PWSTR + GetName( + ) CONST; + + + VCN + QueryNextVcn( + ) CONST; + + + + BOOLEAN + QueryName( + OUT PWSTRING Name + ) CONST; + + + ULONG + QueryNameLength( + ) CONST; + + + VOID + QueryValueLength( + OUT PBIG_INT ValueLength, + OUT PBIG_INT AllocatedLength DEFAULT NULL, + OUT PBIG_INT ValidLength DEFAULT NULL, + OUT PBIG_INT TotalAllocated DEFAULT NULL + ) CONST; + + + VOID + SetTotalAllocated( + IN BIG_INT TotalAllocated + ); + + + USHORT + QueryInstanceTag( + ) CONST; + + + VOID + SetInstanceTag( + USHORT NewTag + ); + + + PCVOID + GetData( + ) CONST; + + + + BOOLEAN + QueryExtentList( + OUT PNTFS_EXTENT_LIST ExtentList + ) CONST; + + + PCVOID + GetResidentValue( + ) CONST; + + + USHORT + QueryResidentValueOffset( + ) CONST; + + + ULONG + QueryResidentValueLength( + ) CONST; + + + BOOLEAN + IsMatch( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name DEFAULT NULL, + IN PCVOID Value DEFAULT NULL, + IN ULONG ValueLength DEFAULT 0 + ) CONST; + + + VOID + DisableUnUse( + IN BOOLEAN NewState DEFAULT TRUE + ); + + + BOOLEAN + IsUnUseDisabled( + ); + + private: + + + VOID + Construct ( + ); + + + VOID + Destroy( + ); + + PATTRIBUTE_RECORD_HEADER _Data; + ULONG _MaximumLength; + BOOLEAN _IsOwnBuffer; + + // + // DisableUnUse will turn the UnUseClusters operation into a + // no-op. + // + + BOOLEAN _DisableUnUse; + + PIO_DP_DRIVE _Drive; // in order to obtain message outlet + +}; + + +INLINE +ULONG +NTFS_ATTRIBUTE_RECORD::QueryRecordLength( + ) CONST +/*++ + +Routine Description: + + This method returns the length of the attribute record. (Note + that this is the length of the record itself, not the length + of the buffer allotted to it). + +Arguments: + + None. + +Return Value: + + The length of the attribute record. + +--*/ +{ + return _Data->RecordLength; +} + + +INLINE +ATTRIBUTE_TYPE_CODE +NTFS_ATTRIBUTE_RECORD::QueryTypeCode( + ) CONST +/*++ + +Routine Description: + + This method returns the attribute type code. + +Arguments: + + None. + +Return Value: + + The attribute type code for this attribute record. + +--*/ +{ + return _Data->TypeCode; +} + + +INLINE +USHORT +NTFS_ATTRIBUTE_RECORD::QueryFlags( + ) CONST +/*++ + +Routine Description: + + This method returns the attribute's flags. + +Arguments: + + None. + +Return Value: + + The flags from the attribute record. + +--*/ +{ + DebugPtrAssert( _Data ); + + return( _Data->Flags ); +} + + + +INLINE +ULONG +NTFS_ATTRIBUTE_RECORD::QueryCompressionUnit( + ) CONST +{ + return( (_Data->FormCode == NONRESIDENT_FORM) ? + _Data->Form.Nonresident.CompressionUnit : + 0 ); +} + + +INLINE +UCHAR +NTFS_ATTRIBUTE_RECORD::QueryResidentFlags( + ) CONST +/*++ + +Routine Description: + + This method returns the attribute's resident-form flags. + +Arguments: + + None. + +Return Value: + + The resident-form flags from the attribute record. If + if the attribute is nonresident, this method returns zero. + +--*/ +{ + DebugPtrAssert( _Data ); + + return( (_Data->FormCode == NONRESIDENT_FORM) ? + 0 : + _Data->Form.Resident.ResidentFlags ); +} + + + +INLINE +BOOLEAN +NTFS_ATTRIBUTE_RECORD::IsResident( + ) CONST +/*++ + +Routine Description: + + This method returns whether the attribute is resident. + +Arguments: + + None. + +Return Value: + + TRUE if the attribute is resident. + +--*/ +{ + return (_Data->FormCode == RESIDENT_FORM); +} + + +INLINE +BOOLEAN +NTFS_ATTRIBUTE_RECORD::IsIndexed( + ) CONST +/*++ + +Routine Description: + + This method returns whether the attribute is indexed. + +Arguments: + + None. + +Return Value: + + TRUE if the attribute is indexed. + +--*/ +{ + return IsResident() && + (_Data->Form.Resident.ResidentFlags & RESIDENT_FORM_INDEXED); +} + + +INLINE +VCN +NTFS_ATTRIBUTE_RECORD::QueryLowestVcn( + ) CONST +/*++ + +Routine Description: + + This method returns the lowest VCN covered by this attribute + record. If this attribute record is resident, the the lowest + VCN is zero; if it is nonresident, the lowest VCN is given by + the + +Arguments: + + None. + +Return Value: + + The lowest VCN covered by this attribute record. + +--*/ +{ + if( IsResident() ) { + + return 0; + + } else { + + return _Data->Form.Nonresident.LowestVcn; + } +} + + +INLINE +VCN +NTFS_ATTRIBUTE_RECORD::QueryNextVcn( + ) CONST +/*++ + +Routine Description: + + This method returns the highest VCN covered by this attribute + record. If this attribute record is resident, then the highest + VCN is zero; if it is nonresident, the highest VCN is given by + the + +Arguments: + + None. + +Return Value: + + The highest VCN covered by this attribute record. + +--*/ +{ + if( IsResident() ) { + + return 1; + + } else { + + return _Data->Form.Nonresident.HighestVcn + 1; + } +} + + +INLINE +ULONG +NTFS_ATTRIBUTE_RECORD::QueryNameLength( + ) CONST +/*++ + +Routine Description: + + This method returns the length of the name, in characters. + Zero indicates that the attribute record has no name. + +Arguments: + + None. + +Return Value: + + Length of the name, in characters. + +--*/ +{ + return _Data->NameLength; +} + + + +INLINE +USHORT +NTFS_ATTRIBUTE_RECORD::QueryInstanceTag( + ) CONST +/*++ + +Routine Description: + + This method queries the attribute record's unique-within-this-file + attribute instance tag. + +Arguments: + + None. + +Return Value: + + The attribute record's instance tag. + +--*/ +{ + return _Data->Instance; +} + + +INLINE +VOID +NTFS_ATTRIBUTE_RECORD::SetInstanceTag( + USHORT NewTag + ) +/*++ + +Routine Description: + + This method sets the attribute record's unique-within-this-file + attribute instance tag. + +Arguments: + + NewTag -- Supplies the new value for the instance tag. + +Return Value: + + None. + +--*/ +{ + _Data->Instance = NewTag; + +} + + +INLINE +PWSTR +NTFS_ATTRIBUTE_RECORD::GetName( + ) CONST +/*++ + +Routine Description: + + This method returns a pointer to the wide-character attribute + name. + +Arguments: + + None. + +Return Value: + + A pointer to the wide-character name in this attribute record; + NULL if there is no name. + +Notes: + + This method is provided to permit optimization of the common + attribute look-op operation in File Record Segments. + +--*/ +{ + + if( QueryNameLength() == 0 ) { + + return NULL; + + } else { + + return( (PWSTR)( (PBYTE)_Data + _Data->NameOffset ) ); + } +} + + + +INLINE +PCVOID +NTFS_ATTRIBUTE_RECORD::GetData( + ) CONST +/*++ + +Routine Description: + + This method returns a pointer to the attribute record's data. + +Arguments: + + None. + +Return Value: + + A pointer to the attribute record's data. + +--*/ +{ + return _Data; +} + + +INLINE +PCVOID +NTFS_ATTRIBUTE_RECORD::GetResidentValue( + ) CONST +/*++ + +Routine Description: + + This method returns a pointer to the attribute record's + resident value. If the attribute record is non-resident, + it returns NULL. + +Arguments: + + None. + +Return Value: + + The attribute record's resident value; NULL if the attribute + record is non-resident. + +--*/ +{ + if( _Data->FormCode == NONRESIDENT_FORM ) { + + return NULL; + + } else { + + return ((PBYTE)_Data + _Data->Form.Resident.ValueOffset); + } +} + + +INLINE +USHORT +NTFS_ATTRIBUTE_RECORD::QueryResidentValueOffset( + ) CONST +/*++ + +Routine Description: + + This method returns the attribute record's resident value offset. + If the attribute record is non-resident, it returns 0. + +Arguments: + + None. + +Return Value: + + The attribute record's resident value offset; 0 if the attribute + record is non-resident. + +--*/ +{ + if( _Data->FormCode == NONRESIDENT_FORM ) { + + return NULL; + + } else { + + return _Data->Form.Resident.ValueOffset; + } +} + + +INLINE +ULONG +NTFS_ATTRIBUTE_RECORD::QueryResidentValueLength( + ) CONST +/*++ + +Routine Description: + + This method returns the attribute record's resident value + length (zero if the attribute is nonresident). + +Arguments: + + None. + +Return Value: + + Who Cares. + +--*/ +{ + return( (_Data->FormCode == NONRESIDENT_FORM) ? + 0 : + _Data->Form.Resident.ValueLength ); +} + +INLINE +VOID +NTFS_ATTRIBUTE_RECORD::DisableUnUse( + IN BOOLEAN NewState + ) +{ + _DisableUnUse = NewState; +} + +INLINE +BOOLEAN +NTFS_ATTRIBUTE_RECORD::IsUnUseDisabled( + ) +{ + return _DisableUnUse; +} + + diff --git a/untfs/inc/badfile.hxx b/untfs/inc/badfile.hxx new file mode 100644 index 0000000..e23a4c1 --- /dev/null +++ b/untfs/inc/badfile.hxx @@ -0,0 +1,90 @@ +/*++ + +Module Name: + + badfile.hxx + +Abstract: + + This module contains the declarations for the NTFS_BAD_CLUSTER_FILE + class, which models the bad cluster file for an NTFS volume. + + The DATA attribute of the bad cluster file is a non-resident + attribute to which bad clusters are allocated. It is stored + as a sparse file with LCN = VCN. + +--*/ + +#pragma once + +#include "frs.hxx" + +DECLARE_CLASS( IO_DP_DRIVE ); +DECLARE_CLASS( NTFS_ATTRIBUTE); +DECLARE_CLASS( NTFS_MASTER_FILE_TABLE ); +DECLARE_CLASS( NTFS_BITMAP ); +DECLARE_CLASS( NUMBER_SET ); + +class NTFS_BAD_CLUSTER_FILE : public NTFS_FILE_RECORD_SEGMENT { + +public: + + DECLARE_CONSTRUCTOR(NTFS_BAD_CLUSTER_FILE); + + + VIRTUAL + ~NTFS_BAD_CLUSTER_FILE( + ); + + BOOLEAN + Initialize( + IN OUT PNTFS_MASTER_FILE_TABLE Mft + ); + + + BOOLEAN + Add( + IN LCN Lcn + ); + + + BOOLEAN + Add( + IN PCNUMBER_SET ClustersToAdd + ); + + + BOOLEAN + AddRun( + IN LCN Lcn, + IN BIG_INT RunLength + ); + + + BOOLEAN + IsInList( + IN LCN Lcn + ); + + + BOOLEAN + Flush( + IN OUT PNTFS_BITMAP Bitmap, + IN OUT PNTFS_INDEX_TREE ParentIndex DEFAULT NULL + ); + +private: + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + PNTFS_ATTRIBUTE _DataAttribute; +}; + + diff --git a/untfs/inc/bitfrs.hxx b/untfs/inc/bitfrs.hxx new file mode 100644 index 0000000..d488410 --- /dev/null +++ b/untfs/inc/bitfrs.hxx @@ -0,0 +1,49 @@ +/*++ + +Module Name: + + bitfrs.hxx + +Abstract: + + This module contains the declarations for the NTFS_BITMAP_FILE + class, which models the bitmap file for an NTFS volume. + +--*/ + +#pragma once + +#include "frs.hxx" + +class NTFS_BITMAP_FILE : public NTFS_FILE_RECORD_SEGMENT { + + public: + + + DECLARE_CONSTRUCTOR( NTFS_BITMAP_FILE ); + + VIRTUAL + ~NTFS_BITMAP_FILE( + ); + + + + BOOLEAN + Initialize( + IN OUT PNTFS_MASTER_FILE_TABLE Mft + ); + + private: + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + +}; + diff --git a/untfs/inc/clusrun.hxx b/untfs/inc/clusrun.hxx new file mode 100644 index 0000000..c013f2b --- /dev/null +++ b/untfs/inc/clusrun.hxx @@ -0,0 +1,327 @@ +/*++ + +Module Name: + + clusrun.hxx + +Abstract: + + This class models a run of clusters on an NTFS volume. Its + principle purpose is to mediate between the cluster-oriented + NTFS volume and the sector-oriented drive object. + +--*/ + +#pragma once + +#include "secrun.hxx" + + +class NTFS_CLUSTER_RUN : public SECRUN { + + public: + + + DECLARE_CONSTRUCTOR( NTFS_CLUSTER_RUN ); + + + VIRTUAL + ~NTFS_CLUSTER_RUN( + ); + + + + BOOLEAN + Initialize( + IN PMEM Mem, + IN PLOG_IO_DP_DRIVE Drive, + IN LCN Lcn, + IN ULONG ClusterFactor, + IN ULONG NumberOfClusters + ); + + + + VOID + Relocate( + IN LCN NewLcn + ); + + + LCN + QueryStartLcn( + ) CONST; + + + ULONG + QueryClusterFactor( + ) CONST; + + + PLOG_IO_DP_DRIVE + GetDrive( + ); + + + VOID + MarkModified( + ); + + + BOOLEAN + IsModified( + ) CONST; + + VIRTUAL + BOOLEAN + Write( + ); + + VIRTUAL + BOOLEAN + Write( + IN BOOLEAN OnlyIfModified + ); + + protected: + + + USHORT + QueryClusterSize( + ) CONST; + + private: + + + VOID + Construct ( + ); + + + VOID + Destroy ( + ); + + + LCN _StartLcn; + ULONG _ClusterFactor; + PLOG_IO_DP_DRIVE _Drive; + BOOLEAN _IsModified; + +}; + + +INLINE +LCN +NTFS_CLUSTER_RUN::QueryStartLcn ( + ) CONST +/*++ + +Routine Description: + + This method gives the client the first LCN of the cluster run. + +Arguments: + + StartLcn -- receives the first LCN of the cluster run. + +Return Value: + + None. + +--*/ +{ + return _StartLcn; +} + + +INLINE +ULONG +NTFS_CLUSTER_RUN::QueryClusterFactor( + ) CONST +/*++ + +Routine Description: + + This method returns the number of sectors per cluster in + this cluster run. + +Arguments: + + None. + +Return Value: + + The cluster run's cluster factor. + +++*/ +{ + return _ClusterFactor; +} + + +INLINE +PLOG_IO_DP_DRIVE +NTFS_CLUSTER_RUN::GetDrive( + ) +/*++ + +Routine Description: + + This method returns the drive on which the Cluster Run resides. + This functionality enables clients of Cluster Run to initialize + other Cluster Runs on the same drive. + +Arguments: + + None. + +Return Value: + + The drive on which the Cluster Run resides. + +--*/ +{ + return _Drive; +} + + +INLINE +VOID +NTFS_CLUSTER_RUN::MarkModified( + ) +/*++ + +Routine Description: + + Mark the Cluster Run as modified. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _IsModified = TRUE; +} + + +INLINE +BOOLEAN +NTFS_CLUSTER_RUN::IsModified( + ) CONST +/*++ + +Routine Description: + + Query whether the Cluster Run has been marked as modified. + +Arguments: + + None. + +Return Value: + + TRUE if the Cluster Run has been marked as modified. + +--*/ +{ + return( _IsModified ); +} + + +INLINE +BOOLEAN +NTFS_CLUSTER_RUN::Write( + ) +/*++ + +Routine Description: + + This method writes the Cluster Run. + +Arguments: + + None. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method is provided to keep the Write method on SECRUN visible. + +--*/ +{ + return SECRUN::Write(); +} + + +INLINE +BOOLEAN +NTFS_CLUSTER_RUN::Write( + IN BOOLEAN OnlyIfModified + ) +/*++ + +Routine Description: + + This method writes the Cluster Run; it also allows the client + to specify that it should only be written if it has been modified. + +Arguments: + + OnlyIfModified -- supplies a flag indicating whether the write + is conditional; if this is TRUE, then the + Cluster Run is written only if it has been + marked as modified. + +Return Value: + + TRUE upon successful completion. + +Notes: + + The Cluster Run does not mark itself dirty; if clients want + to take advantage of the ability to write only if modified, + they have to be sure to call MarkModified appropriately. + +--*/ +{ + _IsModified = (BOOLEAN) + !((OnlyIfModified && !_IsModified) || SECRUN::Write()); + + return !_IsModified; +} + + +INLINE +USHORT +NTFS_CLUSTER_RUN::QueryClusterSize( + ) CONST +/*++ + +Routine Description: + + This method returns the number of bytes per cluster. + +Arguments: + + None. + +Return Value: + + Number of bytes per cluster (zero indicates error). + +--*/ +{ + DebugPtrAssert( _Drive ); + + return( (USHORT)_ClusterFactor * (USHORT)_Drive->QuerySectorSize() ); +} + diff --git a/untfs/inc/extents.hxx b/untfs/inc/extents.hxx new file mode 100644 index 0000000..50fa73d --- /dev/null +++ b/untfs/inc/extents.hxx @@ -0,0 +1,346 @@ +/*++ + +Module Name: + + extents.hxx + +Abstract: + + This module contains the declarations for NTFS_EXTENT_LIST, which + models a set of NTFS extents. + + An extent is a contiguous run of clusters; a non-resident + attribute's value is made up of a list of extents. The + NTFS_EXTENT_LIST object can be used to describe the disk space + allocated to a non-resident attribute. + + This class also encapsulates the knowledge of mapping pairs + and their compression, i.e. of the representation of extent + lists in attribute records. + +--*/ + +#pragma once + +DECLARE_CLASS( NTFS_BITMAP ); +DECLARE_CLASS( NTFS_EXTENT_LIST ); +DECLARE_CLASS( NTFS_EXTENT ); + +typedef struct _MAPPING_PAIR { + + VCN NextVcn; + LCN CurrentLcn; +}; + +DEFINE_TYPE( _MAPPING_PAIR, MAPPING_PAIR ); + + +class NTFS_EXTENT : public OBJECT { + + public: + + DECLARE_CONSTRUCTOR( NTFS_EXTENT ); + + VCN Vcn; + LCN Lcn; + BIG_INT RunLength; + +}; + + +class NTFS_EXTENT_LIST : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( NTFS_EXTENT_LIST ); + + VIRTUAL + ~NTFS_EXTENT_LIST( + ); + + + BOOLEAN + Initialize( + IN VCN LowestVcn, + IN VCN NextVcn + ); + + + BOOLEAN + Initialize( + IN VCN StartingVcn, + IN PCVOID CompressedMappingPairs, + IN ULONG MappingPairsMaximumLength, + OUT PBOOLEAN BadMappingPairs DEFAULT NULL + ); + + + BOOLEAN + Initialize( + IN PCNTFS_EXTENT_LIST ExtentsToCopy + ); + + FRIEND + BOOLEAN + Initialize( + IN PCNTFS_EXTENT_LIST ExtentsToCopy + ); + + + BOOLEAN + IsEmpty( + ) CONST; + + + BOOLEAN + IsSparse( + ) CONST; + + + + ULONG + QueryNumberOfExtents( + ) CONST; + + + BOOLEAN + AddExtent( + IN VCN Vcn, + IN LCN Lcn, + IN BIG_INT RunLength + ); + + + BOOLEAN + AddExtents( + IN VCN StartingVcn, + IN PCVOID CompressedMappingPairs, + IN ULONG MappingPairsMaximumLength, + OUT PBOOLEAN BadMappingPairs DEFAULT NULL + ); + + + VOID + DeleteExtent( + IN ULONG ExtentNumber + ); + + + BOOLEAN + Resize( + IN BIG_INT NewSize, + IN OUT PNTFS_BITMAP Bitmap + ); + + + BOOLEAN + SetSparse( + IN BIG_INT NewSize + ); + + + BOOLEAN + QueryExtent( + IN ULONG ExtentNumber, + OUT PVCN Vcn, + OUT PLCN Lcn, + OUT PBIG_INT RunLength + ) CONST; + + + BOOLEAN + QueryLcnFromVcn( + IN VCN Vcn, + OUT PLCN Lcn, + OUT PBIG_INT RunLength DEFAULT NULL + ) CONST; + + + BOOLEAN + QueryCompressedMappingPairs( + OUT PVCN LowestVcn, + OUT PVCN NextVcn, + OUT PULONG MappingPairsLength, + IN ULONG BufferSize, + IN OUT PVOID Buffer, + OUT PBOOLEAN HasHoleInFront DEFAULT NULL + ) CONST; + + + VCN + QueryLowestVcn( + ) CONST; + + + VCN + QueryNextVcn( + ) CONST; + + + VOID + SetLowestVcn( + IN BIG_INT LowestVcn + ); + + + VOID + SetNextVcn( + IN BIG_INT NextVcn + ); + + + BIG_INT + QueryClustersAllocated( + ) CONST; + + + BOOLEAN + DeleteRange( + IN VCN Vcn, + IN BIG_INT RunLength + ); + + + private: + + + VOID + Construct ( + ); + + + VOID + Destroy( + ); + + + LCN + QueryLastLcn( + ) CONST; + + + VOID + Truncate( + IN BIG_INT NewNumberOfClusters, + IN OUT PNTFS_BITMAP Bitmap + ); + + + VOID + Coalesce( + ); + + STATIC + BOOLEAN + QueryMappingPairsLength( + IN PCVOID CompressedPairs, + IN ULONG MaximumLength, + OUT PULONG Length, + OUT PULONG NumberOfPairs + ); + + STATIC + BOOLEAN + ExpandMappingPairs( + IN PCVOID CompressedPairs, + IN VCN StartingVcn, + IN ULONG MaximumCompressedLength, + IN ULONG MaximumNumberOfPairs, + IN OUT PMAPPING_PAIR MappingPairs, + OUT PULONG NumberOfPairs + ); + + STATIC + BOOLEAN + CompressMappingPairs( + IN PCMAPPING_PAIR MappingPairs, + IN ULONG NumberOfPairs, + IN VCN StartingVcn, + IN OUT PVOID CompressedPairs, + IN ULONG MaximumCompressedLength, + OUT PULONG CompressedLength + ); + + struct _LARGE_MCB* _Mcb; + BOOLEAN _McbInitialized; + VCN _LowestVcn; + VCN _NextVcn; +}; + + +INLINE +BOOLEAN +NTFS_EXTENT_LIST::IsEmpty( + ) CONST +/*++ + +Routine Description: + + This method determines whether the extent list is empty. + +Arguments: + + None. + +Return Value: + + TRUE if there are no extents in the list. + +--*/ +{ + return ( _LowestVcn == _NextVcn ); +} + + +INLINE +VCN +NTFS_EXTENT_LIST::QueryLowestVcn( + ) CONST +/*++ + +Routine Description: + + This method returns the lowest VCN covered by this extent + list. Note that for a sparse file, this is not necessarily + the same as the VCN of the first extent in the list. + +Arguments: + + None. + +Return Value: + + The lowest VCN mapped by this extent list. + +--*/ +{ + return _LowestVcn; +} + + +INLINE +LCN +NTFS_EXTENT_LIST::QueryNextVcn( + ) CONST +/*++ + +Routine Description: + + This method returns the highest VCN covered by this extent + list. Note that for a sparse file, this is not necessarily + the same as the last VCN of the last extent in the list. + +Arguments: + + None. + +Return Value: + + The highest VCN mapped by this extent list. + +--*/ +{ + return _NextVcn; +} + diff --git a/untfs/inc/frs.hxx b/untfs/inc/frs.hxx new file mode 100644 index 0000000..586e036 --- /dev/null +++ b/untfs/inc/frs.hxx @@ -0,0 +1,662 @@ +/*++ + +Module Name: + + frs.hxx + +Abstract: + + This module contains the declarations for the + NTFS_FILE_RECORD_SEGMENT class. This class models File + Record Segments in the NTFS Master File Table; it is the + object through which a file's attributes may be accessed. + +--*/ + +#pragma once + +#include "frsstruc.hxx" +#include "clusrun.hxx" +#include "array.hxx" +#include "hmem.hxx" +#include "list.hxx" +#include "iterator.hxx" + +// Possible return codes for SortIndex: +// +// NTFS_SORT_INDEX_NOT_FOUND -- this FRS does not contain an +// index with the specified name. +// NTFS_SORT_INDEX_WELL_ORDERED -- the index was not sorted because +// it was found to be well-ordered. +// NTFS_SORT_INDEX_BADLY_ORDERED -- The index was found to be badly +// ordered, and it was not sorted. +// NTFS_SORT_INDEX_SORTED -- The index was sorted and new +// attributes were inserted into +// the FRS. +// NTFS_SORT_INSERT_FAILED -- An insertion of an index entry +// into the new tree failed. +// (Probable cause: out of space.) +// NTFS_SORT_ERROR -- Sort failed because of an error. +// +// +typedef enum NTFS_SORT_CODE { + + NTFS_SORT_INDEX_NOT_FOUND, + NTFS_SORT_INDEX_WELL_ORDERED, + NTFS_SORT_INDEX_BADLY_ORDERED, + NTFS_SORT_INDEX_SORTED, + NTFS_SORT_INSERT_FAILED, + NTFS_SORT_ERROR +}; + +// Possible return codes for VerifyAndFixQuotaDefaultId: +// +// NTFS_QUOTA_INDEX_NOT_FOUND -- this FRS does not contain an +// index with the specified name. +// NTFS_QUOTA_DEFAULT_ENTRY_MISSING-- the default entry was not found +// in the index +// NTFS_QUOTA_INDEX_FOUND -- Found the default Id entry in the +// index tree. +// NTFS_QUOTA_INDEX_INSERTED -- Inserted the default Id entry into +// the index tree. +// NTFS_QUOTA_INSERT_FAILED -- An insertion of the default Id +// entry into the index tree failed. +// (Probable cause: out of space.) +// NTFS_QUOTA_ERROR -- error occurred. (Possibly out +// of memory or out of space.) +// +typedef enum NTFS_QUOTA_CODE { + + NTFS_QUOTA_INDEX_NOT_FOUND, + NTFS_QUOTA_INDEX_FOUND, + NTFS_QUOTA_DEFAULT_ENTRY_MISSING, + NTFS_QUOTA_INDEX_INSERTED, + NTFS_QUOTA_INSERT_FAILED, + NTFS_QUOTA_ERROR +}; + +// Possible return codes for FindSecurityIndexEntryAndValidate: +// +// NTFS_SECURITY_INDEX_ENTRY_MISSING -- the specified index entry key +// cannot be found in the index +// NTFS_SECURITY_INDEX_FOUND -- the found entry contains +// correct data +// NTFS_SECURITY_INDEX_FIXED -- the found entry contains invalid +// data but is now corrected +// NTFS_SECURITY_INDEX_DATA_ERROR -- The index was found but the data +// data in it is incorrect. +// NTFS_SECURITY_INDEX_INSERTED -- An index was successfully inserted +// into the specified index. +// NTFS_SECURITY_INSERT_FAILED -- An insertion of an index entry +// into the index tree failed. +// (Probable cause: out of space.) +// NTFS_SECURITY_ERROR -- failed because of an error. +// (Probably out of memory or out +// of space.) +// +typedef enum NTFS_SECURITY_CODE { + NTFS_SECURITY_INDEX_ENTRY_MISSING, + NTFS_SECURITY_INDEX_FOUND, + NTFS_SECURITY_INDEX_FIXED, + NTFS_SECURITY_INDEX_DATA_ERROR, + NTFS_SECURITY_INDEX_INSERTED, + NTFS_SECURITY_INSERT_FAILED, + NTFS_SECURITY_ERROR +}; + +// Forward references + +DECLARE_CLASS( IO_DP_DRIVE ); +DECLARE_CLASS( NTFS_MASTER_FILE_TABLE ); +DECLARE_CLASS( NTFS_MFT_FILE ); +DECLARE_CLASS( NTFS_ATTRIBUTE ); +DECLARE_CLASS( WSTRING ); +DECLARE_CLASS( NTFS_ATTRIBUTE_RECORD ); +DECLARE_CLASS( NTFS_ATTRIBUTE_RECORD_LIST ); +DECLARE_CLASS( NTFS_FILE_RECORD_SEGMENT ); +DECLARE_CLASS( NTFS_ATTRIBUTE_LIST ); +DECLARE_CLASS( NTFS_BITMAP ); +DECLARE_CLASS( NTFS_BAD_CLUSTER_FILE ); + + +class NTFS_FILE_RECORD_SEGMENT : public NTFS_FRS_STRUCTURE { + + public: + + + DECLARE_CONSTRUCTOR( NTFS_FILE_RECORD_SEGMENT ); + + VIRTUAL + + ~NTFS_FILE_RECORD_SEGMENT ( + ); + + + + BOOLEAN + Initialize ( + IN VCN FileNumber, + IN OUT PNTFS_MFT_FILE MftFile + ); + + + + BOOLEAN + Initialize ( + IN VCN FileNumber, + IN OUT PNTFS_MASTER_FILE_TABLE Mft + ); + + + + BOOLEAN + Initialize( + IN VCN FirstFileNumber, + IN ULONG FrsCount, + IN OUT PNTFS_MASTER_FILE_TABLE Mft + ); + + + + BOOLEAN + Initialize( + ); + + + + BOOLEAN + Create ( + IN PCSTANDARD_INFORMATION StandardInformation, + IN USHORT Flags DEFAULT 0 + ); + + + BOOLEAN + Create ( + IN PCMFT_SEGMENT_REFERENCE BaseSegment, + IN USHORT Flags DEFAULT 0 + ); + + + + VIRTUAL + BOOLEAN + Write( + ); + + + BOOLEAN + Flush( + IN OUT PNTFS_BITMAP VolumeBitmap OPTIONAL, + IN OUT PNTFS_INDEX_TREE ParentIndex DEFAULT NULL, + IN BOOLEAN FrsIsEmpty DEFAULT FALSE + ); + + + BOOLEAN + AddDataAttribute( + IN ULONG InitialSize, + IN OUT PNTFS_BITMAP VolumeBitmap, + IN BOOLEAN Fill DEFAULT FALSE, + IN CHAR FillCharacter DEFAULT 0 + ); + + + + BOOLEAN + AddFileNameAttribute( + IN PFILE_NAME FileNameAttributeValue + ); + + + BOOLEAN + AddAttribute( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name OPTIONAL, + IN PCVOID Value OPTIONAL, + IN ULONG Length, + IN OUT PNTFS_BITMAP Bitmap OPTIONAL, + IN BOOLEAN IsIndexed DEFAULT FALSE + ); + + + BOOLEAN + AddEmptyAttribute( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name DEFAULT NULL + ); + + + BOOLEAN + IsAttributePresent ( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name DEFAULT NULL, + IN BOOLEAN IgnoreExternal DEFAULT FALSE + ); + + + BOOLEAN + QueryAttributeRecord ( + OUT PNTFS_ATTRIBUTE_RECORD AttributeRecord, + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name DEFAULT NULL + ); + + + BOOLEAN + QueryAttribute ( + OUT PNTFS_ATTRIBUTE Attribute, + OUT PBOOLEAN Error, + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name DEFAULT NULL + ); + + + + BOOLEAN + QueryAttributeListAttribute ( + OUT PNTFS_ATTRIBUTE Attribute, + OUT PBOOLEAN Error + ); + + + BOOLEAN + QueryFileSizes ( + OUT PBIG_INT AllocatedLength, + OUT PBIG_INT FileSize, + OUT PBOOLEAN Error + ); + + + + BOOLEAN + QueryAttributeByOrdinal ( + OUT PNTFS_ATTRIBUTE Attribute, + OUT PBOOLEAN Error, + IN ATTRIBUTE_TYPE_CODE Type, + IN ULONG Ordinal + ); + + + BOOLEAN + QueryAttributeByTag ( + OUT PNTFS_ATTRIBUTE Attribute, + OUT PBOOLEAN Error, + IN ULONG Tag + ); + + + BOOLEAN + PurgeAttribute ( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name DEFAULT NULL, + IN BOOLEAN IgnoreExternal DEFAULT FALSE + ); + + + BOOLEAN + DeleteResidentAttribute( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name OPTIONAL, + IN PCVOID Value, + IN ULONG ValueLength, + OUT PBOOLEAN Deleted, + IN BOOLEAN IgnoreExternal DEFAULT FALSE + ); + + + BOOLEAN + DeleteResidentAttributeLocal( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name OPTIONAL, + IN PCVOID Value, + IN ULONG ValueLength, + OUT PBOOLEAN Deleted, + OUT PBOOLEAN IsIndexed, + OUT PUSHORT InstanceTag + ); + + VIRTUAL + BOOLEAN + InsertAttributeRecord ( + IN OUT PNTFS_ATTRIBUTE_RECORD NewRecord, + IN BOOLEAN ForceExternal DEFAULT FALSE + ); + + + USHORT + QueryNextInstance( + ); + + + VOID + IncrementNextInstance( + ); + + + ULONG + QueryFreeSpace( + ); + + + ULONG + QueryMaximumAttributeRecordSize ( + ) CONST; + + + BOOLEAN + QueryDuplicatedInformation( + OUT PDUPLICATED_INFORMATION DuplicatedInformation + ); + + + BOOLEAN + UpdateFileNames( + IN PDUPLICATED_INFORMATION DuplicatedInformation, + IN OUT PNTFS_INDEX_TREE Index OPTIONAL, + IN BOOLEAN IgnoreExternal + ); + + + BOOLEAN + Backtrack( + OUT PWSTRING Path + ); + + + VOID + SetLsn( + IN BIG_INT NewLsn + ); + + + BOOLEAN + PurgeAttributeList ( + ); + + + BOOLEAN + IsAttributeListPresent( + ); + + protected: + + + BOOLEAN + Initialize( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN LCN StartOfMft, + IN PNTFS_MASTER_FILE_TABLE Mft + ); + + private: + + + VOID + Construct ( + ); + + inline + + VOID + Destroy2 ( + ); + + + VOID + Destroy ( + ); + + + BOOLEAN + Create ( + IN USHORT Flags DEFAULT 0 + ); + + + BOOLEAN + SetupAttributeList( + ); + + + BOOLEAN + CreateAttributeList( + OUT PNTFS_ATTRIBUTE_LIST AttributeList + ); + + + BOOLEAN + SaveAttributeList( + PNTFS_BITMAP VolumeBitmap + ); + + + BOOLEAN + InsertExternalAttributeRecord( + IN PNTFS_ATTRIBUTE_RECORD NewRecord + ); + + + BOOLEAN + BacktrackWorker( + IN OUT PWSTRING Path + ); + + + PNTFS_FILE_RECORD_SEGMENT + SetupChild( + IN VCN FileNumber + ); + + + BOOLEAN + AddChild( + PNTFS_FILE_RECORD_SEGMENT ChildFrs + ); + + + PNTFS_FILE_RECORD_SEGMENT + GetChild( + VCN FileNumber + ); + + + VOID + DeleteChild( + VCN FileNumber + ); + + HMEM _Mem; + LIST _Children; + PITERATOR _ChildIterator; + PNTFS_MASTER_FILE_TABLE _Mft; + PNTFS_ATTRIBUTE_LIST _AttributeList; + +}; + + +INLINE +USHORT +NTFS_FILE_RECORD_SEGMENT::QueryNextInstance( + ) +/*++ + +Routine Description: + + This method fetches the current value of the FRS' + NextAttributeInstance field. + +Arguments: + + None. + +Return Value: + + The current value of the FRS' NextAttributeInstance field. + +--*/ +{ + return _FrsData->NextAttributeInstance; +} + + +INLINE +VOID +NTFS_FILE_RECORD_SEGMENT::IncrementNextInstance( + ) +/*++ + +Routine Description: + + This method increments the NextAttributeInstance field of + the File Record Segment. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _FrsData->NextAttributeInstance++; +} + + + +INLINE +ULONG +NTFS_FILE_RECORD_SEGMENT::QueryFreeSpace( + ) +/*++ + +Routine Description: + + This method returns the amount of free space following the + last Attribute Record in the File Record Segment. + +Arguments: + + None. + +Return Value: + + The amount of free space. + +Notes: + + This method assumes that the FRS is consistent. + +--*/ +{ + return _FrsData->BytesAvailable - _FrsData->FirstFreeByte; +} + + +INLINE +ULONG +NTFS_FILE_RECORD_SEGMENT::QueryMaximumAttributeRecordSize ( + ) CONST +/*++ + +Routine Description: + + This method returns the size of the largest attribute record + the File Record Segment will accept. Note that this is the + largest record it will ever accept, not what it can currently + accept. + +Arguments: + + None. + +Return Value: + + The size of the largest attribute record a File Record Segment + of this size can accept. + +--*/ +{ + ULONG temp; + + // + // Take a precaution to make sure this routine never returns a + // "negative" number. + // + + temp = _FrsData->FirstAttributeOffset + QuadAlign(sizeof(ATTRIBUTE_TYPE_CODE)); + + if (temp > QuerySize()) { + + return QuerySize(); + + } + + return QuerySize() - temp; +} + + +INLINE +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::AddEmptyAttribute( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name + ) +/*++ + +Routine Description: + + This method adds an empty, non-indexed, resident attribute of + the specified type to the FRS. + +Arguments: + + Type -- Supplies the attribute's type code. + Name -- Supplies the attribute's name. May be NULL, in which + case the attribute has no name. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + NTFS_ATTRIBUTE Attribute; + + return( Attribute.Initialize( GetDrive(), + QueryClusterFactor(), + NULL, + 0, + Type, + Name, + 0 ) && + Attribute.InsertIntoFile( this, NULL ) ); + +} + +INLINE +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::IsAttributeListPresent( + ) +/*++ + +Routine Description: + + This method checks for the presence of an attribute list. + +Arguments: + + N/A + +Return Value: + + TRUE if there is an attribute list. + +--*/ +{ + return (_AttributeList != NULL) || + IsAttributePresent($ATTRIBUTE_LIST, NULL, TRUE); +} + diff --git a/untfs/inc/frsstruc.hxx b/untfs/inc/frsstruc.hxx new file mode 100644 index 0000000..d0577fc --- /dev/null +++ b/untfs/inc/frsstruc.hxx @@ -0,0 +1,884 @@ +/*++ + +Module Name: + + frsstruc.hxx + +Abstract: + + This class models a file record segment structure. + +--*/ + +#pragma once + +#include "volume.hxx" +#include "ntfssa.hxx" + +DECLARE_CLASS( NTFS_FRS_STRUCTURE ); +DECLARE_CLASS( MEM ); +DECLARE_CLASS( NTFS_ATTRIBUTE ); +DECLARE_CLASS( LOG_IO_DP_DRIVE ); +DECLARE_CLASS( NTFS_CLUSTER_RUN ); +DECLARE_CLASS( WSTRING ); +DECLARE_CLASS( MESSAGE ); +DECLARE_CLASS( NTFS_ATTRIBUTE_LIST ); +DECLARE_CLASS( NTFS_BITMAP ); +DECLARE_CLASS( NTFS_UPCASE_TABLE ); + +class NTFS_FRS_STRUCTURE : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( NTFS_FRS_STRUCTURE ); + + VIRTUAL + + ~NTFS_FRS_STRUCTURE( + ); + + + + BOOLEAN + Initialize( + IN OUT PMEM Mem, + IN OUT PNTFS_ATTRIBUTE MftData, + IN VCN FileNumber, + IN ULONG ClusterFactor, + IN BIG_INT VolumeSectors, + IN ULONG FrsSize, + IN PNTFS_UPCASE_TABLE UpcaseTable OPTIONAL + ); + + + BOOLEAN + Initialize( + IN OUT PMEM Mem, + IN OUT PNTFS_ATTRIBUTE MftData, + IN VCN FirstFileNumber, + IN ULONG FrsCount, + IN ULONG ClusterFactor, + IN BIG_INT VolumeSectors, + IN ULONG FrsSize, + IN PNTFS_UPCASE_TABLE UpcaseTable + ); + + + BOOLEAN + Initialize( + IN OUT PMEM Mem, + IN OUT PLOG_IO_DP_DRIVE Drive, + IN LCN StartOfMft, + IN ULONG ClusterFactor, + IN BIG_INT VolumeSectors, + IN ULONG FrsSize, + IN PNTFS_UPCASE_TABLE UpcaseTable DEFAULT NULL, + IN ULONG Offset DEFAULT 0 + ); + + + + VIRTUAL + BOOLEAN + Read( + ); + + + + + BOOLEAN + Write( + ); + + + PVOID + GetNextAttributeRecord( + IN PCVOID AttributeRecord, + IN OUT PMESSAGE Message DEFAULT NULL, + OUT PBOOLEAN ErrorsFound DEFAULT NULL + ); + + + VOID + DeleteAttributeRecord( + IN OUT PVOID AttributeRecord + ); + + + + BOOLEAN + QueryAttributeList( + OUT PNTFS_ATTRIBUTE_LIST AttributeList + ); + + + PVOID + GetAttribute( + IN ULONG TypeCode + ); + + + PVOID + GetAttributeList( + ); + + + MFT_SEGMENT_REFERENCE + QuerySegmentReference( + ) CONST; + + + FILE_REFERENCE + QueryBaseFileRecordSegment( + ) CONST; + + + BOOLEAN + IsBase( + ) CONST; + + + BOOLEAN + IsInUse( + ) CONST; + + + VOID + ClearInUse( + ); + + + BOOLEAN + IsSystemFile( + ) CONST; + + + VOID + SetSystemFile( + ); + + + BOOLEAN + IsViewIndexPresent( + ) CONST; + + + VOID + SetViewIndexPresent( + ); + + + VOID + ClearViewIndexPresent( + ); + + + BOOLEAN + IsIndexPresent( + ) CONST; + + + VOID + SetIndexPresent( + ); + + + VOID + ClearIndexPresent( + ); + + + VCN + QueryFileNumber( + ) CONST; + + + ULONG + QueryClusterFactor( + ) CONST; + + + ULONG + QuerySize( + ) CONST; + + + PLOG_IO_DP_DRIVE + GetDrive( + ); + + + USHORT + QueryReferenceCount( + ) CONST; + + + VOID + SetReferenceCount( + IN USHORT ReferenceCount + ); + + + BIG_INT + QueryVolumeSectors( + ) CONST; + + + PNTFS_UPCASE_TABLE + GetUpcaseTable( + ); + + + VOID + SetUpcaseTable( + IN PNTFS_UPCASE_TABLE UpcaseTable + ); + + + LSN + QueryLsn( + ) CONST; + + protected: + + PFILE_RECORD_SEGMENT_HEADER _FrsData; + + private: + + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + PSECRUN _secrun; + PNTFS_ATTRIBUTE _mftdata; + + PNTFS_UPCASE_TABLE _upcase_table; + + VCN _file_number; + VCN _first_file_number; + ULONG _frs_count; + BOOLEAN _frs_state; + BOOLEAN _read_status; + ULONG _cluster_factor; + ULONG _size; + PLOG_IO_DP_DRIVE _drive; + BIG_INT _volume_sectors; + UCHAR _usa_check; + +}; + + +INLINE +MFT_SEGMENT_REFERENCE +NTFS_FRS_STRUCTURE::QuerySegmentReference( + ) CONST +/*++ + +Routine Description: + + This routine computes the segment reference value for this FRS. + +Arguments: + + None. + +Return Value: + + The segment reference value for this FRS. + +--*/ +{ + MFT_SEGMENT_REFERENCE SegmentReference; + + DebugAssert( _FrsData ); + + SegmentReference.LowPart = _file_number.GetLowPart(); + SegmentReference.HighPart = (USHORT) _file_number.GetHighPart(); + SegmentReference.SequenceNumber = _FrsData->SequenceNumber; + + return SegmentReference; +} + + +INLINE +FILE_REFERENCE +NTFS_FRS_STRUCTURE::QueryBaseFileRecordSegment( + ) CONST +/*++ + +Routine Description: + + This field contains a pointer to the base file record segment for + this file record segment. + +Arguments: + + None. + +Return Value: + + A FILE_REFERENCE to the base file record segment for this file + record segment. + +--*/ +{ + DebugAssert( _FrsData ); + + return _FrsData->BaseFileRecordSegment; +} + + +INLINE +BOOLEAN +NTFS_FRS_STRUCTURE::IsBase( + ) CONST +/*++ + +Routine Description: + + This method determines whether this File Record Segment is the + Base File Record Segment for its file. + +Arguments: + + None. + +Return Value: + + TRUE if this is a Base File Record Segment; FALSE otherwise. + +--*/ +{ + return( _FrsData->BaseFileRecordSegment.LowPart == 0 && + _FrsData->BaseFileRecordSegment.HighPart == 0 && + _FrsData->BaseFileRecordSegment.SequenceNumber == 0 ); +} + + +INLINE +BOOLEAN +NTFS_FRS_STRUCTURE::IsInUse( + ) CONST +/*++ + +Routine Description: + + This routine computes whether or not this file record segment is in + use. + +Arguments: + + None. + +Return Value: + + FALSE - This file record segment is not in use. + TRUE - This file record segment is in use. + +--*/ +{ + DebugAssert( _FrsData ); + + return (_FrsData->Flags & FILE_RECORD_SEGMENT_IN_USE) ? TRUE : FALSE; +} + + +INLINE +VOID +NTFS_FRS_STRUCTURE::ClearInUse( + ) +/*++ + +Routine Description: + + This routine clears the in use bit on this file record segment. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + DebugAssert( _FrsData ); + + _FrsData->Flags &= ~FILE_RECORD_SEGMENT_IN_USE; +} + + +INLINE +BOOLEAN +NTFS_FRS_STRUCTURE::IsSystemFile( + ) CONST +/*++ + +Routine Description: + + This routine computes whether or not this file record segment is a + system file. + +Arguments: + + None. + +Return Value: + + FALSE - This file record segment is not a system file. + TRUE - This file record segment is a system file. + +--*/ +{ + DebugAssert( _FrsData ); + + return (_FrsData->Flags & FILE_SYSTEM_FILE) ? TRUE : FALSE; +} + + +INLINE +VOID +NTFS_FRS_STRUCTURE::SetSystemFile( + ) +/*++ + +Routine Description: + + This routine sets the system file bit on this file record segment. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + DebugAssert( _FrsData ); + + _FrsData->Flags |= FILE_SYSTEM_FILE; +} + + +INLINE +BOOLEAN +NTFS_FRS_STRUCTURE::IsViewIndexPresent( + ) CONST +/*++ + +Routine Description: + + This routine computes whether or not the indices of file record segment + can be viewed. + +Arguments: + + None. + +Return Value: + + FALSE - The indices of this file record segment cannot be viewed. + TRUE - The indices of this file record segment can be viewed. + +--*/ +{ + DebugAssert( _FrsData ); + + return (_FrsData->Flags & FILE_VIEW_INDEX_PRESENT) ? TRUE : FALSE; +} + +INLINE +VOID +NTFS_FRS_STRUCTURE::SetViewIndexPresent( + ) +/*++ + +Routine Description: + + This routine sets the view index present bit on this file record segment. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + DebugAssert( _FrsData ); + + _FrsData->Flags |= FILE_VIEW_INDEX_PRESENT; +} + + +INLINE +VOID +NTFS_FRS_STRUCTURE::ClearViewIndexPresent( + ) +/*++ + +Routine Description: + + This routine clears the view index present bit on this file record segment. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + DebugAssert( _FrsData ); + + _FrsData->Flags &= ~FILE_VIEW_INDEX_PRESENT; +} + + +INLINE +BOOLEAN +NTFS_FRS_STRUCTURE::IsIndexPresent( + ) CONST +/*++ + +Routine Description: + + This routine computes whether or not this file record segment's + FILE_NAME_INDEX_PRESENT flag is set. + +Arguments: + + None. + +Return Value: + + FALSE - This file record segment's FILE_NAME_INDEX_PRESENT is NOT set. + TRUE - This file record segment's FILE_NAME_INDEX_PRESENT is set. + +--*/ +{ + DebugAssert( _FrsData ); + + return (_FrsData->Flags & FILE_FILE_NAME_INDEX_PRESENT) ? TRUE : FALSE; +} + + +INLINE +VOID +NTFS_FRS_STRUCTURE::ClearIndexPresent( + ) +/*++ + +Routine Description: + + This routine clears the index present bit on this file record segment. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + DebugAssert( _FrsData ); + + _FrsData->Flags &= ~FILE_FILE_NAME_INDEX_PRESENT; +} + + +INLINE +VOID +NTFS_FRS_STRUCTURE::SetIndexPresent( + ) +/*++ + +Routine Description: + + This routine sets the index present bit on this file record segment. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + DebugAssert( _FrsData ); + + _FrsData->Flags |= FILE_FILE_NAME_INDEX_PRESENT; +} + + +INLINE +ULONG +NTFS_FRS_STRUCTURE::QuerySize( + ) CONST +/*++ + +Routine Description: + + This routine computes the number of bytes in this file + record segment. + +Arguments: + + None. + +Return Value: + + The number of bytes in this file record segment. + +--*/ +{ + return _size; +} + + +INLINE +VCN +NTFS_FRS_STRUCTURE::QueryFileNumber( + ) CONST +/*++ + +Routine Description: + + This method returns the File Number of the File Record Segment. + +Arguments: + + None. + +Return Value: + + the File Number (i.e. ordinal number within the MFT) of this + File Record Segment. + +--*/ +{ + return _file_number; +} + + +INLINE +ULONG +NTFS_FRS_STRUCTURE::QueryClusterFactor( + ) CONST +/*++ + +Routine Description: + + This method returns the cluster factor. + +Arguments: + + None. + +Return Value: + + The cluster factor with which this File Record Segment was initialized. + +--*/ +{ + return _cluster_factor; +} + +INLINE +PLOG_IO_DP_DRIVE +NTFS_FRS_STRUCTURE::GetDrive( + ) +/*++ + +Routine Description: + + This method returns the drive on which the File Record Segment + resides. This functionality enables clients to initialize + other File Record Segments on the same drive. + +Arguments: + + None. + +Return Value: + + The drive on which the File Record Segment resides. + +--*/ +{ + return _drive; +} + + +INLINE +USHORT +NTFS_FRS_STRUCTURE::QueryReferenceCount( + ) CONST +/*++ + +Routine Description: + + This routine returns the value of the reference count field + in this frs. + +Arguments: + + None. + +Return Value: + + The value of the reference count field in this frs. + +--*/ +{ + return _FrsData->ReferenceCount; +} + + +INLINE +VOID +NTFS_FRS_STRUCTURE::SetReferenceCount( + IN USHORT ReferenceCount + ) +/*++ + +Routine Description: + + This routine sets the value of the reference count field + in this frs. + +Arguments: + + ReferenceCount - Supplies the new reference count. + +Return Value: + + None. + +--*/ +{ + _FrsData->ReferenceCount = ReferenceCount; +} + + +INLINE +BIG_INT +NTFS_FRS_STRUCTURE::QueryVolumeSectors( + ) CONST +/*++ + +Routine Description: + + This routine returns the number of sectors on the volume as recorded in + the boot sector. + +Arguments: + + None. + +Return Value: + + The number of volume sectors. + +--*/ +{ + return _volume_sectors; +} + + +INLINE +PNTFS_UPCASE_TABLE +NTFS_FRS_STRUCTURE::GetUpcaseTable( + ) +/*++ + +Routine Description: + + This method fetches the upcase table for the volume on which + this FRS resides. + +Arguments: + + None. + +Return Value: + + The volume upcase table. + +--*/ +{ + return _upcase_table; +} + +INLINE +VOID +NTFS_FRS_STRUCTURE::SetUpcaseTable( + IN PNTFS_UPCASE_TABLE UpcaseTable + ) +/*++ + +Routine Description: + + This method sets the upcase table for the volume on which + this FRS resides. + +Arguments: + + UpcaseTable -- Supplies the volume upcase table. + +Return Value: + + None. + +--*/ +{ + _upcase_table = UpcaseTable; +} + + +INLINE +LSN +NTFS_FRS_STRUCTURE::QueryLsn( + ) CONST +/*++ + +Routine Description: + + This routine returns the logical sequence number for this file + record segment. + +Arguments: + + None. + +Return Value: + + The logical sequence number for this file record segment. + +--*/ +{ + DebugAssert( _FrsData ); + + return _FrsData->Lsn; +} + + diff --git a/untfs/inc/fsrtlp.h b/untfs/inc/fsrtlp.h new file mode 100644 index 0000000..3f2b78a --- /dev/null +++ b/untfs/inc/fsrtlp.h @@ -0,0 +1,265 @@ +/*++ + +Module Name: + + fsrtlp.h + +Abstract: + + This header file is included by largemcb.h, and is used to stub out the + kernel-only subroutine calls, as well as declare types and functions + provided by the MCB package. + +--*/ + + +#define NTKERNELAPI + +typedef ULONG ERESOURCE, *PERESOURCE; +typedef ULONG FAST_MUTEX, *PFAST_MUTEX; +typedef ULONG KEVENT, *PKEVENT; +typedef ULONG KMUTEX, *PKMUTEX; + +typedef enum _POOL_TYPE { + NonPagedPool, + PagedPool, + NonPagedPoolMustSucceed, + DontUseThisType, + NonPagedPoolCacheAligned, + PagedPoolCacheAligned, + NonPagedPoolCacheAlignedMustS, + MaxPoolType +} POOL_TYPE; + +typedef ULONG VBN, *PVBN; +typedef ULONG LBN, *PLBN; +typedef LONGLONG LBN64, *PLBN64; + +#define PAGED_CODE() /* nothing */ +#define DebugTrace(a, b, c, d) /* nothing */ +#define ExInitializeFastMutex(a) /* nothing */ +#define ExAcquireFastMutex(a) /* nothing */ +#define ExReleaseFastMutex(a) /* nothing */ +#define ExAcquireSpinLock(a, b) /* nothing */ +#define ExReleaseSpinLock(a, b) /* nothing */ + +#define ExIsFullZone(a) FALSE +#define ExAllocateFromZone(a) ((PVOID)1) +#define ExIsObjectInFirstZoneSegment(a, b) TRUE +#define ExFreeToZone(a, p) /* nothing */ + +#define try_return(S) { S; goto try_exit; } + +extern "C" +PVOID +MemAlloc( + IN ULONG Size + ); + +extern "C" +PVOID +MemAllocOrRaise( + IN ULONG Size + ); + +extern "C" +VOID +MemFree( + IN PVOID Addr + ); + +#define ExAllocatePool(type, size) MemAlloc(size) +#define FsRtlAllocatePool(type, size) MemAllocOrRaise(size) +#define ExFreePool(p) MemFree(p) + +// +// Large Integer Mapped Control Blocks routines, implemented in LargeMcb.c +// +// An LARGE_MCB is an opaque structure but we need to declare the size of +// it here so that users can allocate space for one. Consequently the +// size computation here must be updated by hand if the MCB changes. +// +// Current the structure consists of the following. +// PVOID +// ULONG +// ULONG +// POOL_TYPE (enumerated type) +// PVOID +// +// We will round the structure up to a quad-word boundary. +// + +typedef struct _LARGE_MCB { +#ifdef _WIN64 + ULONG Opaque[ 8 ]; +#else + ULONG Opaque[ 6 ]; +#endif +} LARGE_MCB; +typedef LARGE_MCB *PLARGE_MCB; + +NTKERNELAPI +VOID +FsRtlInitializeLargeMcb ( + IN PLARGE_MCB Mcb, + IN POOL_TYPE PoolType + ); + +NTKERNELAPI +VOID +FsRtlUninitializeLargeMcb ( + IN PLARGE_MCB Mcb + ); + +NTKERNELAPI +VOID +FsRtlTruncateLargeMcb ( + IN PLARGE_MCB Mcb, + IN LONGLONG Vbn + ); + +NTKERNELAPI +BOOLEAN +FsRtlAddLargeMcbEntry ( + IN PLARGE_MCB Mcb, + IN LONGLONG Vbn, + IN LONGLONG Lbn, + IN LONGLONG SectorCount + ); + +NTKERNELAPI +VOID +FsRtlRemoveLargeMcbEntry ( + IN PLARGE_MCB Mcb, + IN LONGLONG Vbn, + IN LONGLONG SectorCount + ); + +NTKERNELAPI +BOOLEAN +FsRtlLookupLargeMcbEntry ( + IN PLARGE_MCB Mcb, + IN LONGLONG Vbn, + OUT PLONGLONG Lbn OPTIONAL, + OUT PLONGLONG SectorCountFromLbn OPTIONAL, + OUT PLONGLONG StartingLbn OPTIONAL, + OUT PLONGLONG SectorCountFromStartingLbn OPTIONAL, + OUT PULONG Index OPTIONAL + ); + +NTKERNELAPI +BOOLEAN +FsRtlLookupLastLargeMcbEntry ( + IN PLARGE_MCB Mcb, + OUT PLONGLONG Vbn, + OUT PLONGLONG Lbn + ); + +NTKERNELAPI +ULONG +FsRtlNumberOfRunsInLargeMcb ( + IN PLARGE_MCB Mcb + ); + +NTKERNELAPI +BOOLEAN +FsRtlGetNextLargeMcbEntry ( + IN PLARGE_MCB Mcb, + IN ULONG RunIndex, + OUT PLONGLONG Vbn, + OUT PLONGLONG Lbn, + OUT PLONGLONG SectorCount + ); + +NTKERNELAPI +BOOLEAN +FsRtlSplitLargeMcb ( + IN PLARGE_MCB Mcb, + IN LONGLONG Vbn, + IN LONGLONG Amount + ); + + +// +// Mapped Control Blocks routines, implemented in Mcb.c +// +// An MCB is an opaque structure but we need to declare the size of +// it here so that users can allocate space for one. Consequently the +// size computation here must be updated by hand if the MCB changes. +// + +typedef struct _MCB { + ULONG Opaque[ 4 + (sizeof(PKMUTEX)+3)/4 ]; +} MCB; +typedef MCB *PMCB; + +NTKERNELAPI +VOID +FsRtlInitializeMcb ( + IN PMCB Mcb, + IN POOL_TYPE PoolType + ); + +NTKERNELAPI +VOID +FsRtlUninitializeMcb ( + IN PMCB Mcb + ); + +NTKERNELAPI +VOID +FsRtlTruncateMcb ( + IN PMCB Mcb, + IN VBN Vbn + ); + +NTKERNELAPI +BOOLEAN +FsRtlAddMcbEntry ( + IN PMCB Mcb, + IN VBN Vbn, + IN LBN Lbn, + IN ULONG SectorCount + ); + +NTKERNELAPI +VOID +FsRtlRemoveMcbEntry ( + IN PMCB Mcb, + IN VBN Vbn, + IN ULONG SectorCount + ); + +NTKERNELAPI +BOOLEAN +FsRtlLookupMcbEntry ( + IN PMCB Mcb, + IN VBN Vbn, + OUT PLBN Lbn, + OUT PULONG SectorCount OPTIONAL, + OUT PULONG Index + ); + +NTKERNELAPI +BOOLEAN +FsRtlLookupLastMcbEntry ( + IN PMCB Mcb, + OUT PVBN Vbn, + OUT PLBN Lbn + ); + +NTKERNELAPI +ULONG +FsRtlNumberOfRunsInMcb ( + IN PMCB Mcb + ); + +NTKERNELAPI +BOOLEAN +FsRtlGetNextMcbEntry ( + IN PMCB Mcb, + IN ULONG RunIndex, + OUT PVBN Vbn, + OUT PLBN Lbn, + OUT PULONG SectorCount + ); diff --git a/untfs/inc/indxbuff.hxx b/untfs/inc/indxbuff.hxx new file mode 100644 index 0000000..3c8844a --- /dev/null +++ b/untfs/inc/indxbuff.hxx @@ -0,0 +1,375 @@ +/*++ + +Module Name: + + indxbuff.hxx + +Abstract: + + this module contains the declarations for the NTFS_INDEX_BUFFER + class, which models index buffers in NTFS index trees. + +--*/ + +#pragma once + +#include "hmem.hxx" +#include "indxtree.hxx" + +DECLARE_CLASS( LOG_IO_DP_DRIVE ); +DECLARE_CLASS( NTFS_ATTRIBUTE ); +DECLARE_CLASS( NTFS_INDEX_TREE ); +DECLARE_CLASS( NTFS_UPCASE_TABLE ); + +class NTFS_INDEX_BUFFER : public OBJECT { + + friend class NTFS_INDEX_TREE; + + /* + FRIEND + BOOLEAN + NTFS_INDEX_TREE::InsertIntoBuffer( + PNTFS_INDEX_BUFFER TargetBuffer, + PINTSTACK ParentTrail, + PCINDEX_ENTRY NewEntry, + PINDEX_ENTRY InsertionPoint + ); + + FRIEND + BOOLEAN + NTFS_INDEX_TREE::InsertIntoRoot( + PCINDEX_ENTRY NewEntry, + PINDEX_ENTRY InsertionPoint + ); + + FRIEND + BOOLEAN + NTFS_INDEX_TREE::GetNextParent( + ); + */ + + + public: + + DECLARE_CONSTRUCTOR( NTFS_INDEX_BUFFER ); + + VIRTUAL + ~NTFS_INDEX_BUFFER( + ); + + + BOOLEAN + Initialize( + IN PCLOG_IO_DP_DRIVE Drive, + IN VCN ThisBufferVcn, + IN ULONG ClusterSize, + IN ULONG ClustersPerBuffer, + IN ULONG BufferSize, + IN ULONG CollationRule, + IN PNTFS_UPCASE_TABLE UpcaseTable + ); + + + VOID + Create( + IN BOOLEAN IsLeaf, + IN VCN EndEntryDownpointer + ); + + + BOOLEAN + Read( + IN OUT PNTFS_ATTRIBUTE AllocationAttribute + ); + + + BOOLEAN + Write( + IN OUT PNTFS_ATTRIBUTE AllocationAttribute + ); + + + BOOLEAN + FindEntry( + IN PCINDEX_ENTRY SearchEntry, + IN OUT PULONG Ordinal, + OUT PINDEX_ENTRY* EntryFound + ); + + + BOOLEAN + InsertEntry( + IN PCINDEX_ENTRY NewEntry, + IN PINDEX_ENTRY InsertPoint DEFAULT NULL + ); + + + VOID + RemoveEntry( + IN PINDEX_ENTRY EntryToRemove + ); + + + PINDEX_ENTRY + GetFirstEntry( + ); + + + BOOLEAN + IsLeaf( + ) CONST; + + + VCN + QueryVcn( + ) CONST; + + + ULONG + QuerySize( + ) CONST; + + + PINDEX_ALLOCATION_BUFFER + GetData( + ); + + + PINDEX_ENTRY + FindSplitPoint( + ); + + + BOOLEAN + IsEmpty( + ); + + + BOOLEAN + SetLsn( + IN BIG_INT NewLsn + ); + + + LSN + QueryLsn( + ) CONST; + + BOOLEAN + Copy( + IN PNTFS_INDEX_BUFFER p, + IN PCLOG_IO_DP_DRIVE Drive + ); + private: + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + + VOID + InsertClump( + IN ULONG LengthOfClump, + IN PCVOID Clump + ); + + + VOID + RemoveClump( + IN ULONG LengthOfClump + ); + + + + VCN _ThisBufferVcn; + ULONG _ClusterSize; + ULONG _ClustersPerBuffer; + ULONG _BufferSize; + COLLATION_RULE _CollationRule; + PNTFS_UPCASE_TABLE _UpcaseTable; + + HMEM _Mem; + PINDEX_ALLOCATION_BUFFER _Data; +}; + + +INLINE +PINDEX_ENTRY +NTFS_INDEX_BUFFER::GetFirstEntry( + ) +/*++ + +Routine Description: + + This method returns a pointer to the first entry in the index buffer. + +Arguments: + + None. + +Return Value: + + A pointer to the first index entry in the buffer. + +--*/ +{ + return( (PINDEX_ENTRY)( (PBYTE)&(_Data->IndexHeader) + + _Data->IndexHeader.FirstIndexEntry ) ); +} + + + +INLINE +BOOLEAN +NTFS_INDEX_BUFFER::IsLeaf( + ) CONST +/*++ + +Routine Description: + + This method determines whether this index buffer is a leaf. + +Arguments: + + None. + +Return Value: + + TRUE if this buffer is a leaf; FALSE otherwise. + +--*/ +{ + return( !(_Data->IndexHeader.Flags & INDEX_NODE) ); +} + + +INLINE +VCN +NTFS_INDEX_BUFFER::QueryVcn( + ) CONST +/*++ + +Routine Description: + + This method returns the VCN within the index allocation attribute + of this index buffer. + +Arguments: + + None. + +Return Value: + + The VCN within the index allocation attribute of this index buffer. + +--*/ +{ + return _ThisBufferVcn; +} + + +INLINE +ULONG +NTFS_INDEX_BUFFER::QuerySize( + ) CONST +/*++ + +Routine Description: + + This method returns the size of this buffer. Note that it includes + any free space in the buffer, and that all buffers in a given tree + will have the same size. + +Arguments: + + None. + +Return Value: + + The size of the buffer. +--*/ +{ + return _BufferSize; +} + + +INLINE +PINDEX_ALLOCATION_BUFFER +NTFS_INDEX_BUFFER::GetData( + ) +/*++ + +Routine Description: + + This method returns the index buffer's data buffer. It's a back + door that allows the index tree to read and write the index buffer. + +Arguments: + + None. + +Return Value: + + The index buffer's data buffer. + +--*/ +{ + return _Data; +} + + +INLINE +BOOLEAN +NTFS_INDEX_BUFFER::SetLsn( + IN BIG_INT NewLsn + ) +/*++ + +Routine Description: + + This method sets the Log Sequence Number in the index buffer. + +Arguments: + + NewLsn -- Supplies the new LSN + +Return Value: + + Always returns TRUE. + +--*/ +{ + _Data->Lsn = NewLsn.GetLargeInteger(); + return TRUE; +} + + +INLINE +LSN +NTFS_INDEX_BUFFER::QueryLsn( + ) CONST +/*++ + +Routine Description: + + This method sets the Log Sequence Number in the index buffer. + +Arguments: + + NewLsn -- Supplies the new LSN + +Return Value: + + Always returns TRUE. + +--*/ +{ + return _Data->Lsn; +} + diff --git a/untfs/inc/indxroot.hxx b/untfs/inc/indxroot.hxx new file mode 100644 index 0000000..a0db66a --- /dev/null +++ b/untfs/inc/indxroot.hxx @@ -0,0 +1,268 @@ +/*++ + +Module Name: + + indxroot.hxx + +Abstract: + + this module contains the declarations for the NTFS_INDEX_ROOT + class, which models the root of an NTFS index + +--*/ + +#pragma once + +#include "untfs.hxx" +#include "untfs2.hxx" + +DECLARE_CLASS( NTFS_ATTRIBUTE ); +DECLARE_CLASS( NTFS_UPCASE_TABLE ); + +// If the index buffer size is smaller than the cluster size, we'll +// divide the index buffers into 512-byte blocks, and the ClustersPer- +// IndexBuffer item will actually be blocks per index buffer. +// + +const ULONG NTFS_INDEX_BLOCK_SIZE = 512; + +class NTFS_INDEX_ROOT : public OBJECT { + + public: + + DECLARE_CONSTRUCTOR( NTFS_INDEX_ROOT ); + + VIRTUAL + ~NTFS_INDEX_ROOT( + ); + + + BOOLEAN + Initialize( + IN PNTFS_ATTRIBUTE RootAttribute, + IN PNTFS_UPCASE_TABLE UpcaseTable, + IN ULONG MaximumSize + ); + + + BOOLEAN + Initialize( + IN ATTRIBUTE_TYPE_CODE IndexedAttributeType, + IN COLLATION_RULE CollationRule, + IN PNTFS_UPCASE_TABLE UpcaseTable, + IN ULONG ClustersPerBuffer, + IN ULONG BytesPerBuffer, + IN ULONG MaximumRootSize + ); + + + BOOLEAN + FindEntry( + IN PCINDEX_ENTRY SearchEntry, + IN OUT PULONG Ordinal, + OUT PINDEX_ENTRY* EntryFound + ); + + + BOOLEAN + InsertEntry( + IN PCINDEX_ENTRY NewEntry, + IN PINDEX_ENTRY InsertPoint DEFAULT NULL + ); + + + VOID + RemoveEntry( + PINDEX_ENTRY EntryToRemove + ); + + + PINDEX_ENTRY + GetFirstEntry( + ); + + + VOID + Recreate( + IN BOOLEAN IsLeaf, + IN VCN EndEntryDownpointer + ); + + + BOOLEAN + Write( + PNTFS_ATTRIBUTE RootAttribute + ); + + + ULONG + QueryClustersPerBuffer( + ); + + + ULONG + QueryBufferSize( + ); + + + ULONG + QueryIndexedAttributeType( + ); + + + COLLATION_RULE + QueryCollationRule( + ); + + + + private: + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + + + ULONG _MaximumSize; + ULONG _DataLength; + PINDEX_ROOT _Data; + PNTFS_UPCASE_TABLE _UpcaseTable; + + BOOLEAN _IsModified; + +}; + + +INLINE +PINDEX_ENTRY +NTFS_INDEX_ROOT::GetFirstEntry( + ) +/*++ + +Routine Description: + + This method returns a pointer to the first entry in the index root. + +Arguments: + + None. + +Return Value: + + A pointer to the first index entry in the root. + +--*/ +{ + return( (PINDEX_ENTRY)( (PBYTE)&(_Data->IndexHeader) + + _Data->IndexHeader.FirstIndexEntry ) ); +} + + +INLINE +ULONG +NTFS_INDEX_ROOT::QueryClustersPerBuffer( + ) +/*++ + +Routine Description: + + This method returns the number of clusters in each Index Allocation + Buffer in this index. + +Arguments: + + None. + +Return Value: + + Clusters per Buffer. + +--*/ +{ + return _Data->ClustersPerIndexBuffer; +} + + +INLINE +ULONG +NTFS_INDEX_ROOT::QueryBufferSize( + ) +/*++ + +Routine Description: + + This method returns the number of bytes in each Index Allocation + Buffer in this index. + +Arguments: + + None. + +Return Value: + + Bytes per Buffer. + +--*/ +{ + return _Data->BytesPerIndexBuffer; +} + + + +INLINE +ULONG +NTFS_INDEX_ROOT::QueryIndexedAttributeType( + ) +/*++ + +Routine Description: + + This method returns the Attribute Type Code of the attribute + which is indexed by this index. + +Arguments: + + None. + +Return Value: + + The attribute type code of the attributes in this index. + +--*/ +{ + return _Data->IndexedAttributeType; +} + + + +INLINE +COLLATION_RULE +NTFS_INDEX_ROOT::QueryCollationRule( + ) +/*++ + +Routine Description: + + This method marks the index root as modified. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + return _Data->CollationRule; +} + + diff --git a/untfs/inc/indxtree.hxx b/untfs/inc/indxtree.hxx new file mode 100644 index 0000000..c678ef1 --- /dev/null +++ b/untfs/inc/indxtree.hxx @@ -0,0 +1,439 @@ +/*++ + +Module Name: + + indxtree.hxx + +Abstract: + + this module contains the declarations for the NTFS_INDEX_TREE + class, which models index trees on an NTFS volume. + + An NTFS Index Tree consists of an index root and a set of + index buffers. The index root is stored as the value of + an INDEX_ROOT attribute; the index buffers are part of the + value of an INDEX_ALLOCATION attribute. + +--*/ + +#pragma once + +#include "hmem.hxx" +#include "intstack.hxx" + +DECLARE_CLASS( LOG_IO_DP_DRIVE ); +DECLARE_CLASS( WSTRING ); +DECLARE_CLASS( NTFS_ATTRIBUTE ); +DECLARE_CLASS( NTFS_BITMAP ); +DECLARE_CLASS( NTFS_INDEX_ROOT ); +DECLARE_CLASS( NTFS_INDEX_BUFFER ); +DECLARE_CLASS( NTFS_FILE_RECORD_SEGMENT ); +DECLARE_CLASS( NTFS_UPCASE_TABLE ); + +// This constant is given to FindEntry to indicate it should skip +// all matching entries. +// +CONST ULONG INDEX_SKIP = (ULONG)(-1); + +typedef enum INDEX_ITERATOR_STATE { + + INDEX_ITERATOR_RESET, + INDEX_ITERATOR_CURRENT, + INDEX_ITERATOR_INVALID, + INDEX_ITERATOR_DELETED, + INDEX_ITERATOR_CORRUPT +}; + +typedef enum INDEX_ENTRY_TYPE { + INDEX_ENTRY_WITH_DATA_TYPE_4 = 0, // value is used as an index to + INDEX_ENTRY_WITH_DATA_TYPE_8 = 1, // IndexEntryAttributeLength array; + INDEX_ENTRY_WITH_DATA_TYPE_12 = 2, // these values should not be changed + INDEX_ENTRY_WITH_DATA_TYPE_16 = 3, + INDEX_ENTRY_WITH_DATA_TYPE, + INDEX_ENTRY_WITH_FILE_NAME_TYPE, + INDEX_ENTRY_GENERIC_TYPE +}; + +LONG +NtfsCollate( + IN PCVOID Value1, + IN ULONG Length1, + IN PCVOID Value2, + IN ULONG Length2, + IN COLLATION_RULE CollationRule, + IN PNTFS_UPCASE_TABLE UpcaseTable OPTIONAL + ); + +LONG +CompareNtfsIndexEntries( + IN PCINDEX_ENTRY Entry1, + IN PCINDEX_ENTRY Entry2, + IN COLLATION_RULE CollationRule, + IN PNTFS_UPCASE_TABLE UpcaseTable OPTIONAL + ); + + +class NTFS_INDEX_TREE : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( NTFS_INDEX_TREE ); + + VIRTUAL + + ~NTFS_INDEX_TREE( + ); + + + + BOOLEAN + Initialize( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN OUT PNTFS_BITMAP VolumeBitmap, + IN PNTFS_UPCASE_TABLE UpcaseTable, + IN ULONG MaximumRootSize, + IN PNTFS_FILE_RECORD_SEGMENT SourceFrs, + IN PCWSTRING IndexName DEFAULT NULL + ); + + + BOOLEAN + Initialize( + IN ATTRIBUTE_TYPE_CODE IndexedAttributeType, + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN OUT PNTFS_BITMAP VolumeBitmap, + IN PNTFS_UPCASE_TABLE UpcaseTable, + IN COLLATION_RULE CollationRule, + IN ULONG IndexBufferSize, + IN ULONG MaximumRootSize, + IN PCWSTRING IndexName DEFAULT NULL + ); + + + + BOOLEAN + InsertEntry( + IN PCINDEX_ENTRY NewEntry, + IN BOOLEAN NoDuplicates DEFAULT TRUE, + IN PBOOLEAN Duplicate DEFAULT NULL + ); + + + + + BOOLEAN + Save( + IN OUT PNTFS_FILE_RECORD_SEGMENT TargetFrs + ); + + + + COLLATION_RULE + QueryCollationRule( + ); + + + ATTRIBUTE_TYPE_CODE + QueryIndexedAttributeType( + ); + + + UCHAR + QueryClustersPerBuffer( + ); + + + ULONG + QueryBufferSize( + ); + + + VOID + FreeAllocation( + ); + + + BOOLEAN + UpdateFileName( + IN PCFILE_NAME Name, + IN FILE_REFERENCE ContainingFile + ); + + + PCWSTRING + GetName( + ) CONST; + + STATIC + BOOLEAN + IsIndexEntryCorrupt( + IN PCINDEX_ENTRY IndexEntry, + IN ULONG MaximumLength, + IN PMESSAGE Message, + IN INDEX_ENTRY_TYPE IndexEntryType DEFAULT INDEX_ENTRY_GENERIC_TYPE + ); + + + private: + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + + BOOLEAN + FindEntry( + IN ULONG KeyLength, + IN PVOID KeyValue, + IN ULONG Ordinal, + OUT PINDEX_ENTRY* FoundEntry, + OUT PNTFS_INDEX_BUFFER* ContainingBuffer, + OUT PINTSTACK ParentTrail + ); + + + + BOOLEAN + InsertIntoRoot( + IN PCINDEX_ENTRY NewEntry, + IN PINDEX_ENTRY InsertionPoint DEFAULT NULL + ); + + + BOOLEAN + InsertIntoBuffer( + IN OUT PNTFS_INDEX_BUFFER TargetBuffer, + IN OUT PINTSTACK ParentTrail, + IN PCINDEX_ENTRY NewEntry, + IN PINDEX_ENTRY InsertionPoint DEFAULT NULL + ); + + + BOOLEAN + AllocateIndexBuffer( + OUT PVCN NewBufferVcn + ); + + + VOID + FreeIndexBuffer( + IN VCN BufferVcn + ); + + + ULONG + QueryMaximumEntrySize( + ) CONST; + + + BOOLEAN + CreateAllocationAttribute( + ); + + + PLOG_IO_DP_DRIVE _Drive; + ULONG _ClusterFactor; + ULONG _ClustersPerBuffer; + ULONG _BufferSize; + PNTFS_BITMAP _VolumeBitmap; + PNTFS_ATTRIBUTE _AllocationAttribute; + PNTFS_INDEX_ROOT _IndexRoot; + PNTFS_BITMAP _IndexAllocationBitmap; + + PWSTRING _Name; + ATTRIBUTE_TYPE_CODE _IndexedAttributeType; + COLLATION_RULE _CollationRule; + + PNTFS_UPCASE_TABLE _UpcaseTable; + + + // Iterator state information: + // + // Each index tree has a single iterator associated with it. + // This iterator oscillates among the following states: + // + // INDEX_ITERATOR_RESET -- the iterator is at the beginning + // of the index, and the next call + // to GetNext will return the first + // entry in the index. + // INDEX_ITERATOR_CURRENT -- _CurrentEntry points at the current + // entry. _IsCurrentEntryInRoot is + // TRUE if that entry is in the index + // root, otherwise _CurrentEntryBuffer + // points to the buffer that contains + // the entry, and _CurrentEntryTrail + // contains the parent trail of that + // buffer. In either case, + // _CurrentKeyOrdinal gives the + // ordinal of the current entry + // (ie. it is the nth entry for + // the current key). + // INDEX_ITERATOR_INVALID -- _CurrentEntry has been invalidated, + // and the tree must relocate the + // current entry. _CurrentKey, + // _CurrentKeyLength, and + // _CurrentKeyOrdinal give the entry + // information to locate the current + // entry. + // INDEX_ITERATOR_DELETED -- Differs from INDEX_ITERATOR_INVALID + // only in that _CurrentKey, + // _CurrentKeyLength, and + // _CurrentKeyOrdinal describe the + // next entry, rather than the current. + // INDEX_ITERATOR_CORRUPT -- The iterator (or the tree itself) + // has become corrupt; any attempt to + // use it will return error. + // + // Since the iterator is very closely coupled to the index + // tree, it is built into this class, rather than being maintained + // as a separate object. + + INDEX_ITERATOR_STATE _IteratorState; + + BOOLEAN _IsCurrentEntryInRoot; + PINDEX_ENTRY _CurrentEntry; + PNTFS_INDEX_BUFFER _CurrentBuffer; + INTSTACK _CurrentEntryTrail; + + ULONG _CurrentKeyOrdinal; + PVOID _CurrentKey; + ULONG _CurrentKeyLength; + ULONG _CurrentKeyMaxLength; + +}; + + + +INLINE + +COLLATION_RULE +NTFS_INDEX_TREE::QueryCollationRule( + ) +/*++ + +Routine Description: + + This method returns the collation rule by which this + index tree is ordered. + +Arguments: + + None. + +Return Value: + + The index tree's collation rule. + +--*/ +{ + return _CollationRule; +} + + +INLINE + +ATTRIBUTE_TYPE_CODE +NTFS_INDEX_TREE::QueryIndexedAttributeType( + ) +/*++ + +Routine Description: + + This method returns the type code of the attribute which is + indexed by this index tree. + +Arguments: + + None. + +Return Value: +--*/ +{ + return _IndexedAttributeType; +} + + +INLINE + +UCHAR +NTFS_INDEX_TREE::QueryClustersPerBuffer( + ) +/*++ + +Routine Description: + + This method returns the number of clusters in each allocation + buffer in this index tree. + +Arguments: + + None. + +Return Value: + + The number of clusters per allocation buffer in this tree. + +--*/ +{ + return (UCHAR)_ClustersPerBuffer; +} + +INLINE + +ULONG +NTFS_INDEX_TREE::QueryBufferSize( + ) +/*++ + +Routine Description: + + This method returns the size of each allocation + buffer in this index tree. + +Arguments: + + None. + +Return Value: + + The number of bytes per allocation buffer in this tree. + +--*/ +{ + return _BufferSize; +} + + +INLINE +PCWSTRING +NTFS_INDEX_TREE::GetName( + ) CONST +/*++ + +Routine Description: + + This method returns the name of the index. + +Arguments: + + None. + +Return Value: + + The name of the index. + +--*/ +{ + return _Name; +} + + diff --git a/untfs/inc/mft.hxx b/untfs/inc/mft.hxx new file mode 100644 index 0000000..53b3482 --- /dev/null +++ b/untfs/inc/mft.hxx @@ -0,0 +1,470 @@ +/*++ + +Module Name: + + mft.hxx + +Abstract: + + This module contains the declarations for the NTFS_MASTER_FILE_TABLE + class. The MFT is the root of the file system + +--*/ + +#pragma once + +#include "ntfsbit.hxx" + +DECLARE_CLASS( NTFS_ATTRIBUTE ); +DECLARE_CLASS( NTFS_UPCASE_TABLE ); + +class NTFS_MASTER_FILE_TABLE : public OBJECT { + + public: + + DECLARE_CONSTRUCTOR( NTFS_MASTER_FILE_TABLE ); + + VIRTUAL + ~NTFS_MASTER_FILE_TABLE( + ); + + + BOOLEAN + Initialize( + IN OUT PNTFS_ATTRIBUTE DataAttribute, + IN OUT PNTFS_BITMAP MftBitmap, + IN OUT PNTFS_BITMAP VolumeBitmap OPTIONAL, + IN PNTFS_UPCASE_TABLE UpcaseTable OPTIONAL, + IN ULONG ClusterFactor, + IN ULONG FrsSize, + IN ULONG SectorSize, + IN BIG_INT VolumeSectors, + IN BOOLEAN ReadOnly DEFAULT FALSE + ); + + + + BOOLEAN + AllocateFileRecordSegment( + OUT PVCN FileNumber, + IN BOOLEAN IsMft + ); + + + BOOLEAN + FreeFileRecordSegment( + IN VCN SegmentToFree + ); + + + + BOOLEAN + Extend( + IN ULONG NumberOfSegmentsToAdd + ); + + + PNTFS_ATTRIBUTE + GetDataAttribute( + ); + + + PNTFS_BITMAP + GetMftBitmap( + ); + + + PNTFS_BITMAP + GetVolumeBitmap( + ); + + + VOID + EnableMethods( + ); + + + VOID + DisableMethods( + ); + + + BOOLEAN + AreMethodsEnabled( + ) CONST; + + + ULONG + QueryClusterFactor( + ) CONST; + + + ULONG + QueryFrsSize( + ) CONST; + + + BIG_INT + QueryVolumeSectors( + ) CONST; + + + ULONG + QuerySectorSize( + ) CONST; + + + PNTFS_UPCASE_TABLE + GetUpcaseTable( + ); + + + VOID + SetUpcaseTable( + IN PNTFS_UPCASE_TABLE UpcaseTable + ); + + + private: + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + PNTFS_ATTRIBUTE _DataAttribute; + PNTFS_BITMAP _MftBitmap; + PNTFS_BITMAP _VolumeBitmap; + PNTFS_UPCASE_TABLE _UpcaseTable; + ULONG _ClusterFactor; + ULONG _BytesPerFrs; + BOOLEAN _MethodsEnabled; + BOOLEAN _ReadOnly; + BIG_INT _VolumeSectors; + ULONG _SectorSize; + +}; + + +INLINE +BOOLEAN +NTFS_MASTER_FILE_TABLE::FreeFileRecordSegment( + IN VCN SegmentToFree + ) +/*++ + +Routine Description: + + Free a File Record Segment in the Master File Table. + +Arguments: + + SegmentToFree -- supplies the virtual cluster number withing + the Master File Table of the segment to be + freed. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + DebugAssert(_MftBitmap); + + return _MethodsEnabled ? + (_MftBitmap->SetFree(SegmentToFree, 1), TRUE) : FALSE; +} + + +INLINE +PNTFS_ATTRIBUTE +NTFS_MASTER_FILE_TABLE::GetDataAttribute( + ) +/*++ + +Routine Description: + + This routine computes the MFT's $DATA attribute. + +Arguments: + + None. + +Return Value: + + The data attribute for this class. + +--*/ +{ + return _MethodsEnabled ? _DataAttribute : NULL; +} + + +INLINE +PNTFS_BITMAP +NTFS_MASTER_FILE_TABLE::GetMftBitmap( + ) +/*++ + +Routine Description: + + This routine computes the MFT's $BITMAP attribute. + +Arguments: + + None. + +Return Value: + + The mft bitmap. + +--*/ +{ + return _MethodsEnabled ? _MftBitmap : NULL; +} + + +INLINE +PNTFS_BITMAP +NTFS_MASTER_FILE_TABLE::GetVolumeBitmap( + ) +/*++ + +Routine Description: + + This routine return the volume bitmap + +Arguments: + + None. + +Return Value: + + The volume bitmap. + +--*/ +{ + return _MethodsEnabled ? _VolumeBitmap : NULL; +} + + +INLINE +VOID +NTFS_MASTER_FILE_TABLE::EnableMethods( + ) +/*++ + +Routine Description: + + This method enables the methods provided by this class. + + This method and its complement allow the user of this class to declare + when the passed in data attribute and mft bitmap are good enough to + use. + + After the class is initialized, all of the methods are enabled. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _MethodsEnabled = TRUE; +} + + +INLINE +VOID +NTFS_MASTER_FILE_TABLE::DisableMethods( + ) +/*++ + +Routine Description: + + This method disables the methods provided by this class. + + This method and its complement allow the user of this class to declare + when the passed in data attribute and mft bitmap are good enough to + use. + + After the class is initialized, all of the methods are enabled. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _MethodsEnabled = FALSE; +} + + +INLINE +BOOLEAN +NTFS_MASTER_FILE_TABLE::AreMethodsEnabled( + ) CONST +/*++ + +Routine Description: + + This method computes whether or not the methods are enabled. + +Arguments: + + None. + +Return Value: + + FALSE - The methods are not enabled. + TRUE - The methods are enabled. + +--*/ +{ + return _MethodsEnabled; +} + + +INLINE +ULONG +NTFS_MASTER_FILE_TABLE::QueryClusterFactor( + ) CONST +/*++ + +Routine Description: + + This routine computes the number of sectors per cluster. + +Arguments: + + None. + +Return Value: + + The number of sectors per cluster. + +--*/ +{ + return _ClusterFactor; +} + + +INLINE +BIG_INT +NTFS_MASTER_FILE_TABLE::QueryVolumeSectors( + ) CONST +/*++ + +Routine Description: + + This routine returns the number of sectors on the volume as recorded in + the boot sector. + +Arguments: + + None. + +Return Value: + + The number of volume sectors. + +--*/ +{ + return _VolumeSectors; +} + + +INLINE +ULONG +NTFS_MASTER_FILE_TABLE::QueryFrsSize( + ) CONST +/*++ + +Routine Description: + + This routine computes the number of clusters per FRS. + +Arguments: + + None. + +Return Value: + + The number of clusters per FRS. + +--*/ +{ + return _BytesPerFrs; +} + + +INLINE +PNTFS_UPCASE_TABLE +NTFS_MASTER_FILE_TABLE::GetUpcaseTable( + ) +/*++ + +Routine Description: + + This method fetches the upcase table for the volume. + +Arguments: + + None. + +Return Value: + + The volume upcase table. + +--*/ +{ + return _UpcaseTable; +} + + +INLINE +VOID +NTFS_MASTER_FILE_TABLE::SetUpcaseTable( + IN PNTFS_UPCASE_TABLE UpcaseTable + ) +/*++ + +Routine Description: + + This method sets the upcase table for the volume. + +Arguments: + + UpcaseTable -- Supplies the volume upcase table. + +Return Value: + + None. + +--*/ +{ + _UpcaseTable = UpcaseTable; +} + +INLINE +ULONG +NTFS_MASTER_FILE_TABLE::QuerySectorSize( + ) CONST +{ + return _SectorSize; +} + + diff --git a/untfs/inc/mftfile.hxx b/untfs/inc/mftfile.hxx new file mode 100644 index 0000000..d02f265 --- /dev/null +++ b/untfs/inc/mftfile.hxx @@ -0,0 +1,134 @@ +/*++ + +Module Name: + + mftfile.hxx + +Abstract: + + This module contains the declarations for the NTFS_MFT_FILE + class. The MFT is the root of the file system + +--*/ + +#pragma once + +#include "hmem.hxx" + +#include "bitfrs.hxx" +#include "clusrun.hxx" +#include "frs.hxx" +#include "attrib.hxx" +#include "ntfsbit.hxx" +#include "mft.hxx" +#include "mftref.hxx" + +DECLARE_CLASS( NTFS_MFT_FILE ); + + +class NTFS_MFT_FILE : public NTFS_FILE_RECORD_SEGMENT { + + public: + + + DECLARE_CONSTRUCTOR( NTFS_MFT_FILE ); + + VIRTUAL + + ~NTFS_MFT_FILE( + ); + + + + BOOLEAN + Initialize( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN LCN Lcn, + IN ULONG ClusterFactor, + IN ULONG FrsSize, + IN BIG_INT VolumeSectors, + IN OUT PNTFS_BITMAP VolumeBitmap OPTIONAL, + IN PNTFS_UPCASE_TABLE UpcaseTable OPTIONAL + ); + + + + BOOLEAN + Read( + ); + + BOOLEAN + Flush( + ); + + + PNTFS_MASTER_FILE_TABLE + GetMasterFileTable( + ); + + private: + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + + BOOLEAN + CheckMirrorSize( + IN OUT PNTFS_ATTRIBUTE MirrorDataAttribute, + IN BOOLEAN Fix, + IN OUT PNTFS_BITMAP VolumeBitmap, + OUT PLCN FirstLcn + ); + + + BOOLEAN + WriteMirror( + IN OUT PNTFS_ATTRIBUTE MirrorDataAttribute + ); + + + + LCN _FirstLcn; + NTFS_ATTRIBUTE _DataAttribute; + NTFS_BITMAP _MftBitmap; + NTFS_MASTER_FILE_TABLE _Mft; + PNTFS_BITMAP _VolumeBitmap; + + HMEM _MirrorMem; + NTFS_CLUSTER_RUN _MirrorClusterRun; + +}; + + + + +INLINE +PNTFS_MASTER_FILE_TABLE +NTFS_MFT_FILE::GetMasterFileTable( + ) +/*++ + +Routine Description: + + This routine returns a pointer to master file table. + +Arguments: + + None. + +Return Value: + + A pointer to the master file table. + +--*/ +{ + return _Mft.AreMethodsEnabled() ? &_Mft : NULL; +} + diff --git a/untfs/inc/mftinfo.hxx b/untfs/inc/mftinfo.hxx new file mode 100644 index 0000000..4211d3b --- /dev/null +++ b/untfs/inc/mftinfo.hxx @@ -0,0 +1,79 @@ +/*++ + +Module Name: + + mftinfo.hxx + +Abstract: + + This module contains the declarations for the NTFS_MFT_INFO + class, which stores extracted information from the NTFS MFT. + +--*/ + +#pragma once + +#include "membmgr2.hxx" + +DECLARE_CLASS( NTFS_UPCASE_TABLE ); + +typedef UCHAR FILE_NAME_SIGNATURE[4]; +typedef UCHAR DUP_INFO_SIGNATURE[4]; + +DEFINE_POINTER_AND_REFERENCE_TYPES( FILE_NAME_SIGNATURE ); +DEFINE_POINTER_AND_REFERENCE_TYPES( DUP_INFO_SIGNATURE ); + +struct _NTFS_FILE_NAME_INFO { + FILE_NAME_SIGNATURE Signature; + UCHAR Flags; + UCHAR Reserved[3]; +}; + +struct _NTFS_FRS_INFO { + MFT_SEGMENT_REFERENCE SegmentReference; + DUP_INFO_SIGNATURE DupInfoSignature; + USHORT NumberOfFileNames; + USHORT Reserved; + struct _NTFS_FILE_NAME_INFO FileNameInfo[1]; +}; + +DEFINE_TYPE( _NTFS_FRS_INFO, NTFS_FRS_INFO ); + +class NTFS_MFT_INFO : public OBJECT { + + public: + + DECLARE_CONSTRUCTOR( NTFS_MFT_INFO ); + + VIRTUAL + ~NTFS_MFT_INFO( + ); + + + private: + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + + + VCN _min_file_number; + VCN _max_file_number; + + PVOID *_mft_info; + STATIC PNTFS_UPCASE_TABLE _upcase_table; + STATIC UCHAR _major; + STATIC UCHAR _minor; + MEM_ALLOCATOR _mem_mgr; + ULONG64 _max_mem_use; + ULONG _num_of_files; +}; + + diff --git a/untfs/inc/mftref.hxx b/untfs/inc/mftref.hxx new file mode 100644 index 0000000..933e38d --- /dev/null +++ b/untfs/inc/mftref.hxx @@ -0,0 +1,55 @@ +/*++ + +Module Name: + + mftref.hxx + +Abstract: + + This module contains the declarations for the + NTFS_REFLECTED_MASTER_FILE_TABLE class. This + class models the backup copy of the Master File + Table. + +--*/ + +#pragma once + +#include "frs.hxx" + +DECLARE_CLASS( NTFS_MASTER_FILE_TABLE ); + +class NTFS_REFLECTED_MASTER_FILE_TABLE : public NTFS_FILE_RECORD_SEGMENT { + + public: + + + DECLARE_CONSTRUCTOR( NTFS_REFLECTED_MASTER_FILE_TABLE ); + + VIRTUAL + + ~NTFS_REFLECTED_MASTER_FILE_TABLE( + ); + + + + BOOLEAN + Initialize( + IN OUT PNTFS_MASTER_FILE_TABLE Mft + ); + + + private: + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + +}; + diff --git a/untfs/inc/ntfsbit.hxx b/untfs/inc/ntfsbit.hxx new file mode 100644 index 0000000..10ac3b3 --- /dev/null +++ b/untfs/inc/ntfsbit.hxx @@ -0,0 +1,571 @@ +/*++ + +Module Name: + + ntfsbit.hxx + +Abstract: + + This module contains the declarations for NTFS_BITMAP, + which models the bitmaps on an NTFS volume. There are three + kinds of bitmaps on an NTFS volume: + + (1) The Volume Bitmap, which is stored as the nameless $DATA + attribute of the bitmap file. This bitmap has one bit for + each cluster on the disk, and is of fixed size. It is unique. + + (2) The MFT Bitmap, which is stored as the value of the nameless + $BITMAP attribute of the Master File Table File. It has one + bit per File Record Segment in the Master File Table, and grows + with the Master File Table. It is unique. + + (3) Index Allocation bitmaps. An index allocation bitmap is stored + as the value of a $BITMAP attribute in a File Record Segment + that has an $INDEX_ALLOCATION attribute. The name of the $BITMAP + attribute agrees with the associated $INDEX_ALLOCATION attribute. + This bitmap has one bit for each index allocation buffer in the + associated $INDEX_ALLOCATION attribute, and grows with that + attribute. + + In every case, a set bit indicates that the associated allocation + unit (cluster, File Record Segment, or Index Allocation Buffer) is + allocated; a reset bit indicates that it is free. + + The size of a bitmap (in bytes) is always quad-word aligned; bytes + are added at the end of the bitmap to pad it to the correct size. + If the bitmap is of fixed size, then all bits in the padding are + set (allocated); if it is growable, the bits of the padding are + reset (free). + +--*/ + +#pragma once + +#include "bitvect.hxx" +#include "attrib.hxx" + +DECLARE_CLASS( NTFS_ATTRIBUTE ); +DECLARE_CLASS( NTFS_BITMAP ); +DECLARE_CLASS( NTFS_MASTER_FILE_TABLE ); + +class NTFS_BITMAP : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( NTFS_BITMAP ); + + VIRTUAL + + ~NTFS_BITMAP( + ); + + + + BOOLEAN + Initialize( + IN BIG_INT NumberOfClusters, + IN BOOLEAN IsGrowable, + IN PLOG_IO_DP_DRIVE Drive DEFAULT NULL, + IN ULONG ClusterFactor DEFAULT 0 + ); + + + BOOLEAN + Create( + ); + + + BIG_INT + QuerySize( + ) CONST; + + + VOID + SetFree( + IN LCN Lcn, + IN BIG_INT NumberOfClusters + ); + + + VOID + SetAllocated( + IN LCN Lcn, + IN BIG_INT NumberOfClusters + ); + + + VOID + SetNextAlloc( + IN LCN Lcn + ); + + + BIG_INT + QueryFreeClusters( + ) CONST; + + + + BOOLEAN + IsFree( + IN LCN Lcn, + IN BIG_INT RunLength + ) CONST; + + + + BOOLEAN + IsInRange( + IN LCN Lcn, + IN BIG_INT RunLength + ) CONST; + + + BOOLEAN + AllocateClusters( + IN LCN NearHere OPTIONAL, + IN BIG_INT NumberOfClusters, + OUT PLCN FirstAllocatedLcn, + IN ULONG AlignmentFactor DEFAULT 1 + ); + + + BOOLEAN + Read( + IN OUT PNTFS_ATTRIBUTE BitmapAttribute + ); + + + + BOOLEAN + Write( + IN OUT PNTFS_ATTRIBUTE BitmapAttribute, + IN OUT PNTFS_BITMAP VolumeBitmap OPTIONAL + ); + + + BOOLEAN + CheckAttributeSize( + IN OUT PNTFS_ATTRIBUTE BitmapAttribute, + IN OUT PNTFS_BITMAP VolumeBitmap + ); + + + BOOLEAN + Resize( + IN BIG_INT NumberOfClusters + ); + + + PCVOID + GetBitmapData( + OUT PULONG SizeInBytes + ) CONST; + + + VOID + SetMftPointer( + IN PNTFS_MASTER_FILE_TABLE Mft + ); + + + private: + + + VOID + Construct ( + ); + + + VOID + Destroy( + ); + + BIG_INT _NumberOfClusters; + BOOLEAN _IsGrowable; + + ULONG _BitmapSize; + PVOID _BitmapData; + BIG_INT _NextAlloc; + BITVECTOR _Bitmap; + + // + // This is to support testing newly-allocated clusters in the + // volume bitmap to ensure they can support IO. + // + + PLOG_IO_DP_DRIVE _Drive; + ULONG _ClusterFactor; + PNTFS_MASTER_FILE_TABLE _Mft; +}; + + +INLINE +BIG_INT +NTFS_BITMAP::QuerySize( + ) CONST +/*++ + +Routine Description: + + This method returns the number of clusters covered by this bitmap. + +Arguments: + + None. + +Return Value: + + The number of clusters covered by this bitmap. + +--*/ +{ + return _NumberOfClusters; +} + + + +INLINE +VOID +NTFS_BITMAP::SetFree( + IN LCN Lcn, + IN BIG_INT RunLength + ) +/*++ + +Routine Description: + + This method marks a run of clusters as free in the bitmap. + +Arguments: + + Lcn -- supplies the LCN of the first cluster in the run + RunLength -- supplies the length of the run + +Return Value: + + None. + +Notes: + + This method performs range checking. + +--*/ +{ + // Since the high part of _NumberOfClusters is zero, if + // Lcn and RunLength pass the range-checking, their high + // parts must also be zero. + + if( !(Lcn < 0) && + !(RunLength < 0 ) && + !(Lcn + RunLength > _NumberOfClusters) ) { + + _Bitmap.ResetBit( Lcn.GetLowPart(), RunLength.GetLowPart() ) ; + } +} + + + +INLINE +VOID +NTFS_BITMAP::SetAllocated( + IN LCN Lcn, + IN BIG_INT RunLength + ) +/*++ + +Routine Description: + + This method marks a run of clusters as used in the bitmap. + +Arguments: + + Lcn -- supplies the LCN of the first cluster in the run + RunLength -- supplies the length of the run + +Return Value: + + None. + +Notes: + + This method performs range checking. + +--*/ +{ + // Since the high part of _NumberOfClusters is zero, if + // Lcn and RunLength pass the range-checking, their high + // parts must also be zero. + + if( !(Lcn < 0) && + !(Lcn + RunLength > _NumberOfClusters) ) + { + + _Bitmap.SetBit( Lcn.GetLowPart(), RunLength.GetLowPart() ); + } +} + + +INLINE +VOID +NTFS_BITMAP::SetNextAlloc( + IN LCN Lcn + ) +/*++ + +Routine Description: + + This method sets the next allocation location from the bitmap. + +Arguments: + + Lcn -- supplies the LCN of the first cluster to allocate + +Return Value: + + None. + +Notes: + + This method performs range checking. + +--*/ +{ + if( !(Lcn < 0) && + !(Lcn > _NumberOfClusters) ) { + + _NextAlloc = Lcn; + } +} + + +INLINE +BIG_INT +NTFS_BITMAP::QueryFreeClusters( + ) CONST +/*++ + +Routine Description: + + This method returns the number of free clusters in the bitmap. + +Arguments: + + None. + +Return Value: + + The number of free clusters in the bitmap. + +--*/ +{ + BIG_INT result; + + if( _IsGrowable ) { + + // The bits in the padding are marked as free (ie. are + // not set). Thus, we can just count the number of set + // bits and subtract that from the number of clusters. + // + result = _NumberOfClusters - ((PNTFS_BITMAP) this)->_Bitmap.QueryCountSet(); + + } else { + + // The bits in the padding are marked as in-use (ie. + // are set), so we need to compensate for them when + // we count the number of bits that are set. + // + result = _BitmapSize*8 - ((PNTFS_BITMAP) this)->_Bitmap.QueryCountSet(); + } + + return result; +} + + +INLINE +BOOLEAN +NTFS_BITMAP::Create( + ) +/*++ + +Routine Description: + + This method sets up an NTFS_BITMAP object in memory. Note that + it does not write the bitmap to disk. + +Arguments: + + None. + +Return Value: + + TRUE upon successful completion. + +Notes: + + The newly-created bitmap begins with all clusters marked as FREE. + +--*/ +{ + SetFree( 0, _NumberOfClusters ); + return TRUE; +} + + +INLINE +BOOLEAN +NTFS_BITMAP::IsInRange( + IN LCN Lcn, + IN BIG_INT RunLength + ) CONST +/*++ + +Routine Description: + + This routine computes whether or not the given range of clusters is + in the range allowed by this bitmap. + +Arguments: + + Lcn - Supplies the first cluster in the range. + RunLength - Supplies the number of clusters in the range. + +Return Value: + + FALSE - The specified range of clusters is not within the range of + the bitmap. + TRUE - The specified range of clusters is within the range of + the bitmap. + +--*/ +{ + return (BOOLEAN) ((Lcn >= 0) && + (RunLength >= 0 ) && + (Lcn + RunLength <= _NumberOfClusters)); +} + + + +INLINE +BOOLEAN +NTFS_BITMAP::Read( + PNTFS_ATTRIBUTE BitmapAttribute + ) +/*++ + +Routine Description: + + This method reads the bitmap. + +Arguments: + + BitmapAttribute -- supplies the attribute which describes the + bitmap's location on disk. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + ULONG BytesRead; + + DebugPtrAssert( _BitmapData ); + + return( BitmapAttribute->Read( _BitmapData, + 0, + _BitmapSize, + &BytesRead ) && + + BytesRead == _BitmapSize ); +} + + + +INLINE +BOOLEAN +NTFS_BITMAP::CheckAttributeSize( + IN OUT PNTFS_ATTRIBUTE BitmapAttribute, + IN OUT PNTFS_BITMAP VolumeBitmap + ) +/*++ + +Routine Description: + + This method ensures that the allocated size of the attribute is + big enough to hold the bitmap. + +Arguments: + + BitmapAttribute -- Supplies the attribute which describes the + bitmap's location on disk. + VolumeBitmap -- Supplies the volume bitmap. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + DebugAssert( BitmapAttribute != NULL ); + + return( BitmapAttribute->QueryValueLength() == _BitmapSize || + BitmapAttribute->Resize( _BitmapSize, VolumeBitmap ) ); +} + + + +INLINE +PCVOID +NTFS_BITMAP::GetBitmapData( + OUT PULONG SizeInBytes + ) CONST +/*++ + +Routine Description: + + This routine returns a pointer to the in memory bitmap contained by + this class. It also return the size in bytes of the bitmap. + +Arguments: + + SizeInBytes - Returns the size of the bitmap. + +Return Value: + + The bitmap contained by this class. + +--*/ +{ + *SizeInBytes = _BitmapSize; + return _BitmapData; +} + +INLINE +VOID +NTFS_BITMAP::SetMftPointer( + IN PNTFS_MASTER_FILE_TABLE Mft + ) +/*++ + +Routine Description: + + This routine set the _Mft member in the NTFS_BITMAP so that + subsequent calls to AllocateClusters will be able to add any + bad clusters found to the bad cluster file. + +Arguments: + + Mft - pointer to the volume's mft. + +Return Value: + + None. + +--*/ +{ + _Mft = Mft; +} + + diff --git a/untfs/inc/ntfssa.hxx b/untfs/inc/ntfssa.hxx new file mode 100644 index 0000000..18432a5 --- /dev/null +++ b/untfs/inc/ntfssa.hxx @@ -0,0 +1,396 @@ +/*++ + +Module Name: + + ntfssa.hxx + +Abstract: + + This class supplies the NTFS-only SUPERAREA methods. + +--*/ + +#pragma once + +#include "supera.hxx" +#include "hmem.hxx" +#include "untfs.hxx" +#include "message.hxx" +#include "ntfsbit.hxx" +#include "numset.hxx" + +DECLARE_CLASS( NTFS_INDEX_TREE ); + +#include "indxtree.hxx" + + +DECLARE_CLASS( LOG_IO_DP_DRIVE ); +DECLARE_CLASS( WSTRING ); +DECLARE_CLASS( NTFS_MASTER_FILE_TABLE ); +DECLARE_CLASS( NTFS_BITMAP ); +DECLARE_CLASS( NTFS_FILE_RECORD_SEGMENT ); +DECLARE_CLASS( NTFS_ATTRIBUTE ); +DECLARE_CLASS( NTFS_FRS_STRUCTURE ); +DECLARE_CLASS( NTFS_ATTRIBUTE_LIST ); +DECLARE_CLASS( CONTAINER ); +DECLARE_CLASS( SEQUENTIAL_CONTAINER ); +DECLARE_CLASS( LIST ); +DECLARE_CLASS( NTFS_EXTENT_LIST ); +DECLARE_CLASS( NUMBER_SET ); +DECLARE_CLASS( NTFS_UPCASE_TABLE ); +DECLARE_CLASS( NTFS_MFT_FILE ); + +// +// Types of message for SynchronizeMft() +// +enum MessageMode { + CorrectMessage = 0, + SuppressMessage, + UpdateMessage +}; + +// This global variable used by CHKDSK to compute the largest +// LSN on the volume. + +extern LSN LargestLsnEncountered; +extern LARGE_INTEGER LargestUsnEncountered; +extern ULONG64 FrsOfLargestUsnEncountered; + +CONST ULONG LsnResetThreshholdHighPart = 0x10000; + +CONST UCHAR UpdateSequenceArrayCheckValueMinorError = 2;// should always be non-zero +CONST UCHAR UpdateSequenceArrayCheckValueOk = 1; // should always be non-zero + + +class NTFS_SA : public SUPERAREA { + +public: + + BOOLEAN + MarkInFreeSpace( + IN OUT PNTFS_MASTER_FILE_TABLE Mft, + IN __int64 firstPhysicalDriveSector, + IN __int64 lastPhysicalDriveSector, + IN OUT PNUMBER_SET BadClusters, + IN PNTFS_BAD_CLUSTER_FILE BadClusterFile, + IN OUT PMESSAGE Message + ); + + VIRTUAL + BOOLEAN + MarkBad( + IN __int64 ClusterId, + IN __int64 ClusterCount, + IN OUT PMESSAGE Message + ); + + + + DECLARE_CONSTRUCTOR(NTFS_SA); + + VIRTUAL + ~NTFS_SA( + ); + + + + BOOLEAN + Initialize( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN OUT PMESSAGE Message, + IN LCN CvtStartZone DEFAULT 0, + IN BIG_INT CvtZoneSize DEFAULT 0 + ); + + VIRTUAL + PVOID + GetBuf( + ); + + + BOOLEAN + Read( + ); + + + BOOLEAN + Read( + IN OUT PMESSAGE Message + ); + + VIRTUAL + BOOLEAN + Write( + ); + + VIRTUAL + BOOLEAN + Write( + IN OUT PMESSAGE Message + ); + + + BIG_INT + QueryVolumeSectors( + ) CONST; + + + + + UCHAR + QueryClusterFactor( + ) CONST; + + + LCN + QueryMftStartingLcn( + ) CONST; + + + LCN + QueryMft2StartingLcn( + ) CONST; + + + + + ULONG + QueryFrsSize( + ) CONST; + + + + + USHORT + QueryVolumeFlags( + OUT PBOOLEAN CorruptVolume DEFAULT NULL, + OUT PUCHAR MajorVersion DEFAULT NULL, + OUT PUCHAR MinorVersion DEFAULT NULL + ); + + + STATIC + UCHAR + PostReadMultiSectorFixup( + IN OUT PVOID MultiSectorBuffer, + IN ULONG BufferSize, + IN OUT PIO_DP_DRIVE Drive, + IN ULONG VaildSize DEFAULT MAXULONG + ); + + STATIC + VOID + PreWriteMultiSectorFixup( + IN OUT PVOID MultiSectorBuffer, + IN ULONG BufferSize + ); + + + + STATIC + VOID + SetVersionNumber( + IN UCHAR Major, + IN UCHAR Minor + ); + + STATIC + VOID + QueryVersionNumber( + OUT PUCHAR Major, + OUT PUCHAR Minor + ); + + +private: + + + HMEM _hmem; // memory for SECRUN + PPACKED_BOOT_SECTOR _boot_sector; // packed boot sector + BIOS_PARAMETER_BLOCK _bpb; // unpacked BPB + BIG_INT _boot2; // alternate boot sector + BIG_INT _boot3; // second alternate boot sector + UCHAR _NumberOfStages;// minimum number of stages for chkdsk + // to go thru + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + BOOLEAN _cleanup_that_requires_reboot; + LCN _cvt_zone; // convert region for mft, logfile, etc. + BIG_INT _cvt_zone_size; // convert region size in terms of clusters + + // This version number is used to determine what format + // is used for the compressed mapping pairs of sparse + // files. Ideally, this information should be tracked + // on a per-volume basis; however, that would require + // extensive changes to the UNTFS class interfaces. + // + STATIC UCHAR _MajorVersion, _MinorVersion; +}; + +INLINE +PVOID +NTFS_SA::GetBuf( + ) +/*++ + +Routine Description: + + This routine returns a pointer to the write buffer for the NTFS + SUPERAREA. This routine also packs the bios parameter block. + +Arguments: + + None. + +Return Value: + + A pointer to the write buffer. + +--*/ +{ + PackBios(&_bpb, &(_boot_sector->PackedBpb)); + return SECRUN::GetBuf(); +} + +INLINE +BOOLEAN +NTFS_SA::Write( + ) +/*++ + +Routine Description: + + This routine simply calls the other write with the default message + object. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + MESSAGE msg; + + return Write(&msg); +} + + +INLINE +BIG_INT +NTFS_SA::QueryVolumeSectors( + ) CONST +/*++ + +Routine Description: + + This routine returns the number of sectors on the volume as recorded in + the boot sector. + +Arguments: + + None. + +Return Value: + + The number of volume sectors. + +--*/ +{ + return _boot_sector->NumberSectors; +} + + +INLINE +LCN +NTFS_SA::QueryMftStartingLcn( + ) CONST +/*++ + +Routine Description: + + This routine returns the starting logical cluster number + for the Master File Table. + +Arguments: + + None. + +Return Value: + + The starting LCN for the MFT. + +--*/ +{ + return _boot_sector->MftStartLcn; +} + + +INLINE +LCN +NTFS_SA::QueryMft2StartingLcn( + ) CONST +/*++ + +Routine Description: + + This routine returns the starting logical cluster number + for the mirror of the Master File Table. + +Arguments: + + None. + +Return Value: + + The starting LCN for the mirror of the MFT. + +--*/ +{ + return _boot_sector->Mft2StartLcn; +} + + +INLINE +ULONG +NTFS_SA::QueryFrsSize( + ) CONST +/*++ + +Routine Description: + + This routine computes the number of clusters per file record segment. + +Arguments: + + None. + +Return Value: + + The number of clusters per file record segment. + +--*/ +{ + if (_boot_sector->ClustersPerFileRecordSegment < 0) { + + LONG temp = LONG(_boot_sector->ClustersPerFileRecordSegment); + + return 1 << -temp; + } + + return _boot_sector->ClustersPerFileRecordSegment * + _bpb.SectorsPerCluster * _drive->QuerySectorSize(); +} + diff --git a/untfs/inc/ntfsvol.hxx b/untfs/inc/ntfsvol.hxx new file mode 100644 index 0000000..e1e16f9 --- /dev/null +++ b/untfs/inc/ntfsvol.hxx @@ -0,0 +1,57 @@ +/*++ + +Module Name: + + ntfsvol.hxx + +Abstract: + + This class implements NTFS only VOLUME items. + +--*/ + +#pragma once + +#include "volume.hxx" +#include "ntfssa.hxx" + +DECLARE_CLASS( NTFS_SA ); +DECLARE_CLASS( NTFS_VOL ); +DECLARE_CLASS( MESSAGE ); + +class NTFS_VOL : public VOL_LIODPDRV +{ + +public: + + DECLARE_CONSTRUCTOR(NTFS_VOL); + + VIRTUAL + ~NTFS_VOL( + ); + + + FORMAT_ERROR_CODE + Initialize( + IN PCWSTRING NtDriveName, + IN OUT PMESSAGE Message DEFAULT NULL + ); + + +private: + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + NTFS_SA _ntfssa; + +}; + + diff --git a/untfs/inc/untfs.hxx b/untfs/inc/untfs.hxx new file mode 100644 index 0000000..a9198be --- /dev/null +++ b/untfs/inc/untfs.hxx @@ -0,0 +1,1353 @@ +/*++ + +Module Name: + + untfs.h + +Abstract: + + This module contains basic declarations and definitions for + the NTFS utilities. Note that more extensive description + of the file system structures may be found in ntos\inc\ntfs.h. + +IMPORTANT NOTE: + + The NTFS on-disk structure must guarantee natural alignment of all + arithmetic quantities on disk up to and including quad-word (64-bit) + numbers. Therefore, all attribute records are quad-word aligned, etc. + +--*/ + +#pragma once + +#define FRIEND friend +#define MIN( a, b ) ( ((a) < (b)) ? (a) : (b) ) + + + +#include "bigint.hxx" +#include "bpb.hxx" +#include "wstring.hxx" + +#include "untfs2.hxx" + +#include + +// Miscellaneous constants: + +// This value represents the number of clusters from the Master +// File Table which are copied to the Master File Table Reflection. + +#define REFLECTED_MFT_SEGMENTS (4) +#define BYTES_IN_BOOT_AREA (0x2000) + +// This value is used in a mapping-pair to indicate that the run +// described by the mapping pair doesn't really exist. This allows +// NTFS to support sparse files. Note that the actual values +// are LARGE_INTEGERS; the BIG_INT class manages the conversion. + +#define LCN_NOT_PRESENT -1 +#define INVALID_VCN -1 + +// +// Temporary definitions **** +// + +typedef ULONG COLLATION_RULE; +typedef ULONG DISPLAY_RULE; + +// This defines the number of bytes to read at one time +// when processing the MFT. + +#define MFT_PRIME_SIZE (16*1024) + +// This defines the number of frs'es to read at one time +// when processing the MFT. + +#define MFT_READ_CHUNK_SIZE (128) + +// The compression chunk size is constant for now, at 4KB. +// + +#define NTFS_CHUNK_SIZE (0x1000) + +// +// This number is actually the log of the number of clusters per compression +// unit to be stored in a nonresident attribute record header. +// + +#define NTFS_CLUSTERS_PER_COMPRESSION (4) + +// +// Collation Rules +// + +// +// For binary collation, values are collated by a binary compare of their +// bytes, with the first byte being most significant. +// + +#define COLLATION_BINARY (0) + +// +// For collation of Ntfs file names, file names are collated as Unicode +// strings. See below. +// + +#define COLLATION_FILE_NAME (1) + +// +// For collation of Unicode strings, the strings are collated by their +// binary Unicode value, with the exception that for characters which may +// be upcased, the lower case value for that character collates immediately +// after the upcased value. +// + +#define COLLATION_UNICODE_STRING (2) + +#define COLLATION_ULONG (16) +#define COLLATION_SID (17) +#define COLLATION_SECURITY_HASH (18) +#define COLLATION_ULONGS (19) + +// +// Total number of collation rules +// + +#define COLLATION_NUMBER_RULES (7) + + +INLINE +BOOLEAN +operator == ( + IN RCMFT_SEGMENT_REFERENCE Left, + IN RCMFT_SEGMENT_REFERENCE Right + ) +/*++ + +Routine Description: + + This function tests two segment references for equality. + +Arguments: + + Left -- supplies the left-hand operand + Right -- supplies the right-hand operand + +Return Value: + + TRUE if they are equal; FALSE if not. + +--*/ +{ + return( Left.HighPart == Right.HighPart && + Left.LowPart == Right.LowPart && + Left.SequenceNumber == Right.SequenceNumber ); +} + +// +// If the ClustersPerFileRecordSegment entry is zero, we use the +// following as the size of our frs's regardless of the cluster +// size. +// + +#define SMALL_FRS_SIZE (1024) + +// +// If the DefaultClustersPerIndexAllocationBuffer entry is zero, +// we use the following as the size of our buffers regardless of +// the cluster size. +// + +#define SMALL_INDEX_BUFFER_SIZE (4096) + +// +// If the above NextAttributeInstance field exceeds the following +// value, chkdsk will take steps to prevent it from rolling over. +// + +#define ATTRIBUTE_INSTANCE_TAG_THRESHOLD 0xf000 + + +// +// Define a macro to determine the maximum space available for a +// single attribute. For example, this is required when a +// nonresident attribute has to split into multiple file records - +// we need to know how much we can squeeze into a single file +// record. If this macro has any inaccurracy, it must be in the +// direction of returning a slightly smaller number than actually +// required. +// +// ULONG +// NtfsMaximumAttributeSize ( +// IN ULONG FileRecordSegmentSize +// ); +// + +#define NtfsMaximumAttributeSize(FRSS) ( \ + (FRSS) - QuadAlign(sizeof(FILE_RECORD_SEGMENT_HEADER)) - \ + QuadAlign((((FRSS) / SEQUENCE_NUMBER_STRIDE) * sizeof(UPDATE_SEQUENCE_NUMBER))) - \ + QuadAlign(sizeof(ATTRIBUTE_TYPE_CODE)) \ +) + +// +// Macros for manipulating mapping pair count byte. +// + +INLINE +UCHAR +ComputeMappingPairCountByte( + IN UCHAR VcnLength, + IN UCHAR LcnLength + ) +{ + return VcnLength + 16*LcnLength; +} + +INLINE +UCHAR +VcnBytesFromCountByte( + IN UCHAR CountByte + ) +{ + return CountByte%16; +} + +INLINE +UCHAR +LcnBytesFromCountByte( + IN UCHAR CountByte + ) +{ + return CountByte/16; +} + + +// +// Standard Information Attribute. This attribute is present in every +// base file record, and must be resident. +// + +typedef struct _STANDARD_INFORMATION { + + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastModificationTime; // refers to $DATA attribute + LARGE_INTEGER LastChangeTime; // any attribute + LARGE_INTEGER LastAccessTime; + ULONG FileAttributes; + ULONG MaximumVersions; + ULONG VersionNumber; + ULONG Reserved; + +}; + +DEFINE_TYPE( _STANDARD_INFORMATION, STANDARD_INFORMATION ); + +typedef struct _STANDARD_INFORMATION2 { + + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastModificationTime; // refers to $DATA attribute + LARGE_INTEGER LastChangeTime; // any attribute + LARGE_INTEGER LastAccessTime; + ULONG FileAttributes; + ULONG MaximumVersions; + ULONG VersionNumber; + ULONG ClassId; + ULONG OwnerId; + ULONG SecurityId; + LARGE_INTEGER QuotaCharged; + LARGE_INTEGER Usn; + +}; + +DEFINE_TYPE( _STANDARD_INFORMATION2, STANDARD_INFORMATION2 ); + +#define SIZEOF_NEW_STANDARD_INFORMATION (0x48) + +// +// Define the file attributes, starting with the Fat attributes. +// + +#define FAT_DIRENT_ATTR_READ_ONLY (0x01) +#define FAT_DIRENT_ATTR_HIDDEN (0x02) +#define FAT_DIRENT_ATTR_SYSTEM (0x04) +#define FAT_DIRENT_ATTR_VOLUME_ID (0x08) +#define FAT_DIRENT_ATTR_ARCHIVE (0x20) +#define FAT_DIRENT_ATTR_DEVICE (0x40) + +// +// The FILE_ATTRIBUTE_ENCRYPTED bit coincides with the FILE_ATTRIBUTE_DEVICE on Win9x. +// So we are changing it to a different value. In the meantime, chkdsk should clear +// this bit whenever it sees it on ntfs volume. +// + +#define FILE_ATTRIBUTE_ENCRYPTED_OLD (0x40) + +// +// This bit is duplicated from the file record, to indicate that +// this file has a file name index present (is a "directory"). +// + +#define DUP_FILE_NAME_INDEX_PRESENT (0x10000000) + +#define DUP_VIEW_INDEX_PRESENT (0x20000000) + +// +// Attribute List. Because there is not a special header that goes +// before the list of attribute list entries we do not need to declare +// an attribute list header +// + +// +// The Attributes List attribute is an ordered-list of quad-word +// aligned ATTRIBUTE_LIST_ENTRY records. It is ordered first by +// Attribute Type Code, and then by Attribute Name (if present). No two +// attributes may exist with the same type code, name and LowestVcn. This +// also means that at most one occurrence of a given Attribute Type Code +// without a name may exist. +// +// To binary search this attribute, it is first necessary to make a quick +// pass through it and form a list of pointers, since the optional name +// makes it variable-length. +// + +typedef struct _ATTRIBUTE_LIST_ENTRY { + + ATTRIBUTE_TYPE_CODE AttributeTypeCode; + USHORT RecordLength; // length in bytes + UCHAR AttributeNameLength; // length in characters + UCHAR AttributeNameOffset; // offset from beginning of struct + VCN LowestVcn; + MFT_SEGMENT_REFERENCE SegmentReference; + USHORT Instance; // FRS-unique instance tag + WCHAR AttributeName[1]; + +}; + +DEFINE_TYPE( _ATTRIBUTE_LIST_ENTRY, ATTRIBUTE_LIST_ENTRY ); + +BOOLEAN +operator <= ( + IN RCATTRIBUTE_LIST_ENTRY Left, + IN RCATTRIBUTE_LIST_ENTRY Right + ); + + +typedef struct _DUPLICATED_INFORMATION { + + BIG_INT CreationTime; // File creation + BIG_INT LastModificationTime; // Last change of $DATA attribute + BIG_INT LastChangeTime; // Last change of any attribute + BIG_INT LastAccessTime; // see ntfs.h for notes + BIG_INT AllocatedLength; // File allocated size + BIG_INT FileSize; // File actual size + ULONG FileAttributes; + union { + struct { + USHORT PackedEaSize; + USHORT Reserved; + }; + ULONG ReparsePointTag; + }; +}; + +DEFINE_TYPE( _DUPLICATED_INFORMATION, DUPLICATED_INFORMATION ); + +// +// File Name attribute. A file has one File Name attribute for every +// directory it is entered into (hard links). +// + +typedef struct _FILE_NAME { + + FILE_REFERENCE ParentDirectory; + DUPLICATED_INFORMATION Info; + UCHAR FileNameLength; // length in characters + UCHAR Flags; // FILE_NAME_xxx flags + WCHAR FileName[1]; // First character of file name + +}; + +DEFINE_TYPE( _FILE_NAME, FILE_NAME ); + +// NtfsFileNameGetLength evaluates to the length of a FILE_NAME attribute +// value, which is the FILE_NAME structure plus the length of the name. +// Note that this is the actual length, not the quad-aligned length. + +#define NtfsFileNameGetLength(p) ( FIELD_OFFSET( FILE_NAME, FileName ) \ + + ((p)->FileNameLength * sizeof( WCHAR )) ) + +// NtfsFileNameGetName gets the pointer to the name in a FILE_NAME +// structure, which in turn is the value of an NTFS $FILE_NAME attribute. + +#define NtfsFileNameGetName(p) ( &((p)->FileName[0]) ) + + +// +// File Name flags +// + +#define FILE_NAME_NTFS (0x01) +#define FILE_NAME_DOS (0x02) + +// +// The maximum file name length is 255 (in chars) +// + +#define NTFS_MAX_FILE_NAME_LENGTH (255) + + +// +// The maximum number of links on a file is 1024 +// + +#define NTFS_MAX_LINK_COUNT (1024) + + +// This is the name of all attributes associated with an index +// over $FILE_NAME: + +#define FileNameIndexNameData "$I30" + +// +// Object ID attribute. +// + +typedef struct _OBJECT_ID { + char x[16]; +}; + +DEFINE_TYPE( _OBJECT_ID, OBJECT_ID ); + +typedef struct _OBJID_INDEX_ENTRY_VALUE { + OBJECT_ID key; + MFT_SEGMENT_REFERENCE SegmentReference; + char extraInfo[48]; +}; + +DEFINE_TYPE( _OBJID_INDEX_ENTRY_VALUE, OBJID_INDEX_ENTRY_VALUE ); + +// +// Object Id File Name to appear in the \$Extend directory +// + +#define ObjectIdFileName "$ObjId" +#define LObjectIdFileName L"$ObjId" + +#define ObjectIdIndexNameData "$O" + +// +// Reparse Point Index Entry +// +typedef struct _REPARSE_INDEX_ENTRY_VALUE { + ULONG Tag; +// ULONG Reserved; + MFT_SEGMENT_REFERENCE SegmentReference; +}; + +DEFINE_TYPE( _REPARSE_INDEX_ENTRY_VALUE, REPARSE_INDEX_ENTRY_VALUE ); + +// +// Reparse File Name to appear in the \$Extend directory +// + +#define ReparseFileName "$Reparse" +#define LReparseFileName L"$Reparse" + +#define ReparseIndexNameData "$R" + + +// +// Volume Version attribute. +// + +typedef struct _VOLUME_VERSION { + + ULONG CurrentVersion; + ULONG MaximumVersions; + +}; + +DEFINE_TYPE( _VOLUME_VERSION, VOLUME_VERSION ); + + +// +// Security Descriptor attribute. This is just a normal attribute stream +// containing a security descriptor as defined by NT security and is +// really treated pretty opaque by NTFS. +// +#define SecurityIdIndexNameData "$SII" +#define SecurityDescriptorHashIndexNameData "$SDH" +#define SecurityDescriptorStreamNameData "$SDS" + +// Security Descriptor Stream data is organized into chunks of 256K bytes +// and it contains a mirror copy of each security descriptor. When writing +// to a security descriptor at location X, another copy will be written at +// location (X+256K). When writing a security descriptor that will +// cross the 256K boundary, the pointer will be advanced by 256K to skip +// over the mirror portion. + +#define SecurityDescriptorsBlockSize (0x40000) // 256K +#define SecurityDescriptorMaxSize (0x20000) // 128K + + +// Volume Name attribute. This attribute is just a normal attribute stream +// containing the unicode characters that make up the volume label. It +// is an attribute of the Volume Dasd File. +// + + +// +// Common Index Header for Index Root and Index Allocation Buffers. +// This structure is used to locate the Index Entries and describe the free +// space in either of the two structures above. +// + +typedef struct _INDEX_HEADER { + + // + // Offset from the start of this structure to the first Index Entry. + // + + ULONG FirstIndexEntry; + + // + // Offset from the start of this structure to the first (quad-word aligned) + // free byte. + // + + ULONG FirstFreeByte; + + // + // Total number of bytes available, from the start of this structure. + // In the Index Root, this number must always be equal to FirstFreeByte, + // as the total attribute record will be grown and shrunk as required. + // + + ULONG BytesAvailable; + + // + // INDEX_xxx flags. + // + + UCHAR Flags; + + // + // Reserved to round up to quad word boundary. + // + + UCHAR Reserved[3]; + +} INDEX_HEADER; +typedef INDEX_HEADER *PINDEX_HEADER; + +// +// INDEX_xxx flags +// + +// +// This Index or Index Allocation buffer is an intermediate node, as opposed to +// a leaf in the Btree. All Index Entries will have a Vcn down pointer. +// + +#define INDEX_NODE (0x01) + + +// +// Index Root attribute. The index attribute consists of an index +// header record followed by one or more index entries. +// + +typedef struct _INDEX_ROOT { + + ATTRIBUTE_TYPE_CODE IndexedAttributeType; + COLLATION_RULE CollationRule; + ULONG BytesPerIndexBuffer; + UCHAR ClustersPerIndexBuffer; + UCHAR Reserved[3]; + + // + // Index Header to describe the Index Entries which follow + // + + INDEX_HEADER IndexHeader; + +} INDEX_ROOT; +typedef INDEX_ROOT *PINDEX_ROOT; + +// +// Index Allocation record is used for non-root clusters of the b-tree +// Each non root cluster is contained in the data part of the index +// allocation attribute. Each cluster starts with an index allocation list +// header and is followed by one or more index entries. +// + +typedef struct _INDEX_ALLOCATION_BUFFER { + + // + // Multi-Sector Header as defined by the Cache Manager. This structure + // will always contain the signature "INDX" and a description of the + // location and size of the Update Sequence Array. + // + + UNTFS_MULTI_SECTOR_HEADER MultiSectorHeader; + + // + // Log File Sequence Number of last logged update to this Index Allocation + // Buffer. + // + + LSN Lsn; + + VCN ThisVcn; + + + // + // Index Header to describe the Index Entries which follow + // + + INDEX_HEADER IndexHeader; + + // + // Update Sequence Array to protect multi-sector transfers of the + // Index Allocation Bucket. + // + + UPDATE_SEQUENCE_ARRAY UpdateSequenceArray; + +} INDEX_ALLOCATION_BUFFER; +typedef INDEX_ALLOCATION_BUFFER *PINDEX_ALLOCATION_BUFFER; + + +// +// Index Entry. This structure is common to both the resident index list +// attribute and the Index Allocation records +// + +typedef struct _INDEX_ENTRY { + + // + // Define a union to distinguish directory indices from view indices + // + + union { + + // + // Reference to file containing the attribute with this + // attribute value. + // + + FILE_REFERENCE FileReference; // offset = 0x000 + + // + // For views, describe the Data Offset and Length in bytes + // + + struct { + + USHORT DataOffset; // offset = 0x000 + USHORT DataLength; // offset = 0x001 + ULONG ReservedForZero; // offset = 0x002 + }; + }; + + // + // Length of this index entry, in bytes. + // + + USHORT Length; + + // + // Length of attribute value, in bytes. The attribute value immediately + // follows this record. + // + + USHORT AttributeLength; + + // + // INDEX_ENTRY_xxx Flags. + // + + USHORT Flags; + + // + // Reserved to round to quad-word boundary. + // + + USHORT Reserved; + + // + // If this Index Entry is an intermediate node in the tree, as determined + // by the INDEX_xxx flags, then a VCN is stored at the end of this + // entry at Length - sizeof(VCN). + // + +}; + +DEFINE_TYPE( _INDEX_ENTRY, INDEX_ENTRY ); + + +#define GetDownpointer( x ) \ + (*((PVCN)((PBYTE)(x) + (x)->Length - sizeof(VCN)))) + +#define GetIndexEntryValue( x ) \ + ((PBYTE)(x)+sizeof(INDEX_ENTRY)) + +#define GetNextEntry( x ) \ + ((PINDEX_ENTRY)( (PBYTE)(x)+(x)->Length )) + +CONST UCHAR NtfsIndexLeafEndEntrySize = QuadAlign( sizeof(INDEX_ENTRY) ); + +// +// INDEX_ENTRY_xxx flags +// + +// +// This entry is currently in the intermediate node form, i.e., it has a +// Vcn at the end. +// + +#define INDEX_ENTRY_NODE (0x0001) + +// +// This entry is the special END record for the Index or Index Allocation buffer. +// + +#define INDEX_ENTRY_END (0x0002) + + +// +// Define the struture of the quota data in the quota index. The key for +// the quota index is the 32 bit owner id. +// + +typedef struct _QUOTA_USER_DATA { + ULONG QuotaVersion; + ULONG QuotaFlags; + ULONGLONG QuotaUsed; + ULONGLONG QuotaChangeTime; + ULONGLONG QuotaThreshold; + ULONGLONG QuotaLimit; + ULONGLONG QuotaExceededTime; + SID QuotaSid; +} QUOTA_USER_DATA, *PQUOTA_USER_DATA; + +// +// Define the size of the quota user data structure without the quota SID. +// + +#define SIZEOF_QUOTA_USER_DATA FIELD_OFFSET(QUOTA_USER_DATA, QuotaSid) + +// +// Define the current version of the quote user data. +// + +#define QUOTA_USER_VERSION 1 + +// +// Define the quota flags. +// + +#define QUOTA_FLAG_DEFAULT_LIMITS (0x00000001) +#define QUOTA_FLAG_LIMIT_REACHED (0x00000002) +#define QUOTA_FLAG_ID_DELETED (0x00000004) +#define QUOTA_FLAG_USER_MASK (0x00000007) + +// +// The following flags are only stored in the quota defaults index entry. +// + +#define QUOTA_FLAG_TRACKING_ENABLED (0x00000010) +#define QUOTA_FLAG_ENFORCEMENT_ENABLED (0x00000020) +#define QUOTA_FLAG_TRACKING_REQUESTED (0x00000040) +#define QUOTA_FLAG_LOG_THRESHOLD (0x00000080) +#define QUOTA_FLAG_LOG_LIMIT (0x00000100) +#define QUOTA_FLAG_OUT_OF_DATE (0x00000200) +#define QUOTA_FLAG_CORRUPT (0x00000400) +#define QUOTA_FLAG_PENDING_DELETES (0x00000800) + +// +// Define the quota charge for resident streams. +// + +#define QUOTA_RESIDENT_STREAM (1024) + +// +// Define special quota owner ids. +// + +#define QUOTA_INVALID_ID 0x00000000 +#define QUOTA_DEFAULTS_ID 0x00000001 +#define QUOTA_FISRT_USER_ID 0x00000100 + +// +// Quota File Name to appear in the \$Extend directory +// + +#define QuotaFileName "$Quota" +#define LQuotaFileName L"$Quota" + +// +// Quota Index Names +// + +#define Sid2UseridQuotaNameData "$O" +#define Userid2SidQuotaNameData "$Q" + +// +// Usn Journal +// + +typedef struct { + + ULONG RecordLength; + USHORT MajorVersion; + USHORT MinorVersion; + + union { + + // + // Version 1.0 + // + struct { + ULONGLONG FileReferenceNumber; + ULONGLONG ParentFileReferenceNumber; + USN Usn; + LARGE_INTEGER TimeStamp; + ULONG Reason; + ULONG SecurityId; + ULONG FileAttributes; + USHORT FileNameLength; + WCHAR FileName[1]; + } u1; + + // + // Version 2.0 + // + struct { + ULONGLONG FileReferenceNumber; + ULONGLONG ParentFileReferenceNumber; + USN Usn; + LARGE_INTEGER TimeStamp; + ULONG Reason; + ULONG SourceInfo; // Additional info about source of change + ULONG SecurityId; + ULONG FileAttributes; + USHORT FileNameLength; + USHORT FileNameOffset; // Offset to file name + WCHAR FileName[1]; + } u2; + + } version; + +} USN_REC, *PUSN_REC; + +#define SIZE_OF_USN_REC_V1 0x38 +#define SIZE_OF_USN_REC_V2 0x3E + +#define USN_PAGE_SIZE (0x1000) // 4 K + +#define USN_MAX_SIZE (1024*1024*8) // 8 MBytes +#define USN_ALLOC_DELTA_MAX_SIZE (1024*1024) // 1 MBytes + +#define USN_JRNL_MAX_FILE_SIZE (0x20000000000) // 512 * 2^32 + +// +// Usn Journal Name to appear in the \$Extend directory +// + +#define UsnJournalFileName "$UsnJrnl" +#define LUsnJournalFileName L"$UsnJrnl" + +// +// Usn Journal named $DATA attribute Names +// + +#define UsnJournalNameData "$J" +#define UsnJournalMaxNameData "$Max" + +//#define MAXULONGLONG (0xffffffffffffffff) + + +// +// Key structure for Security Hash index +// + +typedef struct _SECURITY_HASH_KEY +{ + ULONG Hash; // Hash value for descriptor + ULONG SecurityId; // Security Id (guaranteed unique) +} SECURITY_HASH_KEY, *PSECURITY_HASH_KEY; + +// +// Key structure for Security Id index is simply the SECURITY_ID itself +// + +// +// Header for security descriptors in the security descriptor stream. This +// is the data format for all indexes and is part of SharedSecurity +// + +typedef struct _SECURITY_DESCRIPTOR_HEADER +{ + SECURITY_HASH_KEY HashKey; // Hash value for the descriptor + ULONGLONG Offset; // offset to beginning of header + ULONG Length; // Length in bytes +} SECURITY_DESCRIPTOR_HEADER, *PSECURITY_DESCRIPTOR_HEADER; + +typedef struct _SECURITY_ENTRY { + SECURITY_DESCRIPTOR_HEADER security_descriptor_header; + SECURITY_DESCRIPTOR security; +} SECURITY_ENTRY, *PSECURITY_ENTRY; + +#define GETSECURITYDESCRIPTORLENGTH(HEADER) \ + ((HEADER)->Length - sizeof( SECURITY_DESCRIPTOR_HEADER )) + +#define SetSecurityDescriptorLength(HEADER,LENGTH) \ + ((HEADER)->Length = (LENGTH) + sizeof( SECURITY_DESCRIPTOR_HEADER )) + +// +// Define standard values for well-known security IDs +// + +#define SECURITY_ID_INVALID (0x00000000) +#define SECURITY_ID_FIRST (0x00000100) + + +// +// MFT Bitmap attribute +// +// The MFT Bitmap is simply a normal attribute stream in which there is +// one bit to represent the allocation state of each File Record Segment +// in the MFT. Bit clear means free, and bit set means allocated. +// +// Whenever the MFT Data attribute is extended, the MFT Bitmap attribute +// must also be extended. If the bitmap is still in a file record segment +// for the MFT, then it must be extended and the new bits cleared. When +// the MFT Bitmap is in the Nonresident form, then the allocation should +// always be sufficient to store enough bits to describe the MFT, however +// ValidDataLength insures that newly allocated space to the MFT Bitmap +// has an initial value of all 0's. This means that if the MFT Bitmap is +// extended, the newly represented file record segments are automatically in +// the free state. +// +// No structure definition is required; the positional offset of the file +// record segment is exactly equal to the bit offset of its corresponding +// bit in the Bitmap. +// + +// +// The utilities attempt to allocate a more disk space than necessary for +// the initial MFT Bitmap to allow for future growth. +// + +#define MFT_BITMAP_INITIAL_SIZE 0x2000 /* bytes of bitmap */ + + +// +// Symbolic Link attribute ****TBS +// + +typedef struct _SYMBOLIC_LINK { + + LARGE_INTEGER Tbs; + +} SYMBOLIC_LINK; +typedef SYMBOLIC_LINK *PSYMBOLIC_LINK; + + +// +// Ea Information attribute +// + +typedef struct _EA_INFORMATION { + + USHORT PackedEaSize; // Size of buffer to hold in unpacked form + USHORT NeedEaCount; // Count of EA's with NEED_EA bit set + ULONG UnpackedEaSize; // Size of buffer to hold in packed form + +} EA_INFORMATION; + + +typedef EA_INFORMATION *PEA_INFORMATION; + +struct PACKED_EA { + UCHAR Flag; + UCHAR NameSize; + UCHAR ValueSize[2]; // Was USHORT. + CHAR Name[1]; +}; + +DEFINE_POINTER_TYPES( PACKED_EA ); + +#define EA_FLAG_NEED 0x80 + +// +// SLEAZY_LARGE_INTEGER is used because x86 C8 won't allow us to +// do static init on a union (like LARGE_INTEGER) and we can't use +// BIG_INT for cases where we need to static-init an array with a non- +// default constructor. The bit pattern must be the same as +// LARGE_INTEGER. +// +typedef struct _SLEAZY_LARGE_INTEGER { + ULONG LowPart; + LONG HighPart; +} SLEAZY_LARGE_INTEGER, *PSLEAZY_LARGE_INTEGER; + + +// +// Attribute Definition Table +// +// The following struct defines the columns of this table. Initially they +// will be stored as simple records, and ordered by Attribute Type Code. +// + +typedef struct _ATTRIBUTE_DEFINITION_COLUMNS { + + // + // Unicode attribute name. + // + + WCHAR AttributeName[64]; + + // + // Attribute Type Code. + // + + ATTRIBUTE_TYPE_CODE AttributeTypeCode; + + // + // Default Display Rule for this attribute + // + + DISPLAY_RULE DisplayRule; + + // + // Default Collation rule + // + + COLLATION_RULE CollationRule; + + // + // ATTRIBUTE_DEF_xxx flags + // + + ULONG Flags; + + // + // Minimum Length for attribute, if present. + // + + SLEAZY_LARGE_INTEGER MinimumLength; + + // + // Maximum Length for attribute. + // + + SLEAZY_LARGE_INTEGER MaximumLength; + +} ATTRIBUTE_DEFINITION_COLUMNS; + +DEFINE_POINTER_TYPES( ATTRIBUTE_DEFINITION_COLUMNS ); + +// +// ATTRIBUTE_DEF_xxx flags +// + +// +// This flag is set if the attribute may be indexed. +// + +#define ATTRIBUTE_DEF_INDEXABLE (0x00000002) + +// +// This flag is set if the attribute may occur more than once, such as is +// allowed for the File Name attribute. +// + +#define ATTRIBUTE_DEF_DUPLICATES_ALLOWED (0x00000004) + +// +// This flag is set if the value of the attribute may not be entirely +// null, i.e., all binary 0's. +// + +#define ATTRIBUTE_DEF_MAY_NOT_BE_NULL (0x00000008) + +// +// This attribute must be indexed, and no two attributes may exist with +// the same value in the same file record segment. +// + +#define ATTRIBUTE_DEF_MUST_BE_INDEXED (0x00000010) + +// +// This attribute must be named, and no two attributes may exist with +// the same name in the same file record segment. +// + +#define ATTRIBUTE_DEF_MUST_BE_NAMED (0x00000020) + +// +// This attribute must be in the Resident Form. +// + +#define ATTRIBUTE_DEF_MUST_BE_RESIDENT (0x00000040) + +// +// Modifications to this attribute should be logged even if the +// attribute is nonresident. +// + +#define ATTRIBUTE_DEF_LOG_NONRESIDENT (0X00000080) + +// +// The remaining stuff in this file describes some of the lfs data +// structures; some of these are used by chkdsk, and some are used +// only by diskedit. +// + +typedef struct _MULTI_SECTOR_HEADER { + + // + // Space for a four-character signature + // + + UCHAR Signature[4]; + + // + // Offset to Update Sequence Array, from start of structure. The Update + // Sequence Array must end before the last USHORT in the first "sector" + // of size SEQUENCE_NUMBER_STRIDE. (I.e., with the current constants, + // the sum of the next two fields must be <= 510.) + // + + USHORT UpdateSequenceArrayOffset; + + // + // Size of Update Sequence Array (from above formula) + // + + USHORT UpdateSequenceArraySize; + +} MULTI_SECTOR_HEADER, *PMULTI_SECTOR_HEADER; + + +typedef struct _LFS_RESTART_PAGE_HEADER { + + // + // Cache multisector protection header. + // + + MULTI_SECTOR_HEADER MultiSectorHeader; + + // + // This is the last Lsn found by checkdisk for this volume. + // + + LSN ChkDskLsn; + + // + // + + ULONG SystemPageSize; + ULONG LogPageSize; + + // + // Lfs restart area offset. This is the offset from the start of this + // structure to the Lfs restart area. + // + + USHORT RestartOffset; + + USHORT MinorVersion; + USHORT MajorVersion; + + // + // Update Sequence Array. Used to protect the page blcok. + // + + UPDATE_SEQUENCE_ARRAY UpdateSequenceArray; + +} LFS_RESTART_PAGE_HEADER, *PLFS_RESTART_PAGE_HEADER; + +// +// Log Client Record. A log client record exists for each client user of +// the log file. One of these is in each Lfs restart area. +// + +#define LFS_NO_CLIENT 0xffff +#define LFS_CLIENT_NAME_MAX 64 + +typedef struct _LFS_CLIENT_RECORD { + + // + // Oldest Lsn. This is the oldest Lsn that this client requires to + // be in the log file. + // + + LSN OldestLsn; + + // + // Client Restart Lsn. This is the Lsn of the latest client restart + // area written to the disk. A reserved Lsn will indicate that no + // restart area exists for this client. + // + + LSN ClientRestartLsn; + + // + // + // Previous/Next client area. These are the indexes into an array of + // Log Client Records for the previous and next client records. + // + + USHORT PrevClient; + USHORT NextClient; + + // + // Sequence Number. Incremented whenever this record is reused. This + // will happen whenever a client opens (reopens) the log file and has + // no current restart area. + + USHORT SeqNumber; + + // + // Alignment field. + // + + USHORT AlignWord; + + // + // Align the entire record. + // + + ULONG AlignDWord; + + // + // The following fields are used to describe the client name. A client + // name consists of at most 32 Unicode character (64 bytes). The Log + // file service will treat client names as case sensitive. + // + + ULONG ClientNameLength; + + WCHAR ClientName[LFS_CLIENT_NAME_MAX]; + +} LFS_CLIENT_RECORD, *PLFS_CLIENT_RECORD; + +typedef struct _LFS_RESTART_AREA { + + // + // Current Lsn. This is periodic snapshot of the current logical end of + // log file to facilitate restart. + // + + LSN CurrentLsn; + + // + // Number of Clients. This is the maximum number of clients supported + // for this log file. + // + + USHORT LogClients; + + // + // The following are indexes into the client record arrays. The client + // records are linked into two lists. A free list of client records and + // an in-use list of records. + // + + USHORT ClientFreeList; + USHORT ClientInUseList; + + // + // Flag field. + // + // RESTART_SINGLE_PAGE_IO All log pages written 1 by 1 + // LFS_CLEAN_SHUTDOWN + // + + USHORT Flags; + + // + // The following is the number of bits to use for the sequence number. + // + + ULONG SeqNumberBits; + + // + // Length of this restart area. + // + + USHORT RestartAreaLength; + + // + // Offset from the start of this structure to the client array. + // Ignored in versions prior to 1.1 + // + + USHORT ClientArrayOffset; + + // + // Usable log file size. We will stop sharing the value in the page header. + // + + LONGLONG FileSize; + + // + // DataLength of last Lsn. This doesn't include the length of + // the Lfs header. + // + + ULONG LastLsnDataLength; + + // + // The following apply to log pages. This is the log page data offset and + // the length of the log record header. Ignored in versions prior to 1.1 + // + + USHORT RecordHeaderLength; + USHORT LogPageDataOffset; + + // + // Log file open count. Used to determine if there has been a change to the disk. + // + + ULONG RestartOpenLogCount; + + // + // Track log flush failures + // + + ULONG LastFailedFlushStatus; + LONGLONG LastFailedFlushOffset; + LSN LastFailedFlushLsn; + + // + // Keep this structure quadword aligned. + // + + // + // Client data. + // + + LFS_CLIENT_RECORD LogClientArray[1]; + +} LFS_RESTART_AREA, *PLFS_RESTART_AREA; + +// +// Flags definition in LFS_RESTART_AREA +// +#define LFS_CLEAN_SHUTDOWN (0x0002) + +#include + +extern +BOOLEAN +UntfsDefineClassDescriptors(); + +extern +BOOLEAN +UntfsUndefineClassDescriptors(); + diff --git a/untfs/inc/upcase.hxx b/untfs/inc/upcase.hxx new file mode 100644 index 0000000..ce3ea02 --- /dev/null +++ b/untfs/inc/upcase.hxx @@ -0,0 +1,138 @@ +/*++ + +Module Name: + + upcase.hxx + +Abstract: + + This module contains the declarations for the NTFS_UPCASE_TABLE + class. This class models the upcase table stored on an NTFS volume, + which is used to upcase attribute names and file names resident + on that volume. + +--*/ + +#pragma once + +DECLARE_CLASS( NTFS_ATTRIBUTE ); +DECLARE_CLASS( NTFS_BITMAP ); +DECLARE_CLASS( NTFS_UPCASE_TABLE ); + +#include "attrib.hxx" + +// This function is used to compare two NTFS names. Its definition +// appears in upcase.cxx. +// +LONG + +NtfsUpcaseCompare( + IN PCWSTR LeftName, + IN ULONG LeftNameLength, + IN PCWSTR RightName, + IN ULONG RightNameLength, + IN PCNTFS_UPCASE_TABLE UpcaseTable, + IN BOOLEAN CaseSensitive + ); + +class NTFS_UPCASE_TABLE : public OBJECT { + + public: + + + DECLARE_CONSTRUCTOR( NTFS_UPCASE_TABLE ); + + VIRTUAL + + ~NTFS_UPCASE_TABLE( + ); + + + BOOLEAN + Initialize( + IN PNTFS_ATTRIBUTE Attribute + ); + + + WCHAR + UpperCase( + IN WCHAR Character + ) CONST; + + + STATIC + ULONG + QueryDefaultLength( + ); + + + + private: + + + VOID + Construct( + ); + + + VOID + Destroy( + ); + + PWCHAR _Data; + ULONG _Length; +}; + + +INLINE +WCHAR +NTFS_UPCASE_TABLE::UpperCase( + IN WCHAR Character + ) CONST +/*++ + +Routine Description: + + This method returns the upper-case value of the supplied + character. + +Arguments: + + Character -- Supplies the character to upcase. + +Notes: + + If Character is not in the table (ie. is greater or equal + to _Length), it upcases to itself. + +--*/ +{ + return( (Character < _Length) ? _Data[Character] : Character ); +} + + + +INLINE +ULONG +NTFS_UPCASE_TABLE::QueryDefaultLength( + ) +/*++ + +Routine Description: + + This method returns the length (in characters) of the + default upcase table. + +Arguments: + + None. + +Return Value: + + The length (in characters) of the default upcase table. + +--*/ +{ + return 0x10000; +} + diff --git a/untfs/inc/upfile.hxx b/untfs/inc/upfile.hxx new file mode 100644 index 0000000..10163d0 --- /dev/null +++ b/untfs/inc/upfile.hxx @@ -0,0 +1,52 @@ +/*++ + +Module Name: + + upfile.hxx + +Abstract: + + This module contains the declarations for the NTFS_UPCASE_FILE + class, which models the upcase-table file for an NTFS volume. + This class' main purpose in life is to encapsulate the creation + of this file. + +--*/ + +#pragma once + +#include "frs.hxx" + +DECLARE_CLASS( NTFS_BITMAP ); +DECLARE_CLASS( NTFS_UPCASE_TABLE ); + +class NTFS_UPCASE_FILE : public NTFS_FILE_RECORD_SEGMENT { + +public: + + DECLARE_CONSTRUCTOR(NTFS_UPCASE_FILE); + + VIRTUAL + ~NTFS_UPCASE_FILE( + ); + + + BOOLEAN + Initialize( + IN OUT PNTFS_MASTER_FILE_TABLE Mft + ); + + +private: + + VOID + Construct( + ); + + VOID + Destroy( + ); + +}; + + diff --git a/untfs/src/attrib.cxx b/untfs/src/attrib.cxx new file mode 100644 index 0000000..a3652a5 --- /dev/null +++ b/untfs/src/attrib.cxx @@ -0,0 +1,3755 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + attrib.cxx + +Abstract: + + This module contains member function definitions for NTFS_ATTRIBUTE, + which models an NTFS attribute instance. + +--*/ + + +#include "ulib.hxx" + +#include "untfs.hxx" + +#include "drive.hxx" +#include "mft.hxx" +#include "attrrec.hxx" +#include "attrib.hxx" +#include "frs.hxx" +#include "ntfsbit.hxx" +#include "badfile.hxx" +#include "numset.hxx" + + +// This constant specifies the maximum number of clusters Read and +// Write will try to transfer at once. Note that it is chosen to +// ensure that MaximumClustersToTransfer * ClusterSize will fito +// in a ULONG. + +CONST int MaximumClustersToTransfer = 32; + +UCHAR +ComputeCompressionUnit( + IN ULONG ClusterSize + ); + +DEFINE_CONSTRUCTOR( NTFS_ATTRIBUTE, OBJECT ); + + + +NTFS_ATTRIBUTE::~NTFS_ATTRIBUTE( + ) +{ + Destroy(); +} + + +VOID +NTFS_ATTRIBUTE::Construct ( + ) +/*++ + +Routine Description: + + Worker method for object construction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _Type = 0; + _Flags = 0; + _CompressionUnit = 0; + _ValueLength = 0; + _ValidDataLength = 0; + + _ResidentData = NULL; + _ExtentList = NULL; + + _StorageModified = FALSE; +} + + +VOID +NTFS_ATTRIBUTE::Destroy( + ) +/*++ + +Routine Description: + + Worker method for object destruction or reinitialization. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _Type = 0; + + _Flags = 0; + _CompressionUnit = 0; + _ValueLength = 0; + _ValidDataLength = 0; + + if( _ResidentData != NULL ) { + + FREE( _ResidentData ); + _ResidentData = NULL; + } + + if( _ExtentList != NULL ) { + + DELETE( _ExtentList ); + _ExtentList = NULL; + } + + _StorageModified = FALSE; +} + + + +BOOLEAN +NTFS_ATTRIBUTE::Initialize ( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN PCVOID Value, + IN ULONG ValueLength, + IN ATTRIBUTE_TYPE_CODE TypeCode, + IN PCWSTRING Name, + IN USHORT Flags + ) +/*++ + +Routine Description: + + This method initializes a resident attribute based on its value. + +Arguments: + + + Drive -- supplies the drive on which the attribute resides + ClusterFactor -- supplies the cluster factor for that drive + Value -- supplies the attribute's value. + ValueLength -- supplies the length of the attribute's value. + TypeCode -- supplies the attribute's type code. + Name -- supplies the attribute's name. NULL indicates + that the attribute has no name. + Flags -- supplies the attribute's flags. + +Return Value: + + TRUE upon successful completion. + +Notes: + + If ValueLength is zero, then this is an empty attribute (and Value + may be NULL). + +--*/ +{ + Destroy(); + + _Drive = Drive; + _ClusterFactor = ClusterFactor; + if (Flags & (ATTRIBUTE_FLAG_COMPRESSION_MASK | + ATTRIBUTE_FLAG_SPARSE | + ATTRIBUTE_FLAG_ENCRYPTED)) { + _CompressionUnit = ComputeCompressionUnit( + _Drive->QuerySectorSize() * ClusterFactor); + } + + if( (_ResidentData = MALLOC( (UINT) ValueLength )) == NULL ) { + + Destroy(); + return FALSE; + } + + if (Name) { + if (!_Name.Initialize(Name)) { + Destroy(); + return FALSE; + } + } else { + if (!_Name.Initialize("")) { + Destroy(); + return FALSE; + } + } + + + // Copy the value into our buffer: + + memcpy( _ResidentData, Value, (UINT) ValueLength ); + + _ValueLength = ValueLength; + _ValidDataLength = ValueLength; + + _Type = TypeCode; + _Flags = Flags; + _ResidentFlags = 0; + _FormCode = RESIDENT_FORM; + + _StorageModified = TRUE; + + return TRUE; +} + + + +BOOLEAN +NTFS_ATTRIBUTE::Initialize ( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN PCNTFS_EXTENT_LIST Extents, + IN BIG_INT ValueLength, + IN BIG_INT ValidLength, + IN ATTRIBUTE_TYPE_CODE TypeCode, + IN PCWSTRING Name, + IN USHORT Flags + ) +/*++ + +Routine Description: + + This method initializes a non-resident attribute based on an + extent list. + +Arguments: + + Drive -- supplies the drive on which the attribute resides + ClusterFactor -- supplies the cluster factor for that drive + Extents -- supplies the extent list describing the attribute + value's disk storage. + ValueLength -- supplies the actual length of the attribute's value. + ValidLength -- supplies the valid length of the attribute's value. + TypeCode -- supplies the attribute's type code. + Name -- supplies the attribute's name. NULL indicates + that the attribute has no name. + Flags -- supplies the attribute's flags. + +Return Code: + + TRUE upon successful completion. + +--*/ +{ + Destroy(); + + if (Extents->QueryLowestVcn() != 0) { + Destroy(); + return FALSE; + } + + _Drive = Drive; + _ClusterFactor = ClusterFactor; + + if (Flags & (ATTRIBUTE_FLAG_COMPRESSION_MASK | + ATTRIBUTE_FLAG_SPARSE | + ATTRIBUTE_FLAG_ENCRYPTED)) { + _CompressionUnit = ComputeCompressionUnit( + _Drive->QuerySectorSize() * ClusterFactor); + } + + if( (_ExtentList = NEW NTFS_EXTENT_LIST) == NULL || + !_ExtentList->Initialize( Extents ) ) { + + Destroy(); + return FALSE; + } + + if (Name) { + if (!_Name.Initialize(Name)) { + Destroy(); + return FALSE; + } + } else { + if (!_Name.Initialize("")) { + Destroy(); + return FALSE; + } + } + + _ResidentData = NULL; + _ResidentFlags = 0; + + _ValueLength = ValueLength; + _ValidDataLength = ValidLength; + + if (CompareGT(_ValidDataLength, _ValueLength) || + CompareGT(_ValueLength, QueryAllocatedLength())) { + + Destroy(); + return FALSE; + } + + _Type = TypeCode; + _Flags = Flags; + _FormCode = NONRESIDENT_FORM; + _StorageModified = TRUE; + + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE::Initialize ( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN PCNTFS_ATTRIBUTE_RECORD AttributeRecord + ) +/*++ + +Routine Description: + + This method initializes an attribute based on an attribute record. + +Arguments: + + Drive -- supplies the drive on which the attribute resides + ClusterFactor -- supplies the cluster factor for that drive + AttributeRecord -- supplies the attribute record. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + BIG_INT AllocatedLength; + + Destroy(); + + if (AttributeRecord->QueryLowestVcn() != 0) { + Destroy(); + return FALSE; + } + + _Drive = Drive; + _ClusterFactor = ClusterFactor; + + _Type = AttributeRecord->QueryTypeCode(); + _Flags = AttributeRecord->QueryFlags(); + _CompressionUnit = (UCHAR)AttributeRecord->QueryCompressionUnit(); + _StorageModified = FALSE; + + if (!AttributeRecord->QueryName(&_Name)) { + + Destroy(); + return FALSE; + } + + if( AttributeRecord->IsResident() ) { + + _ValueLength = AttributeRecord->QueryResidentValueLength(); + + if( (_ResidentData = MALLOC( (UINT) AttributeRecord-> + QueryResidentValueLength() )) == + NULL ) { + + Destroy(); + return FALSE; + } + + // Copy the value into our buffer: + + memcpy( _ResidentData, + AttributeRecord->GetResidentValue(), + (UINT) AttributeRecord->QueryResidentValueLength() ); + + _ValidDataLength = _ValueLength; + + _FormCode = RESIDENT_FORM; + _ResidentFlags = AttributeRecord->QueryResidentFlags(); + + } else { + + if (!(_ExtentList = NEW NTFS_EXTENT_LIST) || + !AttributeRecord->QueryExtentList(_ExtentList)) { + + Destroy(); + return FALSE; + } + + AttributeRecord->QueryValueLength( &_ValueLength, + &AllocatedLength, + &_ValidDataLength ); + + _FormCode = NONRESIDENT_FORM; + _ResidentFlags = 0; + } + + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE::AddAttributeRecord ( + IN PCNTFS_ATTRIBUTE_RECORD AttributeRecord, + IN OUT PNTFS_EXTENT_LIST *BackupExtent + ) +/*++ + +Routine Description: + + This method adds an attribute record to the attribute. + + This method is intended to be used in inializing the + attribute with multiple attribute records. + +Arguments: + + AttributeRecord -- supplies the attribute record. + BackupExtent -- supplies/receives a copy of the extent list + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + DSTRING record_name; + NTFS_EXTENT_LIST extent_list; + ULONG i, n; + VCN vcn; + LCN lcn; + BIG_INT run_length; + + if (IsResident() || AttributeRecord->IsResident()) { + + // Can't have multiple resident attribute records. + return FALSE; + } + + DebugAssert(_ExtentList); + + if (_Type != AttributeRecord->QueryTypeCode()) { + + // Attribute record must have the same type code. + return FALSE; + } + + // The filesystem only cares about and maintains the Flags member + // in the first attribute record of a multi-frs attribute. So + // I removed the check below, which used to insure that each set + // of flags was identical. -mjb. + + if (!AttributeRecord->QueryName(&record_name) || + record_name.Strcmp(&_Name) || + /* _Flags != AttributeRecord->QueryFlags() || */ + !AttributeRecord->QueryExtentList(&extent_list) || + (*BackupExtent == NULL && + ((*BackupExtent = NEW NTFS_EXTENT_LIST) == NULL || + !(*BackupExtent)->Initialize(_ExtentList)))) { + + return FALSE; + } + + n = extent_list.QueryNumberOfExtents(); + + for (i = 0; i < n; i++) { + // Query i'th extent from attribute record and + // add it to the backup extent list + if (!extent_list.QueryExtent(i, &vcn, &lcn, &run_length) || + !(*BackupExtent)->AddExtent(vcn, lcn, run_length)) { + DebugPrint("UNTFS: Unable to update the backup extent list\n"); + return FALSE; + } + } + + for (i = 0; i < n; i++) { + + // Query i'th extent from attribute record and + // add it to the current extent list + if (!extent_list.QueryExtent(i, &vcn, &lcn, &run_length) || + !_ExtentList->AddExtent(vcn, lcn, run_length)) { + + // Restore the extent list. + for (i=0; i < n; i++) { + if (!extent_list.QueryExtent(i, &vcn, &lcn, &run_length) || + !(*BackupExtent)->DeleteRange(vcn, run_length)) { + DebugPrint("UNTFS: Unable to restore extent list\n"); + return FALSE; + } + } + _ExtentList->Initialize(*BackupExtent); + return FALSE; + } + } + + // If the LowestVcn of this record is less than the LowestVcn + // of the extent list, update the extent list. If the NextVcn + // of this record is greater than the NextVcn of the extent list, + // update the extent list. This will cover the cases where the + // the attribute is sparse and the new record begins or ends with + // a gap + // + + if( AttributeRecord->QueryLowestVcn() < _ExtentList->QueryLowestVcn() ) { + + _ExtentList->SetLowestVcn( AttributeRecord->QueryLowestVcn() ); + (*BackupExtent)->SetLowestVcn( AttributeRecord->QueryLowestVcn() ); + } + + if( AttributeRecord->QueryNextVcn() > _ExtentList->QueryNextVcn() ) { + + _ExtentList->SetNextVcn( AttributeRecord->QueryNextVcn() ); + (*BackupExtent)->SetNextVcn( AttributeRecord->QueryNextVcn() ); + } + + return TRUE; +} + +BOOLEAN +PartitionExtentList( + IN PCNTFS_EXTENT_LIST SourceList, + IN ULONG MaxSize, + OUT PNTFS_EXTENT_LIST ResultList, + OUT PNTFS_EXTENT_LIST RemainderList + ) +/*++ + +Routine Description: + + This routine partitions 'SourceList' into 'ResultList' and + 'RemainderList'. + + 'ResultList' contains as many extents from 'SourceList' as can be + compressed into 'MaxSize' bytes. + + 'RemainderList' contains all of the extents of 'SourceList' which are + not in 'ResultList'. + +Arguments: + + SourceList - Supplies the list of extents to partition. + MaxSize - Supplies the maximum number of bytes for + the compressed mapping pairs of the first + part of the partition. + ResultList - Returns the first part of the partition. + RemainderList - Returns the second part of the partition. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + CONST int MaxBytesPerMappingPair = sizeof(LCN) + sizeof(VCN) + 1; + + VCN lowest; + VCN next; + ULONG mapping_length; + PUCHAR mapping_space; + ULONG buffer_size; + ULONG count; + ULONG ptr; + VCN vcn; + LCN lcn; + BIG_INT run_length; + ULONG i; + VCN part_next; + ULONG num_extents; + UCHAR v, l; + BIG_INT tmp; + BIG_INT sum; + BOOLEAN ends_with_gap = FALSE; + BOOLEAN HasHoleInFront; + + + // Handle an empty list gracefully + + if( SourceList->IsEmpty() ) + { + if( !ResultList->Initialize( SourceList ) || + !RemainderList->Initialize( 0, 0 ) ) { + + return FALSE; + } + + return TRUE; + } + + + // compute an upper bound for the space required by the compressed + // extents of the source extent list. + + // This upper bound formula may be too much as QueryNumberOfExtents will + // return all entries including gaps except the last one if it is a gap. + + buffer_size = MaxBytesPerMappingPair* + (2*SourceList->QueryNumberOfExtents() + 1) + 1; + + if ( (mapping_space = (PUCHAR) MALLOC( (UINT) buffer_size )) == NULL ) { + + return FALSE; + } + + + // Get the compressed mapping pairs for the source list. + + // The HasHoleInFront flag allows us to take into account of the + // (0, ffffffff. ????) entry at the very beginning of an extented + // list. As this entry won't make it into the compression pairs. + // The code below that counts the compression pairs and retrieves + // vcn/lcn/runlength tuple needs to take into account of this entry. + + + if (!SourceList->QueryCompressedMappingPairs(&lowest, + &next, + &mapping_length, + buffer_size, + mapping_space, + &HasHoleInFront)) { + FREE(mapping_space); + return FALSE; + } + + // Let count denote the number of extents in the first partition + + count = 0; + ptr = 0; + sum = 0; + while (mapping_space[ptr] != 0) { + + v = VcnBytesFromCountByte(mapping_space[ptr]); + l = LcnBytesFromCountByte(mapping_space[ptr]); + + // Only consider this mapping pair if it will fit along with + // the next description byte. + + if (ptr + v + l + 2 > MaxSize) { + break; + } + + tmp.Set(l, &mapping_space[ptr + v + 1]); + sum += tmp; + +#if 0 + if (SourceList->QueryNumberOfExtents() > 20) { + DebugPrintTrace(("%d, %I64x, ", count, sum)); + if (SourceList->QueryExtent(count, &vcn, &lcn, &run_length)) { + DebugPrintTrace(("(%I64x, %I64x, %I64x)\n", vcn, lcn, run_length)); + } else { + DebugPrintTrace(("(error)\n")); + } + } +#endif + + count++; + + // If the number of LCN bytes is 0 or the lcn is -1, + // then it's a place holder, not a real extent. + + if (l != 0 && sum != -1) { + ends_with_gap = FALSE; + } else { + ends_with_gap = TRUE; + } + + ptr += v + l + 1; + } + +#if 0 + if (SourceList->QueryNumberOfExtents() > 20) { + DebugPrintTrace(("%d", count)); + if (SourceList->QueryExtent(count, &vcn, &lcn, &run_length)) { + DebugPrintTrace(("(%I64x, %I64x, %I64x)\n", vcn, lcn, run_length)); + } else { + DebugPrintTrace(("(error)\n")); + } + } +#endif + + // Compute the next VCN of the first partition, which is also the + // starting VCN of the remainder. + // + if (mapping_space[ptr] == 0) { + + // We processed and accepted for the first partition the entire + // source list; this means that the result list is the same as + // the sources, and the remainder is empty. + // + count = SourceList->QueryNumberOfExtents(); + part_next = next; + + } else { + + if (!SourceList->QueryExtent(count, + &part_next, &lcn, &run_length)) { + return FALSE; + } + } + + FREE(mapping_space); + + + // Now that we know how to split it up, create the two partitions. + + if (!ResultList->Initialize(lowest, part_next) || + !RemainderList->Initialize(part_next, next)) { + + return FALSE; + } + + for (i = 0; i < count; i++) { + + if (!SourceList->QueryExtent(i, &vcn, &lcn, &run_length)) { + return FALSE; + } + + if (LCN_NOT_PRESENT == lcn) { + continue; + } + + if (!ResultList->AddExtent(vcn, lcn, run_length)) { + return FALSE; + } + } + + num_extents = SourceList->QueryNumberOfExtents(); + + for (i = count; i < num_extents; i++) { + + if (!SourceList->QueryExtent(i, &vcn, &lcn, &run_length)) { + return FALSE; + } + + if (LCN_NOT_PRESENT == lcn) { + continue; + } + + if (!RemainderList->AddExtent(vcn, lcn, run_length)) { + return FALSE; + } + } + + return TRUE; +} + + + + +BOOLEAN +NTFS_ATTRIBUTE::InsertIntoFile ( + IN OUT PNTFS_FILE_RECORD_SEGMENT BaseFileRecordSegment, + IN OUT PNTFS_BITMAP Bitmap + ) +/*++ + + +Routine Description: + + This method inserts the attribute into a File Record Segment. + The attribute packages itself up into Attribute Records and + jams them into the File Record Segment. + +Arguments: + + FileRecordSegment -- Supplies the File Record Segment into + which the attribute will jam itself. + Bitmap -- Supplies the volume bitmap. + +Return Value: + + TRUE upon successful completion. + +Notes: + + If the volume bitmap is supplied, the attribute may make itself + nonresident, or the File Record Segment may make one or more of + its attribute records nonresident or external. + +--*/ +{ + NTFS_ATTRIBUTE_RECORD AttributeRecord; + PVOID AttributeRecordData; + BOOLEAN Result; + ULONG MaxSize; + ULONG MaxExtentsSize; + NTFS_EXTENT_LIST source; + NTFS_EXTENT_LIST result; + NTFS_EXTENT_LIST remainder; + BOOLEAN FirstChunkInserted = FALSE; + BOOLEAN Completed = FALSE; + + + // First purge the attribute out of the file, unless it's indexed. + + if (!IsIndexed()) { + + if (!BaseFileRecordSegment->PurgeAttribute(_Type, &_Name)) { + return FALSE; + } + } + + // If this is the MFT Data attribute, use the private worker method: + // + if( BaseFileRecordSegment->QueryFileNumber() == MASTER_FILE_TABLE_NUMBER && + QueryTypeCode() == $DATA && + _Name.QueryChCount() == 0 ) { + + // First, try to save the MFT data attribute conservatively, + // leaving space for future hotfixing. If that fails (typically + // because of bootstrapping problems), fill it to the gills. + // + if( InsertMftDataIntoFile( BaseFileRecordSegment, Bitmap, TRUE ) ) { + + ResetStorageModified(); + return TRUE; + + } else { + + Result = BaseFileRecordSegment->PurgeAttribute(_Type, &_Name) && + InsertMftDataIntoFile( BaseFileRecordSegment, Bitmap, FALSE ); + ResetStorageModified(); + return Result; + } + } + + + // Allocate a buffer to hold attribute records. + + MaxSize = BaseFileRecordSegment->QueryMaximumAttributeRecordSize(); + + if( (AttributeRecordData = MALLOC( (UINT) MaxSize )) == NULL ) { + + return FALSE; + } + + + // Handle the resident attribute case: + + if ( _ResidentData != NULL ) { + + if( !AttributeRecord.Initialize( GetDrive(), AttributeRecordData, MaxSize ) ) { + + FREE( AttributeRecordData ); + return FALSE; + } + + // The attribute value is resident. Package up a resident + // attribute record. + + Result = AttributeRecord. + CreateResidentRecord( _ResidentData, + _ValueLength.GetLowPart(), + _Type, + &_Name, + _Flags, + _ResidentFlags ); + + // + // Check to see if there is enough space to Create a resident record + // + + if (Result) { + Result = BaseFileRecordSegment-> + InsertAttributeRecord( &AttributeRecord ); + + FREE( AttributeRecordData ); + return Result; + } else { + // Not enough space to do so, make attribute record non-resident + if (IsIndexed() || !Bitmap || !MakeNonresident(Bitmap)) { + FREE( AttributeRecordData ); + return FALSE; + } + } + } + + + // Compute the maximum number of bytes in an extent list. + // + MaxExtentsSize = MaxSize - SIZE_OF_NONRESIDENT_HEADER; + if (_Flags & (ATTRIBUTE_FLAG_COMPRESSION_MASK | + ATTRIBUTE_FLAG_SPARSE)) { + MaxExtentsSize -= sizeof(BIG_INT); + } + MaxExtentsSize -= QuadAlign(_Name.QueryChCount()); + Result = source.Initialize(_ExtentList); + + while (Result && !Completed) { + + // Initialize attribute record. + + Result = AttributeRecord.Initialize( GetDrive(), AttributeRecordData, MaxSize ); + + + // Partition extent list into two pieces, the first of which + // can be made into an attribute record. + + Result = Result && + PartitionExtentList(&source, + MaxExtentsSize, + &result, + &remainder); + + + // Create the attribute record. + + Result = Result && + AttributeRecord. + CreateNonresidentRecord( &result, + QueryAllocatedLength(), + _ValueLength, + _ValidDataLength, + _Type, + &_Name, + _Flags, + _CompressionUnit, + _ClusterFactor*_Drive->QuerySectorSize()); + + + // If we were able to package it up, then give the attribute + // record to the File Record Segment. + + Result = Result && + BaseFileRecordSegment-> + InsertAttributeRecord( &AttributeRecord ); + + + // If all of the extents fit in the last record then we are done. + + if (remainder.IsEmpty()) { + + Completed = TRUE; + + } else { + + Result = Result && + source.Initialize(&remainder); + } + } + + + ResetStorageModified(); + FREE( AttributeRecordData ); + return Result; +} + + + +BOOLEAN +NTFS_ATTRIBUTE::MakeNonresident ( + IN OUT PNTFS_BITMAP Bitmap + ) +/*++ + +Routine Description: + + This method makes the attribute value nonresident. + +Arguments: + + Bitmap -- supplies the volume bitmap. + +Return Value: + + TRUE upon successful completion. + +Notes: + + If the attribute is already nonresident, this method succeeds. + +--*/ +{ + NTFS_CLUSTER_RUN ClusterRun; + HMEM IntermediateBuffer; + LCN Lcn; + ULONG DataLength, ClusterSize, ClustersRequired, ClustersAllocated, MaxRunLength; + ULONG RunLength; + + if (!IsResident()) { + + // The attribute is already nonresident, which makes + // this task pretty easy. + + return TRUE; + } + + + // Since the attribute is resident, its length will fit in a ULONG. + // + DebugAssert(_ValueLength.GetHighPart() == 0); + + DataLength = _ValueLength.GetLowPart(); + + if (DataLength == 0) { + + // This attribute has no data, so we just set up an empty + // extent list. + + if ((_ExtentList = NEW NTFS_EXTENT_LIST) == NULL || + !_ExtentList->Initialize( 0, 0 )) { + if (_ExtentList != NULL) + DELETE(_ExtentList); + return FALSE; + } + + FREE(_ResidentData); + _ResidentData = NULL; + + _FormCode = NONRESIDENT_FORM; + SetStorageModified(); + return TRUE; + } + + // This attribute has data, so we need to allocate disk space + // for it, copy it into that disk space, and set up an extent + // list that describes that disk space. Determine how many + // clusters we need to hold the resident value. + + if ((_ExtentList = NEW NTFS_EXTENT_LIST ) == NULL || + !_ExtentList->Initialize( 0, 0 )) { + + if (_ExtentList != NULL) + DELETE( _ExtentList ); + return FALSE; + } + + ClusterSize = _Drive->QuerySectorSize() * _ClusterFactor; + + ClustersRequired = DataLength / ClusterSize; + if (DataLength % ClusterSize) { + + ClustersRequired += 1; + } + + ClustersAllocated = 0; + MaxRunLength = ClustersRequired; + + while (ClustersAllocated < ClustersRequired) + { + + // + // Never try to allocate more clusters than we need to finish the + // allocation. + // + + RunLength = min(MaxRunLength, ClustersRequired - ClustersAllocated); + + if (!Bitmap->AllocateClusters( 0, RunLength, &Lcn )) { + + if (RunLength == 1) { + + // Out of disk space. + return FALSE; + } + + MaxRunLength /= 2; + continue; + } + + _ExtentList->AddExtent( ClustersAllocated /* vcn */, + Lcn, + RunLength ); + + // + // Copy data from the resident attribute value into this chunk of + // the nonresident attribute allocation. + // + + if (!IntermediateBuffer.Initialize() || + !ClusterRun.Initialize( &IntermediateBuffer, _Drive, Lcn, _ClusterFactor, + RunLength )) { + + Bitmap->SetFree( Lcn, RunLength ); + return FALSE; + } + + memset( ClusterRun.GetBuf(), '\0', ClusterSize * RunLength ); + memcpy( ClusterRun.GetBuf(), + PUCHAR(_ResidentData) + ClustersAllocated * ClusterSize, + min(ClusterSize * RunLength, + DataLength - ClustersAllocated * ClusterSize) ); + + if (!ClusterRun.Write()) { + + Bitmap->SetFree( Lcn, RunLength ); + return FALSE; + } + + ClustersAllocated += RunLength; + } + + // + // We've succeeded in making the attribute value nonresident. + // Clean up the resident data and change the state variables. + // + + FREE( _ResidentData ); + _ResidentData = NULL; + + _FormCode = NONRESIDENT_FORM; + + SetStorageModified(); + + return TRUE; +} + + + +BOOLEAN +NTFS_ATTRIBUTE::Resize ( + IN BIG_INT NewSize, + IN OUT PNTFS_BITMAP Bitmap + ) +/*++ + +Routine Description: + + This method changes the file size of an attribute. It will also modify + the allocated size appropriately, either extending or truncating it. + +Arguments: + + NewSize -- supplies the attribute value's new allocated size. + Bitmap -- supplies the volume bitmap. May be NULL. + +Return Value: + + None. + +Notes: + + If the attribute value is resident and the client attempts to + allocate to a new size which is greater than the maximum ULONG, + this method will fail. The client must call MakeNonresident first. + + Note that a nonresident attribute cannot be extended without + the bitmap; if a nonresident attribute is truncated without + a bitmap then the free space is not updated in the bitmap. + +--*/ +{ + BIG_INT NewNumberOfClusters, NewAllocatedSize; + PVOID NewData; + ULONG ClusterSize; + + if (_ValueLength == NewSize && + QueryAllocatedLength() == NewSize) { + + return TRUE; + } + + if( _ResidentData != NULL ) { + + // The attribute value is resident. We just allocate a + // new chunk of memory, zero it out, copy in the old value + // (or as much of it as fits), and adjust the length fields. + + // Note that we do not allow the client to resize a resident + // attribute to a size greater than the maximum ULONG. + + if( NewSize.GetHighPart() == 0 && + (NewData = MALLOC( NewSize.GetLowPart() )) != NULL ) { + + memset( NewData, '\0', NewSize.GetLowPart() ); + memcpy( NewData, + _ResidentData, + MIN(_ValueLength.GetLowPart(), NewSize.GetLowPart()) ); + + _ValueLength = NewSize; + _ValidDataLength = NewSize; + + FREE( _ResidentData ); + _ResidentData = NewData; + + SetStorageModified(); + + return TRUE; + + } else { + + return FALSE; + } + + } else { + + // The attribute value is nonresident. First, we round the + // allocation size up to a multiple of the volume cluster size. + // Since ClusterSize is always a power of two, the use of + // the low part of NewSize in this modulo operation is safe. + + ClusterSize = _ClusterFactor * _Drive->QuerySectorSize(); + + NewAllocatedSize = NewSize; + + if( NewAllocatedSize % ClusterSize != 0 ) { + + NewAllocatedSize += (ClusterSize - NewAllocatedSize % ClusterSize); + } + + NewNumberOfClusters = NewAllocatedSize / ClusterSize; + + DebugAssert( _ExtentList != NULL ); + + if (_ExtentList == NULL) + return FALSE; + + if( _ExtentList->Resize( NewNumberOfClusters, Bitmap ) ) { + + _ValueLength = NewSize; + + if( CompareGT(_ValidDataLength, _ValueLength) ) { + + _ValidDataLength = _ValueLength; + } + + SetStorageModified(); + + return TRUE; + + } else { + + return FALSE; + } + } +} + + +BOOLEAN +NTFS_ATTRIBUTE::SetSparse( + IN BIG_INT NewSize, + IN OUT PNTFS_BITMAP Bitmap + ) +/*++ + +Routine Description: + + This method changes the file size of an sparse attribute to the given size. It + will free any allocated clusters and put a hole at the beginning. + +Arguments: + + NewSize -- supplies the attribute value's new file size. + Bitmap -- supplies the volume bitmap. + +Return Value: + + None. + +Notes: + +--*/ +{ + BIG_INT NewNumberOfClusters, NewAllocatedSize; + ULONG ClusterSize; + + if (!(_Flags & ATTRIBUTE_FLAG_SPARSE)) { + + // return error if this is not a sparse file + + return FALSE; + } + + if ( _ResidentData != NULL ) { + + // The attribute value is resident. So, make it into a non-resident + // one first. + + if (!MakeNonresident(Bitmap)) + return FALSE; + + } + + // The attribute value is nonresident. First, we round the + // allocation size up to a multiple of the volume cluster size. + // Since ClusterSize is always a power of two, the use of + // the low part of NewSize in this modulo operation is safe. + + ClusterSize = _ClusterFactor * _Drive->QuerySectorSize(); + + NewAllocatedSize = NewSize; + + if( NewAllocatedSize % ClusterSize != 0 ) { + + NewAllocatedSize += (ClusterSize - NewAllocatedSize % ClusterSize); + } + + NewNumberOfClusters = NewAllocatedSize / ClusterSize; + + DebugAssert( _ExtentList != NULL ); + + if (_ExtentList == NULL) + return FALSE; + + // now throw away any allocation and then make + // the stream sparse + + if( _ExtentList->Resize( 0, Bitmap ) && + _ExtentList->SetSparse(NewNumberOfClusters)) { + + _ValueLength = NewSize; + + _ValidDataLength = 0; + + SetStorageModified(); + + return TRUE; + + } else { + + return FALSE; + } +} + + +BOOLEAN +NTFS_ATTRIBUTE::AddExtent( + IN VCN Vcn, + IN LCN Lcn, + IN BIG_INT RunLength + ) +/*++ + +Routine Description: + + This method adds an extent to the Attribute's allocation. (Note + that if the attribute is resident, this method will fail.) + +Arguments: + + Vcn -- supplies the starting VCN of the run. + Lcn -- supplies the starting LCN of the run. + RunLength -- supplies the number of clusters in the run. + +Return Value: + + TRUE upon successful completion. +--*/ +{ + if( _ExtentList == NULL ) { + + return FALSE; + + } else { + + if ( _ExtentList->AddExtent( Vcn, Lcn, RunLength ) ) { + + SetStorageModified(); + return TRUE; + + } else { + + return FALSE; + } + } +} + + + +BOOLEAN +NTFS_ATTRIBUTE::Read ( + OUT PVOID Data, + IN BIG_INT ByteOffset, + IN ULONG BytesToRead, + OUT PULONG BytesRead + ) +/*++ + +Routine Description: + + This method reads data from the attribute's value. + +Arguments: + + Data -- supplies the user's buffer, into which data + will be read. + ByteOffset -- supplies the byte offset into the attribute value + at which the read should commence. + BytesToRead -- supplies the number of bytes to read. + BytesRead -- receives the number of bytes actually read. + +Return Value: + + TRUE upon successful completion. + +Notes: + + Read will only read up to the attribute's actual length. + + Note that Read ignores the attribute's valid data length. + + Note that if Read fails, the contents of the user's buffer is + undefined. + + This method is able to handle sparse attributes. + +--*/ +{ + NTFS_CLUSTER_RUN ClusterRun; + HMEM IntermediateBuffer; + BIG_INT TempBigInt; + BIG_INT RunLength; + VCN CurrentVcn; + LCN CurrentLcn; + PBYTE CurrentData; + ULONG BytesToCopy; + ULONG OffsetIntoCluster; + ULONG ClusterSize; + ULONG CurrentRunLength; + ULONG RemainingRequest; + ULONG BytesToZero = 0; + + // First, perform some range-checking. We can only read to + // the end of the actual size of the attribute value. + + if( _ValueLength <= ByteOffset ) { + + BytesToRead = 0; + + } else if ( _ValueLength < ByteOffset + BytesToRead ) { + + // Since this difference is less than BytesToRead, this + // assignment is safe: + + TempBigInt = _ValueLength - ByteOffset; + BytesToRead = TempBigInt.GetLowPart(); + } + + + if( _ResidentData != NULL ) { + + // Since the attribute value is resident, we can + // just copy it. We've verified above that the request + // fits in the allocated space, so there's nothing more + // to do except the copy itself. + + memcpy( Data, + (PCHAR) _ResidentData + ByteOffset.GetLowPart(), + (UINT) BytesToRead ); + + } else if ( _ExtentList != NULL ) { + + RemainingRequest = BytesToRead; + + // Now check the valid length. If the entire read is beyond + // the end valid data, just zero it out; otherwise, zero out + // the portion beyond the end of valid data. + // + if( CompareLTEQ(_ValidDataLength, ByteOffset) ) { + + // The entire read is beyond the end of valid data. + // + memset( Data, 0, BytesToRead ); + *BytesRead = BytesToRead; + return TRUE; + + } else if( CompareLT(_ValidDataLength, ByteOffset + BytesToRead) ) { + + // Only read the portion up to the end of valid data; + // zero the rest out. + // + TempBigInt = _ValidDataLength - ByteOffset; + RemainingRequest = TempBigInt.GetLowPart(); + + BytesToZero = BytesToRead - RemainingRequest; + + memset( (PBYTE) Data + RemainingRequest, 0, BytesToZero ); + } + + // The attribute value is nonresident, so we'll have to go + // find it on disk. First, we'll read any leading partial + // cluster through an intermediate buffer. Then we'll read + // as many whole clusters as there are in the request directly + // into the user's buffer. Finally, we'll read any trailing + // partial cluster through the intermediate buffer. + + if( RemainingRequest > 0 ) { + + ClusterSize = _ClusterFactor * _Drive->QuerySectorSize(); + CurrentData = (PBYTE) Data; + + OffsetIntoCluster = (ByteOffset % ClusterSize).GetLowPart(); + + if( OffsetIntoCluster != 0 ) { + + // We have a partial leading cluster, so we'll read + // it through the intermediate buffer. + + BytesToCopy = MIN( BytesToRead - BytesToZero, + ClusterSize - OffsetIntoCluster ); + + CurrentVcn = ByteOffset / ClusterSize; + + if( !_ExtentList-> QueryLcnFromVcn( CurrentVcn, + &CurrentLcn, + &RunLength ) ) { + + + DebugPrint( "Unable to Query Lcn from Vcn.\n" ); + + return FALSE; + } + + if( CurrentLcn == LCN_NOT_PRESENT ) { + + // This part of the request hits a hole in a + // sparse file, so we just fill the corresponding + // part of the request with zeroes. + + memset( CurrentData, 0, BytesToCopy ); + + } else { + + // Read the cluster containing this part of the + // request and copy the partial leading cluster + // into the client's buffer. + + if( !IntermediateBuffer.Initialize() || + !ClusterRun.Initialize( &IntermediateBuffer, + _Drive, + CurrentLcn, + _ClusterFactor, + 1 ) || + !ClusterRun.Read() ) { + + DebugPrint( "Cannot read leading clusters.\n" ); + + return FALSE; + } + + memcpy( CurrentData, + (PBYTE)ClusterRun.GetBuf() + OffsetIntoCluster, + (UINT) BytesToCopy ); + } + + RemainingRequest -= BytesToCopy; + CurrentData += BytesToCopy; + ByteOffset += BytesToCopy; + } + + // Now transfer any complete clusters. Because the + // client's buffer may not be suitably aligned, we + // have to cycle these through an intermediate buffer. + + while( RemainingRequest >= ClusterSize ) { + + CurrentVcn = ByteOffset / ClusterSize; + + if( !_ExtentList->QueryLcnFromVcn( CurrentVcn, + &CurrentLcn, + &RunLength ) ) { + + + DebugPrint( "Unable to Query Lcn from Vcn.\n" ); + + return FALSE; + } + + if( RunLength.GetHighPart() != 0 || + RunLength.GetLowPart() > + MaximumClustersToTransfer ) { + + CurrentRunLength = MaximumClustersToTransfer; + + } else { + + CurrentRunLength = RunLength.GetLowPart(); + } + + if( CurrentRunLength * ClusterSize > + RemainingRequest ) { + + CurrentRunLength = RemainingRequest/ClusterSize; + } + + BytesToCopy = CurrentRunLength * ClusterSize; + + if( CurrentLcn == LCN_NOT_PRESENT ) { + + // This part of the read request falls into a hole + // in a sparse attribute, so we can just fill the + // client's buffer with zeroes. + + memset( CurrentData, 0, BytesToCopy ); + + } else { + + // Read the data into the temporary buffer (used + // to avoid alignment problems) and then copy it + // into the client's buffer. + + if( !IntermediateBuffer.Initialize( ) || + !ClusterRun.Initialize( &IntermediateBuffer, + _Drive, + CurrentLcn, + _ClusterFactor, + CurrentRunLength ) || + !ClusterRun.Read() ) { + + DebugPrint( "Cannot read complete clusters.\n" ); + return FALSE; + } + + memcpy( CurrentData, + IntermediateBuffer.GetBuf(), + BytesToCopy ); + } + + RemainingRequest -= BytesToCopy; + CurrentData += BytesToCopy; + ByteOffset += BytesToCopy; + } + + if( RemainingRequest > 0 ) { + + // OK, we have a partial trailing cluster. Read + // it through the intermediate buffer. + + BytesToCopy = RemainingRequest; + + CurrentVcn = ByteOffset / ClusterSize; + + if( !_ExtentList->QueryLcnFromVcn( CurrentVcn, + &CurrentLcn, + &RunLength ) ) { + + + DebugPrint( "Unable to Query Lcn from Vcn.\n" ); + + return FALSE; + } + + if( CurrentLcn == LCN_NOT_PRESENT ) { + + // This part of the read request falls into a hole + // in a sparse attribute, so we can just fill the + // appropriate part of the client's buffer with + // zeroes. + + memset( CurrentData, 0, BytesToCopy ); + + } else { + + // Read this part of the request into an intermediate + // buffer (to avoid alignment problems) and then + // copy the data into the client's buffer. + + if( !IntermediateBuffer.Initialize() || + !ClusterRun.Initialize( &IntermediateBuffer, + _Drive, + CurrentLcn, + _ClusterFactor, + 1 ) || + !ClusterRun.Read() ) { + DebugPrint( "Cannot read partial clusters.\n" ); + + return FALSE; + } + + + // We've read the cluster in question; copy the partial + // trailing cluster of our request. + + memcpy( CurrentData, + ClusterRun.GetBuf(), + (UINT) BytesToCopy ); + } + } + } + + } else { + + DebugAbort( "This attribute is neither resident nor nonresident.\n" ); + return FALSE; + } + + *BytesRead = BytesToRead; + return TRUE; +} + + +VOID +NTFS_ATTRIBUTE::PrimeCache ( + IN BIG_INT ByteOffset, + IN ULONG BytesToRead + ) +/*++ + +Routine Description: + + This routine reads the given range from the attribute. If the drive + hierarchy is cached then this will have the effect of priming the + cache so that fewer reads are necessary. + +Arguments: + + ByteOffset - Supplies where to start the read. + BytesToRead - Supplies the number of bytes to read. + +Return Value: + + None. + +--*/ +{ + HMEM hmem; + PVOID buf; + ULONG bytes_read; + + if (hmem.Initialize() && + (buf = hmem.Acquire(BytesToRead))) { + + Read(buf, ByteOffset, BytesToRead, &bytes_read); + } +} + + + +BOOLEAN +NTFS_ATTRIBUTE::Write ( + IN PCVOID Data, + IN BIG_INT ByteOffset, + IN ULONG BytesToWrite, + OUT PULONG BytesWritten, + IN OUT PNTFS_BITMAP Bitmap + ) + +/*++ + +Routine Description: + + This method writes data to the attribute's value. + +Arguments: + + Data -- supplies the user's buffer containing data + to be written + ByteOffset -- supplies the byte offset within the attribute's + value at shich the write should commence. + BytesToWrite -- supplies the number of bytes to write. + BytesWritten -- receives the number of bytes written. + Bitmap -- supplies the volume bitmap. This may be NULL. + +Notes: + + If the user supplies a bitmap, Write will attempt to extend the + attribute's allocation (if necessary) in order to complete the + write. If the user does not supply a bitmap, Write will fail if + the write extends past the attribute value's allocated length. + (It may or may not write some of the data.) + + Note that this method does not check the attribute's valid data + length, but it does reset the valid data length if it writes + past the valid data length. Therefore, clients must use some + caution to avoid introducing stretches of uninitialized data + in the attribute (which would be a security leak). + + Note that if Write fails, the contents of the attribute on + disk is undefined. + + Note also that Write is not supported for sparse files. + +--*/ +{ + NTFS_CLUSTER_RUN ClusterRun; + HMEM IntermediateBuffer; + BIG_INT TempBigInt; + BIG_INT RunLength; + BIG_INT OldValidDataLength; + VCN CurrentVcn, RecentLcn; + LCN CurrentLcn; + PBYTE CurrentData; + ULONG BytesToCopy, OffsetIntoCluster, CurrentRunLength; + ULONG ClusterSize; + ULONG RemainingRequest; + ULONG BytesToZero; + + // First, make sure that the space allocated to the attribute + // value is sufficient. + + if( CompareLT(QueryAllocatedLength(), ByteOffset + BytesToWrite) && + !Resize( ByteOffset + BytesToWrite, Bitmap ) ) { + + // This attribute does not have enough space allocated + // to it to satisfy the write request, and we could not + // extend the allocation, so the write fails. + + return FALSE; + } + + // Now check the valid data length. If the write begins + // past the end of valid data, we have to fill the intervening + // gap with zeroes. Note that before we call Fill, we + // must set the Valid Data Length appropriately, since Fill + // just recurses back into Write. + // + if( CompareLT(_ValidDataLength, ByteOffset) ) { + + TempBigInt = ByteOffset - _ValidDataLength; + + if( TempBigInt.GetHighPart() != 0 ) { + + DebugPrint( "UNTFS: Writing discontiguous huge attribute.\n" ); + return FALSE; + } + + BytesToZero = TempBigInt.GetLowPart(); + + OldValidDataLength = _ValidDataLength; + _ValidDataLength = ByteOffset; + + if( !Fill( OldValidDataLength, 0, BytesToZero ) ) { + + // Couldn't zero-fill the gap; restore Valid Data + // Length and return failure. + // + _ValidDataLength = OldValidDataLength; + return FALSE; + } + } + + + if( _ResidentData != NULL ) { + + // Since the attribute value is resident, we can + // just copy it. + // + DebugAssert( ByteOffset.GetHighPart() == 0 ); + + memcpy( (PBYTE)_ResidentData + ByteOffset.GetLowPart(), + Data, + (UINT) BytesToWrite ); + + SetStorageModified(); + ByteOffset += BytesToWrite; + + } else if ( _ExtentList != NULL ) { + + // Now we can actually start writing stuff! First, we'll write + // any partial leading cluster through an intermediate buffer. + // Next, we write entire clusters directly from the user's buffer. + // Finally, we write any partial trailing cluster. + + ClusterSize = _ClusterFactor * _Drive->QuerySectorSize(); + + RemainingRequest = BytesToWrite; + + // RecentLcn is used in case we need to grab extents on the + // fly--if we have to allocate space to fill in holes in a + // sparse attribute, using RecentLcn will increase the probability + // that the space we grab is close to the rest of the attribute. + + RecentLcn = 0; + + if( RemainingRequest > 0 ) { + + CurrentData = (PBYTE) Data; + + OffsetIntoCluster = (ByteOffset % ClusterSize).GetLowPart(); + + if( OffsetIntoCluster != 0 ) { + + // We have a partial leading cluster, so we'll write + // it through the intermediate buffer. Note that we + // must read the cluster in, copy the part we intend + // to write, and then write it back out. + + BytesToCopy = MIN( BytesToWrite, + ClusterSize - OffsetIntoCluster ); + + CurrentVcn = ByteOffset / ClusterSize; + + if( !_ExtentList->QueryLcnFromVcn( CurrentVcn, + &CurrentLcn, + &RunLength ) ) { + + DebugPrint( "Unable to Query Lcn from Vcn.\n" ); + + return FALSE; + } + + if( CurrentLcn == LCN_NOT_PRESENT ) { + + // This portion of the request falls into a + // hole in a sparse attribute, so we have + // to allocate disk space for it and add + // this new extent to the extent list. If + // we can't, the request fails. + + if( Bitmap == NULL || + !Bitmap->AllocateClusters( RecentLcn, + 1, + &CurrentLcn) || + !_ExtentList->AddExtent( CurrentVcn, + CurrentLcn, + 1 ) ) { + + return FALSE; + } + } + + RecentLcn = CurrentLcn; + + if( !IntermediateBuffer.Initialize() || + !ClusterRun.Initialize( &IntermediateBuffer, + _Drive, + CurrentLcn, + _ClusterFactor, + 1 ) || + !ClusterRun.Read() ) { + + DebugPrint( "Could not read partial leading sector\n" ); + return FALSE; + } + + // We've read the cluster in question; copy the partial + // leading cluster of our write request and write it + // back out. + + memcpy( (PBYTE)ClusterRun.GetBuf() + OffsetIntoCluster, + CurrentData, + (UINT) BytesToCopy ); + + if( !ClusterRun.Write() ) { + + DebugPrint( "Could not write partial leading sector.\n" ); + return FALSE; + } + + RemainingRequest -= BytesToCopy; + CurrentData += BytesToCopy; + ByteOffset += BytesToCopy; + + } + + // Now transfer any complete clusters. Because the + // client's buffer may not be suitably aligned, we + // have to cycle these through an intermediate buffer. + + while( RemainingRequest >= ClusterSize ) { + + CurrentVcn = ByteOffset / ClusterSize; + + if( !_ExtentList->QueryLcnFromVcn( CurrentVcn, + &CurrentLcn, + &RunLength ) ) { + + + DebugPrint( "Unable to Query Lcn from Vcn.\n" ); + + return FALSE; + } + + if( RunLength.GetHighPart() != 0 || + RunLength.GetLowPart() > + MaximumClustersToTransfer ) { + + CurrentRunLength = MaximumClustersToTransfer; + + } else { + + CurrentRunLength = RunLength.GetLowPart(); + } + + if( CurrentRunLength * ClusterSize > + RemainingRequest ) { + + CurrentRunLength = RemainingRequest/ClusterSize; + } + + BytesToCopy = CurrentRunLength * ClusterSize; + + if( CurrentLcn == LCN_NOT_PRESENT ) { + + // This portion of the request falls into a + // hole in a sparse attribute, so we have + // to allocate disk space for it and add + // this new extent to the extent list. If + // we can't, the request fails. + + if( Bitmap == NULL || + !Bitmap->AllocateClusters( RecentLcn, + CurrentRunLength, + &CurrentLcn) || + !_ExtentList->AddExtent( CurrentVcn, + CurrentLcn, + 1 ) ) { + + return FALSE; + } + } + + RecentLcn = CurrentLcn; + + if( !IntermediateBuffer.Initialize() || + !ClusterRun.Initialize( &IntermediateBuffer, + _Drive, + CurrentLcn, + _ClusterFactor, + CurrentRunLength ) ) { + + + DebugPrint( "Could not get memory to write user data.\n" ); + return FALSE; + } + + memcpy( IntermediateBuffer.GetBuf(), + CurrentData, + BytesToCopy ); + + if( !ClusterRun.Write() ) { + + DebugPrint( "Could not write complete clusters.\n" ); + return FALSE; + } + + RemainingRequest -= BytesToCopy; + CurrentData += BytesToCopy; + ByteOffset += BytesToCopy; + } + + if( RemainingRequest > 0 ) { + + // OK, we have a partial trailing cluster. Write + // it through the intermediate buffer. Again, + // we have to read the cluster, copy the data, + // and write the cluster back out. + + BytesToCopy = RemainingRequest; + + CurrentVcn = ByteOffset / ClusterSize; + + if( !_ExtentList->QueryLcnFromVcn( CurrentVcn, + &CurrentLcn, + &RunLength ) ) { + + + DebugPrint( "Unable to Query Lcn from Vcn.\n" ); + + return FALSE; + } + + if( CurrentLcn == LCN_NOT_PRESENT ) { + + // This portion of the request falls into a + // hole in a sparse attribute, so we have + // to allocate disk space for it and add + // this new extent to the extent list. If + // we can't, the request fails. + + if( Bitmap == NULL || + !Bitmap->AllocateClusters( RecentLcn, + 1, + &CurrentLcn) || + !_ExtentList->AddExtent( CurrentVcn, + CurrentLcn, + 1 ) ) { + + return FALSE; + } + } + + RecentLcn = CurrentLcn; + + if( !IntermediateBuffer.Initialize() || + !ClusterRun.Initialize( &IntermediateBuffer, + _Drive, + CurrentLcn, + _ClusterFactor, + 1 ) || + !ClusterRun.Read() ) { + + DebugPrint( "Failure getting LCN or intermediat buffer.\n" ); + return FALSE; + } + + // We've read the cluster in question; copy the partial + // leading cluster of our write request and write it + // back out. + + memcpy( ClusterRun.GetBuf(), + CurrentData, + (UINT) BytesToCopy ); + + if( !ClusterRun.Write() ) { + + DebugPrint( "Could not write trailing partial cluster.\n" ); + DebugPrintTrace(("Status: %x\n", _Drive->QueryLastNtStatus())); + DebugPrintTrace(("LCN: %x\n", CurrentLcn.GetLowPart())); + return FALSE; + } + + // Update ByteOffset, since it may be used to check + // _ValidDataLength below. + + ByteOffset += RemainingRequest; + } + } + + } else { + + DebugAbort( "This attribute is neither resident nor nonresident.\n" ); + return FALSE; + } + + if( CompareLT(_ValidDataLength, ByteOffset) ) { + + _ValidDataLength = ByteOffset; + SetStorageModified(); + } + + if( CompareLT(_ValueLength, ByteOffset) ) { + + _ValueLength = ByteOffset; + SetStorageModified(); + } + + *BytesWritten = BytesToWrite; + return TRUE; +} + +BOOLEAN +NTFS_ATTRIBUTE::Fill ( + IN BIG_INT Offset, + IN CHAR FillCharacter + ) +/*++ + +Routine Description: + + This method fills the attribute with the specified character + from the given offset until the end of the attribute. + +Arguments: + + Offset -- Starting offset to begin the fill. + FillCharacter -- Supplies the character that will be written + to every byte of the attribute value. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method will fail if it is used on an attribute which has a size + greater than MAXULONG. + +--*/ +{ + BIG_INT TempBigInt; + + if( Offset >= QueryValueLength() ) { + + // Nothing to do. + // + return TRUE; + } + + // Fill to the end of the attribute--compute the number of + // bytes in the attribute starting at Offset. Make sure + // that the amount to fill fits in a ULONG. + // + TempBigInt = QueryValueLength() - Offset; + + if( TempBigInt.GetHighPart() != 0 ) { + + DebugPrint( "UNTFS: Trying to fill a very large attribute.\n" ); + return FALSE; + } + + return( Fill( Offset, FillCharacter, TempBigInt.GetLowPart() ) ); +} + + +BOOLEAN +NTFS_ATTRIBUTE::Fill ( + IN BIG_INT Offset, + IN CHAR FillCharacter, + IN ULONG NumberOfBytes + ) +/*++ + +Routine Description: + + This method fills the attribute with the specified character + from the given offset for the specified number of bytes. + +Arguments: + + Offset -- Starting offset to begin the fill. + FillCharacter -- Supplies the character that will be written + to every byte of the attribute value. + NumberOfBytes -- Number of bytes to fill. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method will fail if it is used on an attribute which has a size + greater than MAXULONG. + +--*/ +{ + PVOID FillBuffer=NULL; + ULONG BytesRemaining, FillBufferSize, BytesToWrite, BytesWritten; + BOOLEAN Result; + + CONST ULONG MaximumBufferSize = 0x10000; + + if( Offset > QueryValueLength() ) { + + DebugPrint( "UNTFS: Filling an attribute starting past end.\n" ); + return TRUE; + } + + // Get a buffer to fill with the fill character. Start out by + // requesting the full amount; if we can't get it, keep asking + // for smaller amounts. + // + BytesRemaining = NumberOfBytes; + FillBufferSize = min( BytesRemaining, MaximumBufferSize ); + + while( FillBufferSize > 0 && + (FillBuffer = MALLOC( FillBufferSize )) == NULL ) { + + FillBufferSize /= 2; + } + + // If we couldn't get a buffer, fail. + // + if( FillBufferSize == 0 || FillBuffer == NULL ) { + + return FALSE; + } + + // Fill the buffer with the fill character. + // + memset( FillBuffer, + FillCharacter, + FillBufferSize ); + + // Chug through the attribute, writing each chunk until we hit + // a failure or reach the end. + // + Result = TRUE; + + while( BytesRemaining > 0 && Result ) { + + // Write the lesser of our buffer size or the remainder + // of the attribute. Note that we pass NULL for the + // bitmap parameter to Write, since this write should + // not affect the allocated length of the buffer. + // + BytesToWrite = min( BytesRemaining, FillBufferSize ); + + if( !Write( FillBuffer, + Offset, + BytesToWrite, + &BytesWritten, + NULL ) || + BytesWritten != BytesToWrite ) { + + DebugPrintTrace(("Write failed in NTFS_ATTRIBUTE::Fill with status %x\n", + GetDrive()->QueryLastNtStatus())); + Result = FALSE; + } + + Offset += BytesToWrite; + BytesRemaining -= BytesToWrite; + } + + FREE( FillBuffer ); + return Result; +} + + + +BOOLEAN +NTFS_ATTRIBUTE::RecoverAttribute( + IN OUT PNTFS_BITMAP VolumeBitmap, + IN OUT PNUMBER_SET BadClusters, + OUT PBIG_INT BytesRecovered + ) +/*++ + +Routine Description: + + This method recovers an attribute. Recovery consists of reading each + cluster in the attribute value, and replacing it with a cluster full + of zeroes if it is unreadable. + +Arguments: + + VolumeBitmap -- supplies the volume bitmap. + BadClusters -- receives the bad clusters identified by this method. + BytesRecovered -- receives the number of bytes recovered (not + mapped out). This parameter may be NULL, in + which case this information is not returned. + Note that if the method returns FALSE, this + parameter's contents will be undefined. + +Notes: + + This method should not be called for any system-defined attribute + other than $DATA. + + Recover for resident attributes is trivial. + +--*/ +{ + HMEM MultiClusterMem, SingleClusterMem; + NTFS_CLUSTER_RUN MultiClusterRun, SingleClusterRun; + VCN StartingVcn, ClustersAttempted, BadVcn; + LCN StartingLcn; + BIG_INT RunLength, dVcn; + ULONG CurrentRunLength; + ULONG ExtentNumber, i, j; + BOOLEAN FoundBad; + LCN NewLcn; + BIG_INT OldOffset; + ULONG ClusterSize; + ULONG MaxClusters, Take; + BOOLEAN AllHoles = TRUE; + VCN LastStartingVcn; + BIG_INT LastRunLength; + + // Compressed attributes receive special handling. + // + if (IsCompressed() && !IsResident()) { + + return RecoverCompressedAttribute( VolumeBitmap, BadClusters, + BytesRecovered ); + } + + if( _ExtentList == NULL ) { + + // The attribute is resident--Recover is a no-op. + // + if( BytesRecovered != NULL ) { + + *BytesRecovered = QueryValueLength(); + } + + return TRUE; + } + + ClusterSize = QueryClusterFactor() * GetDrive()->QuerySectorSize(); + MaxClusters = 0x10000/ClusterSize; + + if( !MultiClusterMem.Initialize() || + !MultiClusterRun.Initialize( &MultiClusterMem, + GetDrive(), + 0, + QueryClusterFactor(), + MaxClusters ) || + !SingleClusterMem.Initialize() || + !SingleClusterRun.Initialize( &SingleClusterMem, + GetDrive(), + 0, + QueryClusterFactor(), + 1 ) ) { + // insufficient memory. + + return FALSE; + } + + + // Initialize the counters. + // + ExtentNumber = 0; + ClustersAttempted = 0; + + if( BytesRecovered != NULL ) { + + *BytesRecovered = 0; + } + + while( _ExtentList->QueryExtent( ExtentNumber, + &StartingVcn, + &StartingLcn, + &RunLength ) ) { + + if( RunLength.GetHighPart() != 0 ) { + + DebugPrint( "NTFS_ATTRIBUTE::Recover--RunLength > Max ULONG )\n" ); + return FALSE; + } + + if (StartingLcn == LCN_NOT_PRESENT) { + ExtentNumber += 1; + continue; + } + + AllHoles = FALSE; + LastStartingVcn = StartingVcn; + LastRunLength = RunLength; + + // Read the extent in chunks until we get a bad sector + // (read failure) or run out. + // + CurrentRunLength = RunLength.GetLowPart(); + + FoundBad = FALSE; + + Take = MaxClusters; + for( i = 0; i < CurrentRunLength && !FoundBad; i += Take ) { + + Take = min(MaxClusters, CurrentRunLength - i); + + MultiClusterRun.Initialize( &MultiClusterMem, + GetDrive(), + StartingLcn + i, + QueryClusterFactor(), + Take ); + + if( MultiClusterRun.Read() ) { + + // This whole run of clusters is good. If this + // range of VCNs has not already been attempted, + // update the count of bytes recovered. + // + if( BytesRecovered && + StartingVcn + i + Take > ClustersAttempted ) { + + dVcn = StartingVcn + i + Take - ClustersAttempted; + OldOffset = ClustersAttempted * ClusterSize; + + ClustersAttempted += dVcn; + + if( OldOffset + dVcn * ClusterSize < QueryValueLength() ) { + + *BytesRecovered += dVcn * ClusterSize; + + } else if( OldOffset < QueryValueLength() ) { + + *BytesRecovered += QueryValueLength() - OldOffset; + } + } + + } else { + + // Check each of the clusters individually. + // + for( j = 0; j < Take && !FoundBad; j++ ) { + + SingleClusterRun.Relocate( StartingLcn + i + j ); + + if( SingleClusterRun.Read() ) { + + // This cluster is good. Update the total + // of bytes recovered. + // + if( BytesRecovered && + StartingVcn + i + j + 1 > ClustersAttempted ) { + + OldOffset = ClustersAttempted * ClusterSize; + ClustersAttempted += 1; + + if( OldOffset+ClusterSize < QueryValueLength() ) { + + *BytesRecovered += ClusterSize; + + } else if( OldOffset < QueryValueLength() ) { + + *BytesRecovered += QueryValueLength() - + OldOffset; + } + } + + } else { + + // Found a bad cluster. Allocate a replacement + // for it, fill the replacement with zeroes, and + // splinter the extent. Note that we don't check + // the return value of the write; instead, on the + // next iteration, we'll check this VCN again. + // + FoundBad = TRUE; + BadVcn = StartingVcn + i + j; + + if( BytesRecovered && + ClustersAttempted < BadVcn + 1 ) { + + ClustersAttempted = BadVcn + 1; + } + + if( !VolumeBitmap->AllocateClusters( StartingLcn, + 1, + &NewLcn) ) { + + return FALSE; + } + + SingleClusterRun.Relocate( NewLcn ); + + memset( SingleClusterMem.GetBuf(), + '\0', + GetDrive()->QuerySectorSize() * + QueryClusterFactor() ); + + SingleClusterRun.Write(); + + _ExtentList->DeleteExtent( ExtentNumber ); + + if( ( i + j > 0 && + !AddExtent( StartingVcn, + StartingLcn, + i + j ) ) || + !AddExtent( BadVcn, NewLcn, 1 ) || + ( i + j + 1 < CurrentRunLength && + !AddExtent( StartingVcn + i + j + 1, + StartingLcn + i + j + 1, + CurrentRunLength - (i + j + 1) ) ) ) { + + DebugPrint( "RECOVER: couldn't splinter extent." ); + return FALSE; + } + + // Add the bad cluster to the list of identified + // bad clusters, and remember that the attribute's + // storage has been modified. + // + if( !BadClusters->Add( StartingLcn + i + j ) ) { + + return FALSE; + } + + SetStorageModified(); + } + } + } + } + + // If we processed this entire extent without finding any bad + // sectors, update the count of bytes recovered and go on to + // the next one. Otherwise, try this one again. + // + if( !FoundBad ) { + + ExtentNumber += 1; + } + } + + if (BytesRecovered) { + // + // fix up BytesRecovered for sparse looking (including encrypted) file + // + if (AllHoles) { + DebugAssert(*BytesRecovered == 0); + *BytesRecovered = QueryValueLength(); + } else { + OldOffset = (LastStartingVcn + LastRunLength)*ClusterSize; + if (OldOffset < QueryValueLength()) { + // + // Hole at the end of the extent list + // + *BytesRecovered += (QueryValueLength() - OldOffset); + } + } + } + + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE::MarkAsAllocated( + IN OUT PNTFS_BITMAP VolumeBitmap + ) CONST +/*++ + +Routine Description: + + This routine allocated the space taken by this attribute in the + given Volume Bitmap. If any of the space taken by this attribute is + beyond the range of the given bitmap then this routine will fail + without allocating any new space in the bitmap. + + This routine allocates the space in the bitmap regardless of whether + or not this space is already allocated in the bitmap. + +Arguments: + + VolumeBitmap - Supplies the bitmap which to mark the allocation. + +Return Value: + + FALSE - The space requested is beyond the natural range of the + given bitmap. + TRUE - Success. + +--*/ +{ + ULONG num_extents; + ULONG i; + VCN next_vcn; + LCN current_lcn; + BIG_INT run_length; + + + DebugAssert(VolumeBitmap); + + + // If the attribute is resident then we have already succeeded. + + if (!_ExtentList) { + return TRUE; + } + + + num_extents = _ExtentList->QueryNumberOfExtents(); + + for (i = 0; i < num_extents; i++) { + + if (!_ExtentList->QueryExtent(i, &next_vcn, ¤t_lcn, + &run_length)) { + + DebugAbort("Could not query extent"); + return FALSE; + } + if (LCN_NOT_PRESENT == current_lcn) { + continue; + } + + if (!VolumeBitmap->IsInRange(current_lcn, run_length)) { + return FALSE; + } + } + + for (i = 0; i < num_extents; i++) { + + if (!_ExtentList->QueryExtent(i, &next_vcn, ¤t_lcn, + &run_length)) { + + DebugAbort("Could not query extent"); + return FALSE; + } + if (LCN_NOT_PRESENT == current_lcn) { + return FALSE; + } + + VolumeBitmap->SetAllocated(current_lcn, run_length); + } + + return TRUE; +} + + +BOOLEAN +AccountForBadClusters( + IN LCN Lcn, + IN BIG_INT RunLength, + IN OUT PNTFS_BITMAP VolumeBitmap, + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + OUT PBOOLEAN SomeWereBad, + IN OUT PNUMBER_SET BadClusters + ) +/*++ + +Routine Description: + + This routine read through the given run of clusters. The clusters + that are bad are added to the list of BadClusters and marked + as allocated in the volume bitmap. The clusters which are good are + marked free in the volume bitmap. + +Arguments: + + Lcn - Supplies the first logical cluster number. + RunLength - Supplies the length of the run. + VolumeBitmap - Supplies the volume bitmap. + Drive - Supplies the drive. + ClusterFactor - Supplies the cluster factor. + SomeWereBad - Returns whether or not any clusters were bad. + BadClusters - Supplies the list of bad volume clusters. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + HMEM hmem; + NTFS_CLUSTER_RUN clusrun; + BIG_INT i; + LCN sup; + + if (!hmem.Initialize()) { + return FALSE; + } + + *SomeWereBad = FALSE; + + sup = Lcn + RunLength; + for (i = Lcn; i < sup; i += 1) { + + if (!clusrun.Initialize(&hmem, Drive, i, ClusterFactor, 1)) { + return FALSE; + } + + if (clusrun.Read()) { + + VolumeBitmap->SetFree(i, 1); + + } else { + + VolumeBitmap->SetAllocated(i, 1); + + *SomeWereBad = TRUE; + + if (!BadClusters->Add(i)) { + return FALSE; + } + } + } + + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE::Hotfix( + IN VCN Vcn, + IN BIG_INT RunLength, + IN OUT PNTFS_BITMAP VolumeBitmap, + IN OUT PNUMBER_SET BadClusters, + IN BOOLEAN Contiguous + ) +/*++ + +Routine Description: + + This routine replaces the cluster run specified by 'Vcn' and + 'RunLength' with new readable clusters allocated from + 'VolumeBitmap'. + + If 'Contiguous' is TRUE then the readable clusters will be allocated + from the bitmap in one contiguous run. + + If 'BadClusters' is specified then the logical cluster numbers + of all of the bad clusters detected by this routine will be added + to this list. This does not include the run to hotfix. + +Arguments: + + Vcn - Supplies the first vcn of the run to hotfix. + RunLength - Supplies the number of clusters to hotfix. + VolumeBitmap - Supplies a valid volume bitmap from which to + allocate new clusters. + BadClusters - Supplies a list to which to add the bad clusters + of the volume. + Contiguous - Supplies whether or not the new clusters must be + contiguous. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + BIG_INT alloc_size; + BIG_INT total_so_far; + LCN first_lcn; + BOOLEAN some_were_bad; + NTFS_EXTENT_LIST new_stuff; + NTFS_EXTENT_LIST backup_copy; + VCN i; + VCN vcn; + LCN lcn; + BIG_INT run_length; + ULONG j; + ULONG num_extents; + + + DebugAssert(RunLength != 0); + + if (!_ExtentList) { + return FALSE; + } + + if (!new_stuff.Initialize(0, 0) || + !backup_copy.Initialize(_ExtentList)) { + + return FALSE; + } + + // Allocate the space necessary on the bitmap making sure that + // the sectors are good. + + alloc_size = RunLength; + total_so_far = 0; + + while (total_so_far < RunLength) { + + if (VolumeBitmap->AllocateClusters(0, alloc_size, &first_lcn)) { + + if (!AccountForBadClusters(first_lcn, alloc_size, + VolumeBitmap, GetDrive(), + QueryClusterFactor(), + &some_were_bad, + BadClusters)) { + + return FALSE; + } + + if (some_were_bad) { + continue; + } + + VolumeBitmap->SetAllocated(first_lcn, alloc_size); + + if (!new_stuff.AddExtent(Vcn + total_so_far, + first_lcn, + alloc_size)) { + + return FALSE; + } + + total_so_far += alloc_size; + + alloc_size = min(alloc_size, RunLength - total_so_far); + + } else { + + if (Contiguous || alloc_size == 1) { + return FALSE; + } + + alloc_size = alloc_size/2; + } + } + + + // Delete the given range of VCN's from the extent list. + + if (!_ExtentList->DeleteRange(Vcn, RunLength)) { + return FALSE; + } + + + // Now insert the extents into the extent list. + + num_extents = new_stuff.QueryNumberOfExtents(); + + for (j = 0; j < num_extents; j++) { + + if (!new_stuff.QueryExtent(j, &vcn, &lcn, &run_length) || + !_ExtentList->AddExtent(vcn, lcn, run_length)) { + + _ExtentList->Initialize(&backup_copy); + return FALSE; + } + } + + SetStorageModified(); + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE::ReplaceVcns( + IN VCN StartingVcn, + IN LCN NewLcn, + IN BIG_INT NumberOfClusters + ) +/*++ + +Routine Description: + + This routine replaces the VCNs specified by 'StartingVcn' and + 'NumberOfClusters' with the contiguous run that starts at + 'NewLcn'. + +Arguments: + + StartingVcn - Supplies the starting vcn to replace. + NewLcn - Supplies a run of 'NumberOfClusters' clusters. + NumberOfClusters - Supplies the number of clusters to replace. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + if (!_ExtentList->DeleteRange(StartingVcn, NumberOfClusters)) { + return FALSE; + } + + if (!_ExtentList->AddExtent(StartingVcn, NewLcn, NumberOfClusters)) { + return FALSE; + } + + return TRUE; +} + + +BOOLEAN +operator==( + IN RCNTFS_ATTRIBUTE Left, + IN RCNTFS_ATTRIBUTE Right + ) +/*++ + +Routine Description: + + This routine computes whether or not the two given attributes are + equal. + +Arguments: + + Left - Supplies the left argument. + Right - Supplies the right argument. + +Return Value: + + FALSE - The given attributes are not equal. + TRUE - The given attributes are equal. + +--*/ +{ + VCN left_vcn, right_vcn; + LCN left_lcn, right_lcn; + BIG_INT left_length, right_length; + ULONG num_extents; + PNTFS_EXTENT_LIST left_list; + PNTFS_EXTENT_LIST right_list; + ULONG i; + + if (Left._ClusterFactor != Right._ClusterFactor || + Left._Type != Right._Type || + Left._Name.Strcmp(&Right._Name) || + Left._Flags != Right._Flags || + Left._FormCode != Right._FormCode || + Left._ValueLength != Right._ValueLength || + Left._ValidDataLength != Right._ValidDataLength || + Left._ResidentFlags != Right._ResidentFlags) { + + return FALSE; + } + + if (Left._ResidentData) { + + if (!Right._ResidentData) { + return FALSE; + } + + return !memcmp(Left._ResidentData, + Right._ResidentData, + (UINT) Left._ValueLength.GetLowPart()); + } + + DebugAssert(Left._ExtentList); + DebugAssert(Right._ExtentList); + + left_list = Left._ExtentList; + right_list = Right._ExtentList; + + if (left_list->QueryNumberOfExtents() != + right_list->QueryNumberOfExtents() || + left_list->QueryLowestVcn() != right_list->QueryLowestVcn() || + left_list->QueryNextVcn() != right_list->QueryNextVcn()) { + + return FALSE; + } + + num_extents = left_list->QueryNumberOfExtents(); + + for (i = 0; i < num_extents; i += 1) { + + if (!left_list->QueryExtent(i, &left_vcn, &left_lcn, &left_length) || + !right_list->QueryExtent(i, &right_vcn, &right_lcn, &right_length) || + left_vcn != right_vcn || + left_lcn != right_lcn || + left_length != right_length) { + + return FALSE; + } + } + + return TRUE; +} + + +BIG_INT +NTFS_ATTRIBUTE::QueryClustersAllocated( + ) CONST +/*++ + +Routine Description: + + This routine computes the number of clusters allocated for this + attribute. + +Arguments: + + None. + +Return Value: + + The number of clusters allocated by this attribute. + +--*/ +{ + BIG_INT r; + + if (_ExtentList) { + r = _ExtentList->QueryClustersAllocated(); + } else { + r = 0; + } + + return r; +} + + + +BOOLEAN +NTFS_ATTRIBUTE::InsertMftDataIntoFile ( + IN OUT PNTFS_FILE_RECORD_SEGMENT BaseFileRecordSegment, + IN OUT PNTFS_BITMAP Bitmap OPTIONAL, + IN BOOLEAN BeConservative + ) +/*++ + +Routine Description: + + This method inserts the MFT Data attribute into a File Record + Segment (presumably FRS 0). It is a private worker method for + InsertIntoFile. + +Arguments: + + FileRecordSegment -- Supplies the File Record Segment into + which the attribute will jam itself. + Bitmap -- Supplies the volume bitmap. + BeConservative -- Supplies a flag which indicates, if TRUE, + that the attribute should try to leave free + space in the File Record Segments (to leave + room for changes due to hotfixing). If this + flag is FALSE, the attribute will make each + attribute record as large as it can. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + NTFS_ATTRIBUTE_RECORD AttributeRecord; + PVOID AttributeRecordData; + BOOLEAN Result; + ULONG MaxSize; + ULONG MaxExtentsSize, CurrentMaxExtentsSize; + NTFS_EXTENT_LIST source; + NTFS_EXTENT_LIST result; + NTFS_EXTENT_LIST remainder; + BOOLEAN FirstChunkInserted = FALSE; + BOOLEAN Completed = FALSE; + + // Allocate a buffer to hold attribute records. If we're being + // conservative, reduce the size of the maximum record by 1/8. + // + MaxSize = BaseFileRecordSegment->QueryMaximumAttributeRecordSize(); + + if( BeConservative ) { + + // Reduce the maximum record size by 1/8 of the FRS size, + // to allow for hotfixing and other changes. + // + MaxSize -= BaseFileRecordSegment->QuerySize()/8; + } + + if( (AttributeRecordData = MALLOC( (UINT) MaxSize )) == NULL ) { + + return FALSE; + } + + + // The MFT data attribute must be resident. + // + if( _ResidentData != NULL ) { + + if( !AttributeRecord.Initialize( GetDrive(), AttributeRecordData, MaxSize ) ) { + + FREE( AttributeRecordData ); + return FALSE; + } + + // The attribute value is resident. Package up a resident + // attribute record. + + Result = AttributeRecord. + CreateResidentRecord( _ResidentData, + _ValueLength.GetLowPart(), + _Type, + &_Name, + _Flags, + _ResidentFlags ); + + // + // Check to see if there is enough space to Create a resident record + // + + if (Result) { + Result = BaseFileRecordSegment-> + InsertAttributeRecord( &AttributeRecord ); + + FREE( AttributeRecordData ); + return Result; + } else { + // Not enough space to do so, make attribute record non-resident + if (IsIndexed() || !Bitmap || !MakeNonresident(Bitmap)) { + FREE( AttributeRecordData ); + return FALSE; + } + } + } + + // Compute the maximum number of bytes in an extent list. + // + MaxExtentsSize = MaxSize - SIZE_OF_NONRESIDENT_HEADER; + if (_Flags & (ATTRIBUTE_FLAG_COMPRESSION_MASK | + ATTRIBUTE_FLAG_SPARSE)) { + MaxExtentsSize -= sizeof(BIG_INT); + } + MaxExtentsSize -= QuadAlign(_Name.QueryChCount()); + + // The first chunk of the MFT's DATA attribute gets + // special treatment, since it has to fit into the + // Base FRS. If our first attempt doesn't fit, we + // keep whittling it down until it does or until we + // run out of possibilities. + // + CurrentMaxExtentsSize = MaxExtentsSize; + + Result = AttributeRecord.Initialize( GetDrive(), AttributeRecordData, MaxSize ); + + while( Result && !FirstChunkInserted ) { + + // Partition the extent list. + // + if( PartitionExtentList( _ExtentList, + CurrentMaxExtentsSize, + &result, + &remainder ) ) { + + if( !AttributeRecord. + CreateNonresidentRecord( &result, + QueryAllocatedLength(), + _ValueLength, + _ValidDataLength, + _Type, + &_Name, + _Flags, + _CompressionUnit ) || + !BaseFileRecordSegment-> + InsertAttributeRecord( &AttributeRecord ) ) { + + // This partition didn't work. Try a smaller one. + // + CurrentMaxExtentsSize /= 2; + + if( CurrentMaxExtentsSize == 0 ) { + + Result = FALSE; + } + + } else { + + // Successfully inserted first chunk. Set up + // source to continue inserting the remaining + // chunks. + // + FirstChunkInserted = TRUE; + + if (remainder.IsEmpty()) { + + Completed = TRUE; + + } else { + + Result = source.Initialize(&remainder); + } + } + + } else { + + Result = FALSE; + } + } + + + + while (Result && !Completed) { + + // Initialize attribute record. + + Result = AttributeRecord.Initialize( GetDrive(), AttributeRecordData, MaxSize ); + + + // Partition extent list into two pieces, the first of which + // can be made into an attribute record. + + Result = Result && + PartitionExtentList(&source, + MaxExtentsSize, + &result, + &remainder); + + + // Create the attribute record. + + Result = Result && + AttributeRecord. + CreateNonresidentRecord( &result, + QueryAllocatedLength(), + _ValueLength, + _ValidDataLength, + _Type, + &_Name, + _Flags, + _CompressionUnit ); + + + // If we were able to package it up, then give the attribute + // record to the File Record Segment. + + Result = Result && + BaseFileRecordSegment-> + InsertAttributeRecord( &AttributeRecord ); + + + // If all of the extents fit in the last record then we are done. + + if (remainder.IsEmpty()) { + + Completed = TRUE; + + } else { + + Result = Result && + source.Initialize(&remainder); + } + } + + + ResetStorageModified(); + FREE( AttributeRecordData ); + return Result; +} + + +BOOLEAN +NTFS_ATTRIBUTE::RecoverCompressedAttribute( + IN OUT PNTFS_BITMAP VolumeBitmap, + IN OUT PNUMBER_SET BadClusters, + OUT PBIG_INT BytesRecovered + ) +/*++ + +Routine Description: + + This method is a private helper function for RecoverAttribute; + it is invoked if the attribute is compressed. Recovery consists + of reading each cluster in the attribute value, and replacing it + with a cluster full of zeroes if it is unreadable. + +Arguments: + + VolumeBitmap -- supplies the volume bitmap. + BadClusters -- receives the bad clusters identified by this method. + BytesRecovered -- receives the number of bytes recovered (not + mapped out). This parameter may be NULL, in + which case this information is not returned. + Note that if the method returns FALSE, this + parameter's contents will be undefined. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + HMEM ClusterMem, OneClusterMem; + NTFS_CLUSTER_RUN ClusterRun, OneCluster; + VCN CurrentVcn, Vcn; + LCN Lcn, lcnStart, lcnEnd; + BIG_INT RunLength, j; + ULONG i; + ULONG BytesPerCluster, ClustersPerCompressionUnit, ClustersRemaining; + ULONG CompressedClusters, ThisRun, UncompressedSize, Holes; + BOOLEAN DiscardThisUnit; + PUCHAR CompressedBuffer, UncompressedBuffer; + NTSTATUS Status; + + + if (IsResident() || !IsCompressed()) { + + DebugPrintTrace(( "UNTFS: RecoverCompressedAttribute called for non-compressed attribute.\n" )); + return FALSE; + } + + if( !OneClusterMem.Initialize() || + !OneCluster.Initialize( &OneClusterMem, + GetDrive(), + 0, + QueryClusterFactor(), + 1 ) ) { + + return FALSE; + } + + if( BytesRecovered ) { + + *BytesRecovered = 0; + } + + ClustersPerCompressionUnit = 1 << QueryCompressionUnit(); + BytesPerCluster = QueryClusterFactor() * GetDrive()->QuerySectorSize(); + + CompressedBuffer = (PUCHAR)MALLOC( ClustersPerCompressionUnit * BytesPerCluster ); + UncompressedBuffer = (PUCHAR)MALLOC( ClustersPerCompressionUnit * BytesPerCluster ); + + if( CompressedBuffer == NULL || UncompressedBuffer == NULL ) { + + CompressedBuffer ? FREE( CompressedBuffer ) : 0; + UncompressedBuffer ? FREE( UncompressedBuffer ) : 0; + return FALSE; + } + + ClustersRemaining = QueryAllocatedLength().GetLowPart()/BytesPerCluster; + CurrentVcn = 0; + + while( ClustersRemaining ) { + + DiscardThisUnit = FALSE; + + if( ClustersRemaining < ClustersPerCompressionUnit ) { + + ClustersPerCompressionUnit = ClustersRemaining; + } + + CompressedClusters = 0; + Holes = 0; + + while( CompressedClusters + Holes < ClustersPerCompressionUnit ) { + + if( !QueryLcnFromVcn( CurrentVcn + CompressedClusters + Holes, + &Lcn, + &RunLength ) ) { + + // No more clusters in this compression unit. + // Check to make sure that the gap covers at + // least the rest of the compression unit. + // + + if( CompressedClusters + Holes < ClustersPerCompressionUnit ) { + + DebugPrintTrace(( "UNTFS: malformed compression unit at VCN 0x%I64x\n", + CurrentVcn.GetLargeInteger() )); + DiscardThisUnit = TRUE; + } + break; + } + + if( Lcn == LCN_NOT_PRESENT ) { + + Holes += RunLength.GetLowPart(); + continue; + } + + if( CompressedClusters + RunLength.GetLowPart() > + ClustersPerCompressionUnit ) { + + ThisRun = ClustersPerCompressionUnit - CompressedClusters; + + } else { + + ThisRun = RunLength.GetLowPart(); + } + + if( !ClusterMem.Initialize() || + !ClusterRun.Initialize( &ClusterMem, + GetDrive(), + Lcn, + QueryClusterFactor(), + ThisRun ) ) { + + FREE( CompressedBuffer ); + FREE( UncompressedBuffer ); + return FALSE; + } + + if( !ClusterRun.Read() ) { + + // There's a bad sector in here. Read the clusters + // one at a time to see which ones are bad. + // + for( i = 0; i < ThisRun; i++ ) { + + OneCluster.Relocate( Lcn + i ); + + if( !OneCluster.Read() ) { + + // This one's bad--add it to the bad block list. + // + BadClusters->Add( Lcn + i ); + } + } + + DebugPrintTrace(( "UNTFS: unreadable compression unit at VCN 0x%I64x\n", + (CurrentVcn + CompressedClusters + Holes).GetLargeInteger() )); + DiscardThisUnit = TRUE; + + } else { + + // Copy this run into the compressed buffer. + // + memcpy( CompressedBuffer + CompressedClusters * BytesPerCluster, + ClusterRun.GetBuf(), + ThisRun * BytesPerCluster ); + + } + + CompressedClusters += ThisRun; + } + + if( !DiscardThisUnit && + CompareLT(CurrentVcn * BytesPerCluster, QueryValidDataLength()) && + CompressedClusters != 0 && + CompressedClusters != ClustersPerCompressionUnit ) { + + // The clusters can all be read--see if they can be + // decompressed. + // + Status = RtlDecompressBuffer( COMPRESSION_FORMAT_LZNT1, + UncompressedBuffer, + ClustersPerCompressionUnit * BytesPerCluster, + CompressedBuffer, + CompressedClusters * BytesPerCluster, + &UncompressedSize ); + + // If the RTL compression routines are not implemented, + // assume that the compressed data is just fine. + // + if( Status != STATUS_NOT_IMPLEMENTED && + Status != STATUS_NOT_SUPPORTED && + !NT_SUCCESS( Status ) ) { + + // Can't decompress it. + // + DebugPrintTrace(( "UNTFS: RtlDecompressBuffer failed--status 0x%x\n", Status )); + DiscardThisUnit = TRUE; + } + } + + if( DiscardThisUnit ) { + + // Replace this compression unit with a gap. + // Walk through all the extents, clipping any + // extent that intersects the discarded unit. + // + SetStorageModified(); + + i = 0; + + while( _ExtentList->QueryExtent( i, + &Vcn, + &Lcn, + &RunLength ) ) { + + if( Lcn == LCN_NOT_PRESENT ) { + + i++; + continue; + } + + if( Vcn >= CurrentVcn + ClustersPerCompressionUnit ) { + + // We're done. + // + break; + } + + if( Vcn + RunLength <= CurrentVcn ) { + + // This run does not overlap the discarded + // segment. Try the next one. + // + i++; + continue; + } + + _ExtentList->DeleteExtent( i ); + + if( Vcn < CurrentVcn) { + + if (!_ExtentList->AddExtent( Vcn, + Lcn, + CurrentVcn - Vcn ) ) { + + FREE( CompressedBuffer ); + FREE( UncompressedBuffer ); + return FALSE; + } + lcnStart = Lcn + (CurrentVcn - Vcn); + } else + lcnStart = Lcn; + + if( Vcn + RunLength > CurrentVcn + ClustersPerCompressionUnit) { + + lcnEnd = Lcn + (CurrentVcn - Vcn) + ClustersPerCompressionUnit; + if (!_ExtentList->AddExtent( CurrentVcn + ClustersPerCompressionUnit, + lcnEnd, + RunLength - (CurrentVcn - Vcn) - + ClustersPerCompressionUnit ) ) { + + FREE( CompressedBuffer ); + FREE( UncompressedBuffer ); + return FALSE; + } + } else + lcnEnd = Lcn + RunLength; + + // Free stuff in the bitmap. + // + for( j = lcnStart; j < lcnEnd; j = j + 1) { + + if( !BadClusters->DoesIntersectSet( j, 1 ) ) { + + // this cluster was not added to the bad clusters, + // it has become free. + // + VolumeBitmap->SetFree( j, 1 ); + } + } + + // Don't advance to the next extent, since we may have + // deleted this entire extent. + } + + } else { + + if( BytesRecovered && + CurrentVcn * BytesPerCluster < QueryValueLength() ) { + + if( CurrentVcn * BytesPerCluster + + ClustersPerCompressionUnit * BytesPerCluster > + QueryValueLength() ) { + + *BytesRecovered += QueryValueLength() - CurrentVcn * BytesPerCluster; + + } else { + + *BytesRecovered += ClustersPerCompressionUnit * BytesPerCluster; + } + } + } + + ClustersRemaining -= ClustersPerCompressionUnit; + CurrentVcn += ClustersPerCompressionUnit; + } + + FREE( CompressedBuffer ); + FREE( UncompressedBuffer ); + return TRUE; +} + +BOOLEAN +NTFS_ATTRIBUTE::IsAllocationZeroed( + OUT PBOOLEAN Error + ) +/*++ + +Routine Description: + + This routine reads the attribute value and checks whether all the + bytes in the allocation are zeros. + +Arguments: + + Error - if this routine returns false, check error to see if it + encountered an error trying to read the attribute. + +Return Value: + + TRUE if all zeroes, FALSE otherwise. + +--*/ +{ + CONST int MaxNumBytesToCheck = 65536; + ULONG chomp_length, bytes_read, disk_bytes; + PUCHAR buf; + ULONG i, j; + BOOLEAN error; + + DebugAssert(QueryValueLength().GetHighPart() == 0); + + if (NULL != Error) { + *Error = FALSE; + } else { + Error = &error; + } + + disk_bytes = QueryValueLength().GetLowPart(); + + if (NULL == (buf = NEW UCHAR[min(MaxNumBytesToCheck, disk_bytes)])) { + + *Error = TRUE; + return FALSE; + } + + for (i = 0; i < disk_bytes; i += MaxNumBytesToCheck) { + + chomp_length = min(MaxNumBytesToCheck, disk_bytes - i); + + if (!Read(buf, i, chomp_length, &bytes_read) || + bytes_read != chomp_length) { + + *Error = TRUE; + DELETE_ARRAY(buf); + return FALSE; + } + + for (j = 0; j < chomp_length; j++) { + + if (buf[j] != 0) { + DELETE_ARRAY(buf); + return FALSE; + } + } + } + + DELETE_ARRAY(buf); + return TRUE; +} + +BOOLEAN +NTFS_ATTRIBUTE::GetNextAllocationOffset( + IN OUT PBIG_INT ByteOffset, + IN OUT PBIG_INT Length + ) +/*++ + +Routine Description: + + This routine locates the next allocation offset and allocation length of a + file. This routine is useful for skipping over holes within the value of + the attribute. + +Arguments: + + ByteOffset - Supplies the location to start searching and returns + the next existing location. + Length - Supplies the length of existing clusters starting at + ByteOffset of the attribute. If -1, the search starts + at the given location; otherwise, it starts at the + location after the given location. On return, it + contains the length of the allocation starting at + ByteOffset. If return value is zero, it means there + is no more allocation starting at that offset. + +Return Value: + + TRUE if all succeeded. + FALSE if failed. + +Notes: + + All offsets and lengths are rounded down to the nearest cluster. + +--*/ +{ + BIG_INT CurrentVcn; + BIG_INT CurrentLcn; + ULONG ClusterSize; + + ClusterSize = _ClusterFactor * _Drive->QuerySectorSize(); + CurrentVcn = *ByteOffset / ClusterSize; + + if (_ResidentData != NULL) { + + // attribute is resident; allocation is contiguous + // return zero as the offset and the attribute's valid + // data length as the length from the offset. + + *ByteOffset = 0; + *Length = _ValidDataLength; + return TRUE; + + } else if (_ExtentList != NULL) { + + if (*Length != -1) + CurrentVcn += *Length/ClusterSize; + + for(;;) { + if (!_ExtentList->QueryLcnFromVcn(CurrentVcn, + &CurrentLcn, + Length)) { + + // the CurrentVcn is out of range already + // so set the length to zero and return + // successful status + + *Length = 0; + break; + } + + if (CurrentLcn == LCN_NOT_PRESENT) { + CurrentVcn += *Length; + } else { + *Length = *Length * ClusterSize; + break; + } + } + + *ByteOffset = CurrentVcn * ClusterSize; + return TRUE; + + } else { + DebugAbort( "This attribute is neither resident nor nonresident.\n" ); + return FALSE; + } +} + +UCHAR +ComputeCompressionUnit( + IN ULONG ClusterSize + ) +{ + if (ClusterSize <= (1024*4)) + return 4; + else if (ClusterSize == (1024*8)) + return 3; + else if (ClusterSize == (1024*16)) + return 2; + else if (ClusterSize == (1024*32)) + return 1; + else if (ClusterSize == (1024*64)) + return 0; + else { + DebugAbort("Unable to determine compression unit value."); + return 0; + } +} + diff --git a/untfs/src/attrlist.cxx b/untfs/src/attrlist.cxx new file mode 100644 index 0000000..3bafd5d --- /dev/null +++ b/untfs/src/attrlist.cxx @@ -0,0 +1,1325 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + attrlist.cxx + +Abstract: + + This module contains the member function definitions for + NTFS_ATTRIBUTE_LIST, which models an ATTRIBUTE_LIST Attribute + in an NTFS File Record Segment. + + If a file has any external attributes (i.e. if it has more than + one File Record Segment), then it will have an ATTRIBUTE_LIST + attribute. This attribute's value consists of a series of + Attribute List Entries, which describe the attribute records + in the file's File Record Segments. There is an entry for each + attribute record attached to the file, including the attribute + records in the base File Record Segment, and in particular + including the attribute records which describe the ATTRIBUTE_LIST + attribute itself. + + An entry in the Attribute List gives the type code and name (if any) + of the attribute, along with the LowestVcn of the attribute record + (zero if the attribute record is Resident) and a segment reference + (which combines an MFT VCN with a sequence number) showing where + the attribute record may be found. + + The entries in the Attribute List are sorted first by attribute + type code and then by name. Note that two attributes can have the + same type code and name only if they can be distinguished by + value. + + +--*/ + + +#include "ulib.hxx" + +#include "untfs.hxx" + +#include "wstring.hxx" + +#include "attrlist.hxx" +#include "attrrec.hxx" +#include "upcase.hxx" + +#include "message.hxx" +#include "ntfsbit.hxx" + + + +DEFINE_CONSTRUCTOR( NTFS_ATTRIBUTE_LIST, NTFS_ATTRIBUTE ); + + +NTFS_ATTRIBUTE_LIST::~NTFS_ATTRIBUTE_LIST( + ) +{ + Destroy(); +} + +VOID +NTFS_ATTRIBUTE_LIST::Construct( + ) +/*++ + +Routine Description: + + Worker function for object construction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _LengthOfList = 0; + _UpcaseTable = NULL; +} + +VOID +NTFS_ATTRIBUTE_LIST::Destroy( + ) +/*++ + +Routine Description: + + Worker function for object destruction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _LengthOfList = 0; + _UpcaseTable = NULL; +} + + +BOOLEAN +NTFS_ATTRIBUTE_LIST::Initialize( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN PNTFS_UPCASE_TABLE UpcaseTable + ) +/*++ + +Routine Description: + + This method initializes an empty attribute list. + +Arguments: + + Drive -- supplies the drive on which the attribute list resides + ClusterFactor -- supplies the cluster factor for that drive + UpcaseTable -- supplies the volume upcase table. + +Return Value: + + TRUE upon successful completion. + +Notes: + + UpcaseTable may be NULL if the client will never compare + named attribute records. + +--*/ +{ + Destroy(); + + if( !_Mem.Initialize() || + !NTFS_ATTRIBUTE::Initialize( Drive, + ClusterFactor, + NULL, + 0, + $ATTRIBUTE_LIST ) ) { + + return FALSE; + } + + _UpcaseTable = UpcaseTable; + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE_LIST::Initialize( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN PCNTFS_ATTRIBUTE_RECORD AttributeRecord, + IN PNTFS_UPCASE_TABLE UpcaseTable + ) +/*++ + +Routine Description: + + This method initializes an attribute list based on an + attribute record. + +Arguments: + + Drive -- supplies the drive on which the attribute list resides + ClusterFactor -- supplies the cluster factor for that drive + AttributeRecord -- supplies the attribute record describing the + attribute list. + UpcaseTable -- supplies the volume upcase table. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method does not read the attribute list. + + UpcaseTable may be NULL if the client will never compare + named attribute records. + +--*/ +{ + Destroy(); + + if( !_Mem.Initialize() || + !NTFS_ATTRIBUTE::Initialize( Drive, + ClusterFactor, + AttributeRecord ) ) { + + return FALSE; + } + + _UpcaseTable = UpcaseTable; + return TRUE; +} + +BOOLEAN +NTFS_ATTRIBUTE_LIST::AddEntry( + IN ATTRIBUTE_TYPE_CODE Type, + IN VCN LowestVcn, + IN PCMFT_SEGMENT_REFERENCE SegmentReference, + IN USHORT InstanceTag, + IN PCWSTRING Name + ) +/*++ + +Routine Description: + + This adds an Attribute List Entry to the list. + +Arguments: + + Type -- supplies the attribute type code of the + attribute record corresponding to this entry + LowestVcn -- supplies the record's LowestVcn + SegmentReference -- supplies the location of the record + InstanceTag -- supplies the record's attribute instance tag. + Name -- supplies the name associated with the + record (NULL if it has no name). + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + ULONG LengthOfNewEntry; + ULONG NewLengthOfList; + PATTRIBUTE_LIST_ENTRY CurrentEntry; + ULONG EntryOffset, NameLength; + + + // Compute the size of the new entry and the new length of the + // list with this entry added. + // + NameLength = ( Name == NULL ) ? 0 : (Name->QueryChCount()); + + LengthOfNewEntry = QuadAlign( sizeof(ATTRIBUTE_LIST_ENTRY) + + NameLength * sizeof(WCHAR) ); + + NewLengthOfList = _LengthOfList + LengthOfNewEntry; + + // If our existing buffer isn't big enough, stretch it to + // hold the new entry. + + if( !_Mem.Resize( NewLengthOfList ) ) { + + return FALSE; + } + + // Scan forward to the point at which the new entry should + // be inserted. + + CurrentEntry = FindEntry( Type, Name, LowestVcn, &EntryOffset ); + + if (CurrentEntry == NULL) + return FALSE; // fail as there is no insertion point + + // Insert a new entry at CurrentEntry. + + memmove( (PBYTE)CurrentEntry + LengthOfNewEntry, + (PVOID)CurrentEntry, + _LengthOfList - EntryOffset ); + + memset( (PVOID)CurrentEntry, '\0', LengthOfNewEntry ); + + _LengthOfList = NewLengthOfList; + + // Fill in the new entry + + CurrentEntry->AttributeTypeCode = Type; + CurrentEntry->RecordLength = (USHORT)LengthOfNewEntry; + CurrentEntry->AttributeNameLength = (UCHAR)NameLength; + CurrentEntry->LowestVcn = LowestVcn; + CurrentEntry->SegmentReference = *SegmentReference; + CurrentEntry->Instance = InstanceTag; + CurrentEntry->AttributeNameOffset = FIELD_OFFSET( ATTRIBUTE_LIST_ENTRY, + AttributeName ); + + if( Name != NULL ) { + + Name->QueryWSTR( 0, + TO_END, + NameFromEntry( CurrentEntry ), + Name->QueryChCount(), + FALSE ); + } + + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE_LIST::DeleteEntry( + IN ULONG EntryIndex + ) +/*++ + +Routine Description: + + This method deletes the nth entry from the list. + +Arguments: + + EntryIndex -- supplies the index of the entry to be deleted + +Return Value: + + TRUE upon successful completion. Note that if there are + not enough entries, this method returns TRUE. + +--*/ +{ + PATTRIBUTE_LIST_ENTRY CurrentEntry; + ULONG CurrentOffset; + ULONG BytesToRemove; + ULONG i; + + + // Scan forward to the requested entry + + CurrentOffset = 0; + CurrentEntry = (PATTRIBUTE_LIST_ENTRY)(_Mem.GetBuf()); + + if( _LengthOfList == 0 ) { + + // The list is empty. + + return TRUE; + } + + + for( i = 0; i < EntryIndex; i++ ) { + + CurrentOffset += CurrentEntry->RecordLength; + + if( CurrentOffset >= _LengthOfList ) { + + // We ran out of entries. + + return TRUE; + } + + CurrentEntry = NextEntry( CurrentEntry ); + } + + + + // Delete the entry. + + BytesToRemove = CurrentEntry->RecordLength; + + DebugAssert( CurrentOffset + BytesToRemove <= _LengthOfList ); + + memmove( CurrentEntry, + (PBYTE)CurrentEntry + BytesToRemove, + _LengthOfList - (CurrentOffset + BytesToRemove) ); + + _LengthOfList -= BytesToRemove; + + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE_LIST::DeleteCurrentEntry( + IN PATTR_LIST_CURR_ENTRY Entry + ) +/*++ + +Routine Description: + + This method deletes the current entry from the list. + +Arguments: + + Entry -- supplies the entry to delete + +Return Value: + + TRUE upon successful completion. Note that if there are + not enough entries, this method returns TRUE. + +--*/ +{ + PATTRIBUTE_LIST_ENTRY CurrentEntry; + ULONG CurrentOffset; + ULONG BytesToRemove; + + if( _LengthOfList == 0 ) { + + // The list is empty. + + return TRUE; + } + + CurrentEntry = Entry->CurrentEntry; + CurrentOffset = Entry->CurrentOffset; + + if( CurrentOffset >= _LengthOfList ) { + + // We ran out of entries. + + return TRUE; + } + + // Delete the entry. + + BytesToRemove = CurrentEntry->RecordLength; + + DebugAssert( CurrentOffset + BytesToRemove <= _LengthOfList ); + + memmove( CurrentEntry, + (PBYTE)CurrentEntry + BytesToRemove, + _LengthOfList - (CurrentOffset + BytesToRemove) ); + + _LengthOfList -= BytesToRemove; + + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE_LIST::DeleteEntries( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name + ) +/*++ + +Routine Description: + + This method deletes all entries in the list which match the input. + This is used when deleting a unique attribute, since all attribute + records for that attribute type-code and name will be removed. + +Arguments: + + Type -- Supplies the attribute type code of the + entry to be deleted. + Name -- Supplies the name of the entry to be deleted; + may be NULL, in which case it is ignored. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + PATTRIBUTE_LIST_ENTRY CurrentEntry; + ULONG EntryOffset; + ULONG BytesToRemove; + ULONG NameLength; + PWSTR NameBuffer = NULL; + + // This is slightly ugly but necessary. NTFS attribute names + // are collated straight, so we can't use the WSTRING name + // comparison, which relies on the current locale. + + if( Name != NULL ) { + + NameLength = Name->QueryChCount(); + NameBuffer = Name->QueryWSTR(); + + if( NameBuffer == NULL ) { + + return FALSE; + } + } + + + // find the first matching entry. + + CurrentEntry = FindEntry( Type, Name, 0, &EntryOffset ); + + if (CurrentEntry) { + while( EntryOffset < _LengthOfList && + CurrentEntry->AttributeTypeCode == Type && + ( Name == NULL || + ( NameLength == CurrentEntry->AttributeNameLength && + memcmp( NameBuffer, + NameFromEntry( CurrentEntry ), + NameLength * sizeof(WCHAR) ) == 0 ) ) ) { + + // This entry matches, so we delete it. Note that instead of + // incrementing CurrentEntry and EntryOffset, we draw the + // succeeding entries down to the current point. + + BytesToRemove = CurrentEntry->RecordLength; + + DebugAssert( EntryOffset + BytesToRemove <= _LengthOfList ); + + memmove( CurrentEntry, + (PBYTE)CurrentEntry + BytesToRemove, + _LengthOfList - (EntryOffset + BytesToRemove) ); + + _LengthOfList -= BytesToRemove; + } + } + + if( NameBuffer != NULL ) { + + FREE( NameBuffer ); + } + + return TRUE; +} + + + +BOOLEAN +NTFS_ATTRIBUTE_LIST::IsInList( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name + ) CONST +/*++ + +Routine Description: + +Arguments: + + Type -- supplies the type code of the attribute in question. + Name -- supplies the name of the attribute in question. + (may be NULL, in which case the attribute has no name.) +Return Value: + + TRUE if there is an entry in the attribute list with this + type code and (if specified) name. + +--*/ +{ + PATTRIBUTE_LIST_ENTRY CurrentEntry; + ULONG EntryOffset, CurrentEntryIndex; + ULONG NameLength; + PCWSTR NameBuffer = NULL; + + // This is slightly ugly but necessary. NTFS attribute names + // are collated straight, so we can't use the WSTRING name + // comparison, which relies on the current locale. + + if( Name != NULL ) { + + NameLength = Name->QueryChCount(); + NameBuffer = Name->GetWSTR(); + + } else { + + NameLength = 0; + } + + + // Find the first entry which matches this type code & name. + + CurrentEntry = FindEntry( Type, Name, 0, + &EntryOffset, &CurrentEntryIndex ); + + if( CurrentEntry == NULL || + EntryOffset >= _LengthOfList || + CurrentEntry->AttributeTypeCode != Type || + NameLength != CurrentEntry->AttributeNameLength || + (NameLength != 0 && + NtfsUpcaseCompare( NameBuffer, + NameLength, + NameFromEntry( CurrentEntry ), + NameLength, + _UpcaseTable, + TRUE) != 0) ) { + + // We've gone too far. There are no matching entries. + + return FALSE; + } + + return TRUE; +} + +#if 0 + +BOOLEAN +NTFS_ATTRIBUTE_LIST::QueryEntry( + IN ULONG EntryIndex, + OUT PATTRIBUTE_TYPE_CODE Type, + OUT PVCN LowestVcn, + OUT PMFT_SEGMENT_REFERENCE SegmentReference, + OUT PUSHORT InstanceTag, + OUT PWSTRING Name + ) CONST +/*++ + +Routine Description: + + This method fetches the nth entry in the list. + +Arguments: + + EntryIndex -- supplies the index into the list of the + entry to fetch + Type -- receives the entry's attribute type code + LowestVcn -- receives the entry's LowestVcn + SegmentReference -- receives the entry's SegmentReference + InstanceTag -- receives the entry's attribute instance tag. + Name -- receives the entry's Name (if any) + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + ULONG i; + PATTRIBUTE_LIST_ENTRY CurrentEntry; + ULONG CurrentOffset; + + CurrentOffset = 0; + CurrentEntry = (PATTRIBUTE_LIST_ENTRY)(((PNTFS_ATTRIBUTE_LIST) this)->_Mem.GetBuf()); + + if( _LengthOfList == 0 ) { + + // The list is empty. + + return FALSE; + } + + + for( i = 0; i < EntryIndex; i++ ) { + + if (CurrentEntry->RecordLength == 0) + return FALSE; + + CurrentOffset += CurrentEntry->RecordLength; + + if( CurrentOffset >= _LengthOfList ) { + + // We ran out of entries. + + return FALSE; + } + + CurrentEntry = NextEntry( CurrentEntry ); + } + + *Type = CurrentEntry->AttributeTypeCode; + *LowestVcn = CurrentEntry->LowestVcn; + *SegmentReference = CurrentEntry->SegmentReference; + *InstanceTag = CurrentEntry->Instance; + + if( !Name->Initialize( NameFromEntry( CurrentEntry ), + CurrentEntry->AttributeNameLength ) ) { + + return FALSE; + } + + return TRUE; +} +#endif + + +BOOLEAN +NTFS_ATTRIBUTE_LIST::QueryNextEntry( + IN OUT PATTR_LIST_CURR_ENTRY CurrEntry, + OUT PATTRIBUTE_TYPE_CODE Type, + OUT PVCN LowestVcn, + OUT PMFT_SEGMENT_REFERENCE SegmentReference, + OUT PUSHORT InstanceTag, + OUT PWSTRING Name + ) CONST +/*++ + +Routine Description: + + This method fetches the next entry in the list. + +Arguments: + + NextEntry -- supplies the pointer to the entry + Type -- receives the entry's attribute type code + LowestVcn -- receives the entry's LowestVcn + SegmentReference -- receives the entry's SegmentReference + InstanceTag -- receives the entry's attribute instance tag. + Name -- receives the entry's Name (if any) + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + PATTRIBUTE_LIST_ENTRY CurrentEntry; + ULONG CurrentOffset; + + DebugPtrAssert(CurrEntry); + + if (CurrEntry->CurrentEntry == NULL) { + + CurrentEntry = (PATTRIBUTE_LIST_ENTRY)(((PNTFS_ATTRIBUTE_LIST) this)->_Mem.GetBuf()); + CurrEntry->CurrentEntry = CurrentEntry; + CurrEntry->CurrentOffset = 0; + + *Type = CurrentEntry->AttributeTypeCode; + *LowestVcn = CurrentEntry->LowestVcn; + *SegmentReference = CurrentEntry->SegmentReference; + *InstanceTag = CurrentEntry->Instance; + + if( !Name->Initialize( NameFromEntry( CurrentEntry ), + CurrentEntry->AttributeNameLength ) ) { + + return FALSE; + } + + return TRUE; + } else { + + CurrentEntry = CurrEntry->CurrentEntry; + CurrentOffset = CurrEntry->CurrentOffset; + } + + if( _LengthOfList == 0 ) { + + // The list is empty. + + return FALSE; + } + + if ( CurrentEntry->RecordLength == 0 ) { + + // something is not right or it's the end + + return FALSE; + } + + CurrentOffset += CurrentEntry->RecordLength; + + if( CurrentOffset >= _LengthOfList ) { + + // it's the end + + return FALSE; + } + + CurrentEntry = NextEntry( CurrentEntry ); + + CurrEntry->CurrentEntry = CurrentEntry; + CurrEntry->CurrentOffset = CurrentOffset; + + *Type = CurrentEntry->AttributeTypeCode; + *LowestVcn = CurrentEntry->LowestVcn; + *SegmentReference = CurrentEntry->SegmentReference; + *InstanceTag = CurrentEntry->Instance; + + if( !Name->Initialize( NameFromEntry( CurrentEntry ), + CurrentEntry->AttributeNameLength ) ) { + + return FALSE; + } + + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE_LIST::QueryEntry( + IN MFT_SEGMENT_REFERENCE SegmentReference, + IN USHORT InstanceTag, + OUT PATTRIBUTE_TYPE_CODE Type, + OUT PVCN LowestVcn, + OUT PWSTRING Name + ) CONST +/*++ + +Routine Description: + + This routine returns the type, lowestvcn, and name of the attribute + list entry with the given segment reference and instance tag. + +Arguments: + + SegmentReference - Supplies the entry's segment reference. + InstanceTag - Supplies the entry's instance tag. + Type - Returns the entry's type code. + LowestVcn - Returns the entry's lowest vcn. + Name - Returns the entry's name. + +Return Value: + + FALSE - An entry with the given segment reference and instance was + not found. + TRUE - Success. + +--*/ +{ + ULONG i; + PATTRIBUTE_LIST_ENTRY CurrentEntry; + ULONG CurrentOffset; + + CurrentOffset = 0; + CurrentEntry = (PATTRIBUTE_LIST_ENTRY)(_Mem.GetBuf()); + + if( _LengthOfList == 0 ) { + + // The list is empty. + + return FALSE; + } + + + for( i = 0; ; i++ ) { + + if (CurrentEntry->Instance == InstanceTag && + CurrentEntry->SegmentReference == SegmentReference) { + + break; + } + + CurrentOffset += CurrentEntry->RecordLength; + + if( CurrentOffset >= _LengthOfList ) { + + // We ran out of entries. + + return FALSE; + } + + CurrentEntry = NextEntry( CurrentEntry ); + } + + *Type = CurrentEntry->AttributeTypeCode; + *LowestVcn = CurrentEntry->LowestVcn; + + if( !Name->Initialize( NameFromEntry( CurrentEntry ), + CurrentEntry->AttributeNameLength ) ) { + + return FALSE; + } + + return TRUE; +} + + + +PCATTRIBUTE_LIST_ENTRY +NTFS_ATTRIBUTE_LIST::GetNextAttributeListEntry( + IN PCATTRIBUTE_LIST_ENTRY CurrentEntry + ) CONST +/*++ + +Routine Description: + + This routine fetches the next attribute list entry structure. + +Arguments: + + CurrentEntry - Supplies the current attribute list entry. + Supplying NULL as the current entry specifies that + you want the first entry in the list. + +Return Value: + + The next attribute list entry or NULL if the current entry + is at the end of the list. + +--*/ +{ + ULONG CurrentOffset; + + if (!_LengthOfList) { + return NULL; + } + + if (!CurrentEntry) { + return (PCATTRIBUTE_LIST_ENTRY) _Mem.GetBuf(); + } + + CurrentOffset = (ULONG)((PCHAR) CurrentEntry - (PCHAR) _Mem.GetBuf()); + + if (CurrentOffset + CurrentEntry->RecordLength >= _LengthOfList) { + return NULL; + } + + return (PCATTRIBUTE_LIST_ENTRY) ((PCHAR) CurrentEntry + CurrentEntry->RecordLength); +} + + + +BOOLEAN +NTFS_ATTRIBUTE_LIST::QueryExternalReference( + IN ATTRIBUTE_TYPE_CODE Type, + OUT PMFT_SEGMENT_REFERENCE SegmentReference, + OUT PULONG EntryIndex, + IN PCWSTRING Name, + IN PVCN DesiredVcn, + OUT PVCN StartingVcn + ) CONST +/*++ + +Routine Description: + + This method fetches an entry from the list based on a type code, + name (optional), and VCN. + +Arguments: + + Type -- supplies the attribute type code to search for. + SegmentReference -- receives the entry's SegmentReference + EntryIndex -- receives the entry's index into the list + Name -- supplies the entry's name. If this pointer + is NULL, attribute names are ignored. + DesiredVcn -- supplies a pointer to the VCN we're interested + in. (Note that this pointer may be NULL if + the caller just wants the first entry for this + type & name.) + StartingVcn -- receives the LowestVcn of the entry found; + if this pointer is NULL, that information is + not returned. + + +Return Value: + + TRUE if a matching entry is found. + +Notes: + + A client who wishes to find all the entries for a particular + attribute can take advantage of the fact that the list is sorted + by type code and name. Thus, the client finds the first matching + entry (using QueryExternalReference), and then queries successive + entries by index until one doesn't match. + +--*/ +{ + PATTRIBUTE_LIST_ENTRY CurrentEntry, PreviousEntry=NULL; + ULONG EntryOffset, CurrentEntryIndex; + ULONG NameLength; + PWSTR NameBuffer = NULL; + + // This is slightly ugly but necessary. NTFS attribute names + // are collated straight, so we can't use the WSTRING name + // comparison, which relies on the current locale. + + if( Name != NULL ) { + + NameLength = Name->QueryChCount(); + NameBuffer = Name->QueryWSTR(); + + if( NameBuffer == NULL ) { + + return FALSE; + } + } + + + // The search algorithm for this method is slightly different than + // the other methods for this class. Instead of the first matching + // entry, we want the last matching entry which has a LowestVcn field + // less than or equal to *DesiredVcn. (If DesiredVcn is NULL, we can + // just return the first entry we find.) + + CurrentEntry = FindEntry( Type, Name, 0, + &EntryOffset, &CurrentEntryIndex ); + + if( CurrentEntry == NULL || + EntryOffset >= _LengthOfList || + CurrentEntry->AttributeTypeCode != Type || + ( Name != NULL && + ( NameLength != CurrentEntry->AttributeNameLength || + memcmp( NameBuffer, + NameFromEntry( CurrentEntry ), + NameLength * sizeof(WCHAR) ) != 0 ) ) || + ( DesiredVcn != NULL && + CurrentEntry->LowestVcn > *DesiredVcn ) ) { + + // We've gone too far. There are no matching entries. + + if( NameBuffer != NULL ) { + + FREE( NameBuffer ); + } + + return FALSE; + } + + if( DesiredVcn != NULL ) { + + // The caller specified a particular VCN, so we have to find the + // entry that contains it. We do this by scanning forward until + // we find an entry that is beyond what we want, and then backing + // up one. Since we passed the test above, we know that the + // loop below will execute at least once, so PreviousEntry is + // sure to get set. + + while( EntryOffset < _LengthOfList && + CurrentEntry->AttributeTypeCode == Type && + ( Name == NULL || + ( NameLength == CurrentEntry->AttributeNameLength && + memcmp( NameBuffer, + NameFromEntry( CurrentEntry ), + NameLength * sizeof(WCHAR) ) == 0 ) ) && + CurrentEntry->LowestVcn <= *DesiredVcn ) { + + PreviousEntry = CurrentEntry; + CurrentEntryIndex += 1; + EntryOffset += CurrentEntry->RecordLength; + CurrentEntry = NextEntry( CurrentEntry ); + } + + // Now back up one, to the entry we really want: + + CurrentEntry = PreviousEntry; + CurrentEntryIndex -= 1; + } + + // Fill in the output parameters. + + memcpy( SegmentReference, + &CurrentEntry->SegmentReference, + sizeof( MFT_SEGMENT_REFERENCE ) ); + + *EntryIndex = CurrentEntryIndex; + + if( StartingVcn != NULL ) { + + *StartingVcn = CurrentEntry->LowestVcn; + } + + if( NameBuffer != NULL ) { + + FREE( NameBuffer ); + } + + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE_LIST::QueryNextAttribute( + IN OUT PATTRIBUTE_TYPE_CODE TypeCode, + IN OUT PWSTRING Name + ) CONST +/*++ + +Routine Description: + + This method determines the type code and name of the first + attribute list which is strictly greater than the supplied + type and name. + +Arguments: + + TypeCode -- supplies the current attribute type code. Receives + the type code of the next attribute. A returned type + code of $END indicates that there are no more attributes. + Name -- supplies the current name. Receives the name of the + next attribute. + + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method is useful for iterating through the non-indexed + attributes of a file, since there can only be one non-indexed + attribute with a given type code and name in the file. However, + it offers no way of dealing with indexed attributes, which may + be distinguished only by value. + +--*/ +{ + PATTRIBUTE_LIST_ENTRY CurrentEntry; + ULONG CurrentEntryOffset, CurrentEntryIndex; + + // Use FindEntry to get to the entry we want. Note that we use + // a LowestVcn of -1, to skip over all matching entries. + + if( (CurrentEntry = FindEntry( *TypeCode, + Name, + -1, + &CurrentEntryOffset, + &CurrentEntryIndex )) == NULL ) { + + // An error occurred searching the list. + return FALSE; + } + + if( CurrentEntryOffset >= _LengthOfList ) { + + // This is the end of the list; there are no more entries. + + *TypeCode = $END; + + return Name->Initialize(""); + } + + // OK, we have the entry we want. Copy its type and name (if any). + + *TypeCode = CurrentEntry->AttributeTypeCode; + + if( !Name->Initialize( NameFromEntry(CurrentEntry), + CurrentEntry->AttributeNameLength ) ) { + + return FALSE; + } + + return TRUE; +} + + + +BOOLEAN +NTFS_ATTRIBUTE_LIST::ReadList( + ) +/*++ + +Routine Description: + + This method reads the list into the object's private buffer. + +Arguments: + + None. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + BIG_INT ValueLength; + ULONG BytesRead; + + // Determine the length of the list. + + QueryValueLength( &ValueLength ); + + DebugAssert( ValueLength.GetHighPart() == 0 ); + + _LengthOfList = ValueLength.GetLowPart(); + + // Initialize our MEM object and use it to get the correct + // amount of memory. + + if( !_Mem.Initialize() || + !_Mem.Acquire( (LONG)_LengthOfList ) ) { + + return FALSE; + } + + // Read the attribute's value into our buffer. + + return( Read( _Mem.GetBuf(), 0, _LengthOfList, &BytesRead) && + BytesRead == _LengthOfList ); +} + + + +PATTRIBUTE_LIST_ENTRY +NTFS_ATTRIBUTE_LIST::FindEntry( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name, + IN VCN LowestVcn, + OUT PULONG EntryOffset, + OUT PULONG EntryIndex + ) CONST +/*++ + +Routine Description: + + This method finds the first entry in the list which matches + its input or, if there is no match, the first entry that + would come after it (i.e. the place it would be if it were there). + +Arguments: + + Type -- supplies the attribute type code to find + Name -- supplies the name to find (may be NULL, in which case it + is ignored) + LowestVcn -- supplies the VCN to find. A value of -1 indicates + we should skip all entries for this type and name. + EntryOffset -- receives the offset into the list of the + returned pointer. (May be NULL, in which case + this value is not returned.) + EntryIndex -- receives the index into the list of the returned + entry. (May be NULL, in which case this value + is not returned. + + +Return Value: + + A pointer to the first entry in the list which matches the input. + If there is no match, this method returns the next entry (i.e. the + point at which a matching entry should be inserted). + + NULL is returned to indicate end of entry. + +--*/ +{ + PATTRIBUTE_LIST_ENTRY CurrentEntry; + ULONG CurrentOffset, CurrentIndex; + ULONG NameLength; + PWSTR NameBuffer = NULL; + + // This is slightly ugly but necessary. NTFS attribute names + // are collated straight, so we can't use the WSTRING name + // comparison, which relies on the current locale. + + if( Name != NULL ) { + + NameLength = Name->QueryChCount(); + NameBuffer = Name->QueryWSTR(); + + if( NameBuffer == NULL ) { + + return NULL; + } + } + + // Start at the beginning of the list. + + CurrentEntry = (PATTRIBUTE_LIST_ENTRY)(((PNTFS_ATTRIBUTE_LIST) this)->_Mem.GetBuf()); + CurrentOffset = 0; + CurrentIndex = 0; + + // Scan forward to the first entry which has a type code + // greater than or equal to the type code we want. + + while( CurrentOffset < _LengthOfList && + Type > CurrentEntry->AttributeTypeCode ) { + + CurrentIndex += 1; + CurrentOffset += CurrentEntry->RecordLength; + CurrentEntry = NextEntry( CurrentEntry ); + } + + + // CurrentEntry now points at the first entry with an attribute + // type code greater than or equal to the one we're seeking. + // Within the group of entries with the same type code, the + // entries are sorted first by name and then by LowestVcn. + + if( Name != NULL ) { + + // The caller specified a name name, so we need to scan + // through the entries with this attribute type code for + // the first entry with a name greater than or equal to + // that name. + + while( CurrentOffset < _LengthOfList && + Type == CurrentEntry->AttributeTypeCode && + NtfsUpcaseCompare( NameBuffer, + NameLength, + NameFromEntry( CurrentEntry ), + CurrentEntry->AttributeNameLength, + _UpcaseTable, + TRUE ) > 0 ) { + + CurrentIndex += 1; + CurrentOffset += CurrentEntry->RecordLength; + CurrentEntry = NextEntry( CurrentEntry ); + } + + // Now scan forward by LowestVcn through the attributes with + // this type code and name. Note that a search value of -1 + // for LowestVcn indicates we should skip all matching entries. + + while( CurrentOffset < _LengthOfList && + Type == CurrentEntry->AttributeTypeCode && + ( NameLength == CurrentEntry->AttributeNameLength && + memcmp( NameBuffer, + NameFromEntry( CurrentEntry ), + NameLength * sizeof(WCHAR) ) == 0 ) && + ( (LowestVcn == -1) || + (LowestVcn > CurrentEntry->LowestVcn) ) ) { + + CurrentIndex += 1; + CurrentOffset += CurrentEntry->RecordLength; + CurrentEntry = NextEntry( CurrentEntry ); + } + + } else { + + // The caller did not specify a name, so we only examine + // entries without names. These come before entries with + // that same attribute type code that have names. Scan + // forward by LowestVcn through the entries that have this + // attribute type code and no name. Note that a search value + // of -1 for LowestVcn indicates that we should skip all matching + // entries. + + while( CurrentOffset < _LengthOfList && + Type == CurrentEntry->AttributeTypeCode && + CurrentEntry->AttributeNameLength == 0 && + ( (LowestVcn == -1) || + (LowestVcn > CurrentEntry->LowestVcn) ) ) { + + CurrentIndex += 1; + CurrentOffset += CurrentEntry->RecordLength; + CurrentEntry = NextEntry( CurrentEntry ); + } + } + + if( EntryOffset != NULL ) { + + *EntryOffset = CurrentOffset; + } + + if( EntryIndex != NULL ) { + + *EntryIndex = CurrentIndex; + } + + if( NameBuffer != NULL ) { + + FREE( NameBuffer ); + } + + return CurrentEntry; +} diff --git a/untfs/src/attrrec.cxx b/untfs/src/attrrec.cxx new file mode 100644 index 0000000..cada40d --- /dev/null +++ b/untfs/src/attrrec.cxx @@ -0,0 +1,911 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + attrrec.hxx + +Abstract: + + This module contains the member function definitions for + NTFS_ATTRIBUTE_RECORD, which models NTFS attribute records. + + An Attribute Record may be a template laid over a chunk of + memory; in that case, it does not own the memory. It may + also be told, upon initialization, to allocate its own memory + and copy the supplied data. In that case, it is also responsible + for freeing that memory. + + Attribute Records are passed between Attributes and File + Record Segments. A File Record Segment can initialize + an Attribute with a list of Attribute Records; when an + Attribute is Set into a File Record Segment, it packages + itself up into Attribute Records and inserts them into + the File Record Segment. + + File Record Segments also use Attribute Records to scan + through their list of attribute records, and to shuffle + them around. + +--*/ + + +#include "ulib.hxx" + +#include "untfs.hxx" + +#include "wstring.hxx" +#include "extents.hxx" +#include "attrrec.hxx" +#include "ntfsbit.hxx" +#include "extents.hxx" +#include "upcase.hxx" + + +DEFINE_CONSTRUCTOR( NTFS_ATTRIBUTE_RECORD, OBJECT ); + + +NTFS_ATTRIBUTE_RECORD::~NTFS_ATTRIBUTE_RECORD( + ) +{ + Destroy(); +} + +VOID +NTFS_ATTRIBUTE_RECORD::Construct( + ) +/*++ + +Routine Description: + + This method is the private worker function for object construction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _Data = NULL; + _MaximumLength = 0; + _IsOwnBuffer = FALSE; + _DisableUnUse = FALSE; + _Drive = NULL; +} + +VOID +NTFS_ATTRIBUTE_RECORD::Destroy( + ) +/*++ + +Routine Description: + + This method is the private worker function for object destruction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + if( _IsOwnBuffer && _Data != NULL ) { + + FREE( _Data ); + } + + _Data = NULL; + _MaximumLength = 0; + _IsOwnBuffer = FALSE; + _Drive = NULL; +} + + +BOOLEAN +NTFS_ATTRIBUTE_RECORD::Initialize( + IN PIO_DP_DRIVE Drive, + IN OUT PVOID Data, + IN ULONG MaximumLength, + IN BOOLEAN MakeCopy + ) +/*++ + +Routine Description: + + This method initializes an NTFS_ATTRIBUTE_RECORD object, + handing it a buffer with attribute record data. The caller + may also ask the object to make a private copy of the data. + +Arguments: + + Drive -- supplies the drive on which the attribute record resides + Data -- supplies a buffer containing the attribute + record data the object will own. + MaximumLength -- supplies the size of the buffer. + MakeCopy -- supplies a flag indicating whether the object + should copy the data to a private buffer. + +Return Value: + + TRUE upon successful completion. + +Notes: + + If MakeCopy is TRUE, then the object must allocate its own + buffer and copy the attribute record data to it, in which + case the object is also responsible for freeing that private + buffer. It that flag is FALSE, then the object will cache a + pointer to the buffer supplied by the client; the client is + responsible for making sure that buffer remains valid for + the lifetime of the NTFS_ATTRIBUTE_RECORD object. + + This object is reinitializable. + +--*/ +{ + Destroy(); + + if( !MakeCopy ) { + + _Data = (PATTRIBUTE_RECORD_HEADER) Data; + _MaximumLength = MaximumLength; + _IsOwnBuffer = FALSE; + _Drive = Drive; + + return TRUE; + + } else { + + if( (_Data = (PATTRIBUTE_RECORD_HEADER) + MALLOC( (UINT) MaximumLength )) == NULL ) { + + Destroy(); + return FALSE; + } + + _MaximumLength = MaximumLength; + _IsOwnBuffer = TRUE; + _Drive = Drive; + memcpy(_Data, Data, (UINT) MaximumLength); + + return TRUE; + } +} + + + +BOOLEAN +NTFS_ATTRIBUTE_RECORD::Initialize( + IN PIO_DP_DRIVE Drive, + IN OUT PVOID Data + ) +/*++ + +Routine Description: + + This version of Initialize takes it's maximum size from the + attribute record. + +Arguments: + + Drive - supplies the drive on which the attribute record resides + Data - supplies a buffer containing the attribute + record data. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + Destroy(); + + _Data = (PATTRIBUTE_RECORD_HEADER) Data; + _MaximumLength = _Data->RecordLength; + _IsOwnBuffer = FALSE; + _Drive = Drive; + + return TRUE; +} + +BOOLEAN +NTFS_ATTRIBUTE_RECORD::CreateResidentRecord( + IN PCVOID Value, + IN ULONG ValueLength, + IN ATTRIBUTE_TYPE_CODE TypeCode, + IN PCWSTRING Name, + IN USHORT Flags, + IN UCHAR ResidentFlags + ) +/*++ + +Routine Description: + + This method formats the object's buffer with a resident + attribute record. + +Arguments: + + Value -- supplies the attribute value + ValueLength -- supplies the length of the value + TypeCode -- supplies the attribute type code + Name -- supplies the name of the attribute + (may be NULL) + Flags -- supplies the attribute's flags. + ResidentFlags -- supplies the attribute's resident flags + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + // Clear the memory first. + memset(_Data, 0, (UINT) _MaximumLength); + + // We will arrange the attribute in the following order: + // Attribute Record Header + // Name (if any) + // Value + + if( _MaximumLength < SIZE_OF_RESIDENT_HEADER ) { + + DebugAbort( "Create: buffer is too small.\n" ); + return FALSE; + } + + _Data->TypeCode = TypeCode; + _Data->FormCode = RESIDENT_FORM; + _Data->Flags = Flags; + + if( Name != NULL ) { + + _Data->NameLength = (UCHAR) Name->QueryChCount(); + _Data->NameOffset = QuadAlign(SIZE_OF_RESIDENT_HEADER); + + // + // The structure should be quad aligned already. This check is just in case. + // + DebugAssert(QuadAlign(SIZE_OF_RESIDENT_HEADER) == SIZE_OF_RESIDENT_HEADER); + + _Data->Form.Resident.ValueOffset = + QuadAlign( _Data->NameOffset + + _Data->NameLength * sizeof( WCHAR ) ); + + } else { + + _Data->NameLength = 0; + _Data->NameOffset = 0; + + _Data->Form.Resident.ValueOffset = + QuadAlign(SIZE_OF_RESIDENT_HEADER); + } + + _Data->Form.Resident.ValueLength = ValueLength; + _Data->Form.Resident.ResidentFlags = ResidentFlags; + + _Data->RecordLength = + QuadAlign(_Data->Form.Resident.ValueOffset + ValueLength ); + + if( _Data->RecordLength > _MaximumLength ) { + + return FALSE; + } + + // Now that we're sure there's room, copy the name (if any) + // and the value into their respective places. + + if( Name != NULL ) { + + Name->QueryWSTR( 0, + _Data->NameLength, + (PWSTR)((PBYTE)_Data + _Data->NameOffset), + _Data->NameLength, + FALSE ); + } + + memcpy( (PBYTE)_Data + _Data->Form.Resident.ValueOffset, + Value, + (UINT) ValueLength ); + + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE_RECORD::CreateNonresidentRecord( + IN PCNTFS_EXTENT_LIST Extents, + IN BIG_INT AllocatedLength, + IN BIG_INT ActualLength, + IN BIG_INT ValidLength, + IN ATTRIBUTE_TYPE_CODE TypeCode, + IN PCWSTRING Name, + IN USHORT Flags, + IN USHORT CompressionUnit, + IN ULONG ClusterSize + ) +/*++ + +Routine Description: + + This method formats the attribute record to hold a nonresident + attribute. + +Arguments: + + Extents -- supplies an extent list describing the + attribute value's disk storage. + AllocatedLength -- supplies the allocated length of the value + ActualLength -- supplies the actual length of the value + ValidLength -- supplies the valid length of the value + TypeCode -- supplies the attribute type code + Name -- supplies the name of the attribute + (may be NULL) + Flags -- supplies the attribute's flags. + CompressionUnit -- supplies the log in base 2 of the number of + clusters per compression unit. + +--*/ +{ + ULONG MappingPairsLength; + VCN NextVcn, HighestVcn; + USHORT sizeOfNonResidentHeader = SIZE_OF_NONRESIDENT_HEADER; + + if (Flags & (ATTRIBUTE_FLAG_COMPRESSION_MASK | + ATTRIBUTE_FLAG_SPARSE)) { + sizeOfNonResidentHeader += sizeof(BIG_INT); // TotalAllocated field + } + + // Clear the memory first. + memset(_Data, 0, (UINT) _MaximumLength); + + // We will arrange the attribute in the following order: + // Attribute Record Header + // Name (if any) + // Compressed Mapping Pairs + + if( _MaximumLength < sizeOfNonResidentHeader ) { + + DebugAbort( "Create: buffer is too small.\n" ); + return FALSE; + } + + _Data->TypeCode = TypeCode; + _Data->FormCode = NONRESIDENT_FORM; + _Data->Flags = Flags; + + if( Name != NULL ) { + + _Data->NameLength = (UCHAR) Name->QueryChCount(); + _Data->NameOffset = QuadAlign(sizeOfNonResidentHeader); + + // + // The structure should be quad aligned already. This check is just in case. + // + DebugAssert(QuadAlign(sizeOfNonResidentHeader) == sizeOfNonResidentHeader); + + _Data->Form.Nonresident.MappingPairsOffset = + (USHORT)QuadAlign( _Data->NameOffset + + _Data->NameLength * sizeof( WCHAR ) ); + + } else { + + _Data->NameLength = 0; + _Data->NameOffset = 0; + + _Data->Form.Nonresident.MappingPairsOffset = + (USHORT)QuadAlign(sizeOfNonResidentHeader); + } + + _Data->Form.Nonresident.CompressionUnit = (UCHAR)CompressionUnit; + + _Data->Form.Nonresident.AllocatedLength = + AllocatedLength.GetLargeInteger(); + + if (Flags & (ATTRIBUTE_FLAG_COMPRESSION_MASK | + ATTRIBUTE_FLAG_SPARSE)) { + _Data->Form.Nonresident.TotalAllocated = + (Extents->QueryClustersAllocated()*ClusterSize).GetLargeInteger(); + } + _Data->Form.Nonresident.FileSize = ActualLength.GetLargeInteger(); + _Data->Form.Nonresident.ValidDataLength = ValidLength.GetLargeInteger(); + + + // Copy the name + + if( Name != NULL ) { + + if( (ULONG)(_Data->NameOffset + _Data->NameLength) > _MaximumLength ) { + + // There isn't enough room for the name. + + return FALSE; + } + + Name->QueryWSTR( 0, + _Data->NameLength, + (PWSTR)((PBYTE)_Data + _Data->NameOffset), + _Data->NameLength, + FALSE ); + } + + + if( !Extents->QueryCompressedMappingPairs( + (PVCN)&(_Data->Form.Nonresident.LowestVcn), + &NextVcn, + &MappingPairsLength, + _MaximumLength - + _Data->Form.Nonresident.MappingPairsOffset, + (PVOID)((PBYTE)_Data + + _Data->Form.Nonresident.MappingPairsOffset) ) ) { + + // Unable to get the compressed mapping pairs. + + DebugPrint( "Could not get compressed mapping pairs.\n" ); + return FALSE; + } + + HighestVcn = NextVcn - 1; + memcpy( &_Data->Form.Nonresident.HighestVcn, &HighestVcn, sizeof(VCN) ); + + _Data->RecordLength = + QuadAlign(_Data->Form.Nonresident.MappingPairsOffset + + MappingPairsLength ); + + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE_RECORD::UseClusters( + IN OUT PNTFS_BITMAP VolumeBitmap, + OUT PBIG_INT ClusterCount + ) CONST +/*++ + +Routine Description: + + This routine allocates the disk space claimed by this attribute + record in the bitmap provided. A check is made to verify that + the requested disk space is free before the allocation takes + place. If the requested space is not available in the bitmap + then this routine will return FALSE. + +Arguments: + + VolumeBitmap - Supplies the bitmap. + ClusterCount - Receives the number of clusters allocated + to this record. Not set if method fails. + +Return Value: + + FALSE - The request bitmap space was not available. + TRUE - Success. + +--*/ +{ + NTFS_EXTENT_LIST extent_list; + ULONG num_extents; + ULONG i, j; + VCN next_vcn; + LCN current_lcn; + BIG_INT run_length; + + DebugAssert(VolumeBitmap); + + if (IsResident()) { + *ClusterCount = 0; + return TRUE; + } + + if (!QueryExtentList(&extent_list)) { + return FALSE; + } + + num_extents = extent_list.QueryNumberOfExtents(); + + for (i = 0; i < num_extents; i++) { + + if (!extent_list.QueryExtent(i, &next_vcn, ¤t_lcn, + &run_length)) { + return FALSE; + } + + if (current_lcn == LCN_NOT_PRESENT) { + continue; + } + + + // Make sure that the run is free before allocating. + // If it is not, this indicates a cross-link. + + if (!VolumeBitmap->IsFree(current_lcn, run_length)) + { + + DebugPrintTrace(("cross-linked run starts at 0x%I64x for 0x%I64x\n", + current_lcn.GetLargeInteger(), + run_length.GetLargeInteger())); + + // Free everything so far allocated by this routine. + + for (j = 0; j < i; j++) { + + if (!extent_list.QueryExtent(j, &next_vcn, ¤t_lcn, + &run_length)) { + + DebugAbort("Could not query extent"); + return FALSE; + } + if (current_lcn == LCN_NOT_PRESENT) { + continue; + } + + VolumeBitmap->SetFree(current_lcn, run_length); + } + + return FALSE; + } + + VolumeBitmap->SetAllocated(current_lcn, run_length); + } + + *ClusterCount = extent_list.QueryClustersAllocated(); + return TRUE; +} + + +BOOLEAN +NTFS_ATTRIBUTE_RECORD::QueryName( + OUT PWSTRING Name + ) CONST +/*++ + +Routine Description: + + This method returns the name of the attribute. + +Arguments: + + Name - Returns the name of the attribute. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + if (FIELD_OFFSET(ATTRIBUTE_RECORD_HEADER, Flags) >= _MaximumLength || + ULONG(_Data->NameOffset + _Data->NameLength) > _MaximumLength || + _Data->NameLength == 0) { + + return Name->Initialize( "" ); + + } else { + + return Name->Initialize((PWSTR)((PBYTE)_Data + _Data->NameOffset), + _Data->NameLength); + + } +} + + +VOID +NTFS_ATTRIBUTE_RECORD::QueryValueLength( + OUT PBIG_INT ValueLength, + OUT PBIG_INT AllocatedLength, + OUT PBIG_INT ValidLength, + OUT PBIG_INT TotalAllocated + ) CONST +/*++ + +Routine Description: + + This method returns the actual, allocated, valid, and + total allocated lengths + of the attribute value associated with this record. + + If the attribute is resident, these values are all + the length of the resident value, except total allocated, + which is meaningless. + + If the attribute is nonresident, these four values are only + meaningful if the LowestVcn of this attribute record is 0. + Additionally, TotalAllocated is only valid for compressed + attributes. + +Arguments: + + ValueLength -- receives the actual length of the value. + AllocatedLength -- receives the allocated size of the value. + (may be NULL if the caller doesn't care) + ValidLength -- receives the valid length of the value. + (may be NULL if the caller doesn't care) + TotalAllocated -- receives the total allocated length of the + value (may be NULL). + +Return Value: + + None. + +--*/ +{ + DebugPtrAssert( _Data ); + + if( _Data->FormCode == RESIDENT_FORM ) { + + *ValueLength = _Data->Form.Resident.ValueLength; + + if( AllocatedLength != NULL ) { + + *AllocatedLength = _Data->Form.Resident.ValueLength; + } + + if( ValidLength != NULL ) { + + *ValidLength = _Data->Form.Resident.ValueLength; + } + + if (TotalAllocated != NULL ) { + + // no such value for resident attributes + + *TotalAllocated = 0; + } + + } else { + + DebugAssert( _Data->FormCode == NONRESIDENT_FORM ); + + *ValueLength = _Data->Form.Nonresident.FileSize; + + if( AllocatedLength != NULL ) { + + *AllocatedLength = _Data->Form.Nonresident.AllocatedLength; + } + + if( ValidLength != NULL ) { + + *ValidLength = _Data->Form.Nonresident.ValidDataLength; + } + + if (TotalAllocated != NULL) { + if ((_Data->Flags & (ATTRIBUTE_FLAG_COMPRESSION_MASK | + ATTRIBUTE_FLAG_SPARSE)) != 0) { + *TotalAllocated = _Data->Form.Nonresident.TotalAllocated; + } else { + *TotalAllocated = 0; + } + } + + } +} + +VOID +NTFS_ATTRIBUTE_RECORD::SetTotalAllocated( + IN BIG_INT TotalAllocated + ) +/*++ + +Routine Description: + + Set the "TotalAllocated" field in the attribute record. If the + attribute record doesn't have a total allocated field because + the attribute isn't compressed or because it's resident, this + method has no effect. + +Arguments: + + TotalAllocated - the new value. + +Return Value: + + None. + +--*/ +{ + DebugPtrAssert( _Data ); + + if( _Data->FormCode == RESIDENT_FORM ) { + + // no such value for resident attributes; ignore + + return; + + } + + DebugAssert( _Data->FormCode == NONRESIDENT_FORM ); + + if ((_Data->Flags & (ATTRIBUTE_FLAG_COMPRESSION_MASK | + ATTRIBUTE_FLAG_SPARSE)) != 0) { + _Data->Form.Nonresident.TotalAllocated = + TotalAllocated.GetLargeInteger(); + } +} + + + +BOOLEAN +NTFS_ATTRIBUTE_RECORD::QueryExtentList( + OUT PNTFS_EXTENT_LIST ExtentList + ) CONST +/*++ + +Routine Description: + +Arguments: + + None. + +Return Value: + + A pointer to the extent list. A return value of NULL indicates + that the attribute is resident or that an error occurred processing + the compressed mapping pairs. (Clients should use IsResident to + determine whether the attribute value is resident.) + +--*/ +{ + DebugPtrAssert( _Data ); + + if( _Data->FormCode == NONRESIDENT_FORM && + ExtentList->Initialize( _Data->Form.Nonresident.LowestVcn, + (PVOID)((PBYTE)_Data + + _Data->Form.Nonresident.MappingPairsOffset), + _MaximumLength - + _Data->Form.Nonresident. + MappingPairsOffset ) ) { + + return TRUE; + + } else { + + return FALSE; + } +} + + +BOOLEAN +NTFS_ATTRIBUTE_RECORD::IsMatch( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name, + IN PCVOID Value, + IN ULONG ValueLength + ) CONST +/*++ + +Routine Description: + + This method determines whether the attribute record matches the + parameters given. + +Arguments: + + Type -- Supplies the type code of the attribute. This + is the primary key, and must always be present. + Name -- Supplies a name to match. A name of NULL is the + same as specifying the null string. + Value -- Supplies the value to match. If this argument is + null, any value matches. Only resident + attributes can be checked for value matches. + ValueLength -- Supplies the length of the value (if any). + +Notes: + + Value matching is not supported for nonresident attribute values; + if a Value parameter is supplied, then no non-resident attribute + records will match. + +--*/ +{ + DSTRING RecordName; + + DebugPtrAssert( _Data ); + + if( Type != _Data->TypeCode ) { + + return FALSE; + } + + if( Name != NULL ) { + + if( !RecordName.Initialize((PWSTR)((PBYTE)_Data + _Data->NameOffset), + _Data->NameLength ) ) { + + return FALSE; + } + + if( Name->Strcmp( &RecordName ) != 0 ) { + + return FALSE; + } + } else if (_Data->NameLength) { + return FALSE; + } + + if( Value != NULL && + ( _Data->FormCode != RESIDENT_FORM || + ValueLength != _Data->Form.Resident.ValueLength || + memcmp( Value, + (PBYTE)_Data + _Data->Form.Resident.ValueOffset, + (UINT) ValueLength ) ) ) { + + return FALSE; + } + + return TRUE; +} + + +LONG +CompareAttributeRecords( + IN PCNTFS_ATTRIBUTE_RECORD Left, + IN PCNTFS_ATTRIBUTE_RECORD Right, + IN PCNTFS_UPCASE_TABLE UpcaseTable + ) +/*++ + +Routine Description: + + This method compares two attribute records to determine their + correct ordering in the File Record Segment. + +Arguments: + + Left -- Supplies the left-hand operand of the comparison. + Right -- Supplies the right-hand operand of the comparison. + UpcaseTable -- Supplies the upcase table for the volume. + If this parameter is NULL, name comparison + cannot be performed. + +Return Value: + + <0 if Left is less than Right + 0 if Left equals Right + >0 if Left is greater than Right. + +Notes: + + Attribute records are ordered first by type code and then + by name. An attribute record without a name is less than + any attribute record of the same type with a name. + + Name comparision is first done case-insensitive; if the names + are equal by that metric, a case-sensitive comparision is made. + + The UpcaseTable parameter may be omitted if either or both names + are zero-length, or if they are identical (including case). + Otherwise, it must be supplied. + +--*/ +{ + ULONG Result; + + // First, compare the type codes: + // + Result = Left->QueryTypeCode() - Right->QueryTypeCode(); + + if( Result != 0 ) { + + return Result; + } + + // They have the same type code, so we have to compare the names. + // Pass in TRUE for the IsAttribute parameter, to indicate that + // we are comparing attribute names. + // + return( NtfsUpcaseCompare( Left->GetName(), + Left->QueryNameLength(), + Right->GetName(), + Right->QueryNameLength(), + UpcaseTable, + TRUE ) ); +} diff --git a/untfs/src/badfile.cxx b/untfs/src/badfile.cxx new file mode 100644 index 0000000..cb153da --- /dev/null +++ b/untfs/src/badfile.cxx @@ -0,0 +1,337 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + badfile.hxx + +Abstract: + + This module contains the declarations for the NTFS_BAD_CLUSTER_FILE + class, which models the bad cluster file for an NTFS volume. + + The DATA attribute of the bad cluster file is a non-resident + attribute to which bad clusters are allocated. It is stored + as a sparse file with LCN = VCN. + +--*/ + +#include "ulib.hxx" + +#include "untfs.hxx" + +#include "drive.hxx" +#include "numset.hxx" + +#include "ntfsbit.hxx" +#include "mft.hxx" +#include "attrrec.hxx" +#include "attrib.hxx" + +#include "badfile.hxx" +#include "ifssys.hxx" +#include "message.hxx" + + +#define BadfileDataNameData "$Bad" + + +DEFINE_CONSTRUCTOR( NTFS_BAD_CLUSTER_FILE, NTFS_FILE_RECORD_SEGMENT ); + + +NTFS_BAD_CLUSTER_FILE::~NTFS_BAD_CLUSTER_FILE( + ) +{ + Destroy(); +} + +VOID +NTFS_BAD_CLUSTER_FILE::Construct( + ) +/*++ + +Routine Description: + + Worker method for NTFS_BAD_CLUSTER_FILE construction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _DataAttribute = NULL; +} + +VOID +NTFS_BAD_CLUSTER_FILE::Destroy( + ) +/*++ + +Routine Description: + + Worker method for NTFS_BAD_CLUSTER_FILE destruction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + DELETE( _DataAttribute ); +} + + + +BOOLEAN +NTFS_BAD_CLUSTER_FILE::Initialize( + IN OUT PNTFS_MASTER_FILE_TABLE Mft + ) +/*++ + +Routine Description: + + This method initializes an NTFS_BAD_CLUSTER_FILE object. + +Arguments: + + Mft -- Supplies the volume MasterFile Table. + +--*/ +{ + Destroy(); + + return( NTFS_FILE_RECORD_SEGMENT:: + Initialize( BAD_CLUSTER_FILE_NUMBER, + Mft ) ); +} + + +BOOLEAN +NTFS_BAD_CLUSTER_FILE::Add( + IN LCN Lcn + ) +/*++ + +Routine Description: + + This method adds a cluster to the Bad Cluster List. Note that it + does not mark it as used in the volume bitmap. + +Arguments: + + Lcn -- supplies the LCN of the bad cluster + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + return( AddRun( Lcn, 1 ) ); +} + + + +BOOLEAN +NTFS_BAD_CLUSTER_FILE::Add( + IN PCNUMBER_SET ClustersToAdd + ) +/*++ + +Routine Description: + + This method adds a set of clusters to the Bad Cluster List. Note + that it does not mark them as used in the volume bitmap. + +Arguments: + + BadClusters -- Supplies the clusters to be added to the + bad cluster file. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + BIG_INT NumberOfClustersToAdd; + LCN CurrentLcn; + ULONG i; + + NumberOfClustersToAdd = ClustersToAdd->QueryCardinality(); + + for( i = 0; i < NumberOfClustersToAdd; i++ ) + { + CurrentLcn = ClustersToAdd->QueryNumber(i); + + if( !IsInList( CurrentLcn ) && + !Add( CurrentLcn ) ) + { + return FALSE; + } + } + + return TRUE; +} + + + +BOOLEAN +NTFS_BAD_CLUSTER_FILE::AddRun( + IN LCN Lcn, + IN BIG_INT RunLength + ) +/*++ + +Routine Description: + + This method adds a run of clusters to the Bad Cluster List. Note + that it does not mark these clusters as used in the volume bitmap. + +Arguments: + + Lcn -- supplies the LCN of the first cluster in the run. + RunLength -- supplies the number of clusters in the run. + +Return Value: + + TRUE upon successful completion. + +Notes: + + If LCN is in the range of the volume but the run extends past + the end of the volume, then the run is truncated. + + If LCN or the RunLength is negative, the run is ignored. (The + method succeeds without doing anything in this case.) + +--*/ +{ + DSTRING DataAttributeName; + BIG_INT num_clusters; + BOOLEAN Error; + + num_clusters = QueryVolumeSectors()/QueryClusterFactor(); + + if( Lcn < 0 || + Lcn >= num_clusters || + RunLength < 0 ) + { + return TRUE; + } + + if (Lcn + RunLength > num_clusters) + { + RunLength = num_clusters - Lcn; + } + + if( _DataAttribute == NULL && + ( !DataAttributeName.Initialize( BadfileDataNameData ) || + (_DataAttribute = NEW NTFS_ATTRIBUTE) == NULL || + !QueryAttribute( _DataAttribute, + &Error, + $DATA, + &DataAttributeName ) ) ) + { + DELETE( _DataAttribute ); + return FALSE; + } + + return( _DataAttribute->AddExtent( Lcn, Lcn, RunLength ) ); +} + + +BOOLEAN +NTFS_BAD_CLUSTER_FILE::IsInList( + IN LCN Lcn + ) +/*++ + +Routine Description: + + This method determines whether a particular LCN is in the bad + cluster list. + +Arguments: + + Lcn -- supplies the LCN in question. + +Return Value: + + TRUE if the specified LCN is in the list of bad clusters. + +Notes: + + This method cannot be CONST because it may need to fetch the + data attribute. + +--*/ +{ + DSTRING DataAttributeName; + LCN QueriedLcn; + BOOLEAN Error; + + if( _DataAttribute == NULL && + (!DataAttributeName.Initialize( BadfileDataNameData ) || + (_DataAttribute = NEW NTFS_ATTRIBUTE) == NULL || + !QueryAttribute( _DataAttribute, + &Error, + $DATA, + &DataAttributeName ) ) ) + { + DELETE( _DataAttribute ); + return FALSE; + } + + if( !_DataAttribute->QueryLcnFromVcn( Lcn, &QueriedLcn ) || + QueriedLcn == LCN_NOT_PRESENT ) + { + return FALSE; + } + + return TRUE; +} + + + +BOOLEAN +NTFS_BAD_CLUSTER_FILE::Flush( + IN OUT PNTFS_BITMAP Bitmap, + IN OUT PNTFS_INDEX_TREE ParentIndex + ) +/*++ + +Routine Description: + + Write the modified bad cluster list to disk. + +Arguments: + + Bitmap -- supplies the volume bitmap. (May be NULL). + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + if( _DataAttribute != NULL && + _DataAttribute->IsStorageModified() && + !_DataAttribute->InsertIntoFile( this, Bitmap ) ) { + + return FALSE; + } + + return( NTFS_FILE_RECORD_SEGMENT::Flush( Bitmap, ParentIndex ) ); +} + diff --git a/untfs/src/bitfrs.cxx b/untfs/src/bitfrs.cxx new file mode 100644 index 0000000..8d3952d --- /dev/null +++ b/untfs/src/bitfrs.cxx @@ -0,0 +1,115 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + bitfrs.cxx + +Abstract: + + This module contains the member function definitions for + the NTFS_BITMAP_FILE class. + +--*/ + + + +#include "ulib.hxx" + +#include "untfs.hxx" + +#include "ntfsbit.hxx" +#include "drive.hxx" +#include "attrib.hxx" +#include "bitfrs.hxx" + +DEFINE_CONSTRUCTOR( NTFS_BITMAP_FILE, NTFS_FILE_RECORD_SEGMENT ); + + +NTFS_BITMAP_FILE::~NTFS_BITMAP_FILE( + ) +{ + Destroy(); +} + + +VOID +NTFS_BITMAP_FILE::Construct( + ) +/*++ + +Routine Description: + + Worker function for the construtor. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ +} + + +VOID +NTFS_BITMAP_FILE::Destroy( + ) +/*++ + +Routine Description: + + Clean up an NTFS_BITMAP_FILE object in preparation for + destruction or reinitialization. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ +} + + +BOOLEAN +NTFS_BITMAP_FILE::Initialize( + IN OUT PNTFS_MASTER_FILE_TABLE Mft + ) +/*++ + +Routine Description: + + This method initializes a Bitmap File object. + The only special knowledge that it adds to the File Record Segment + initialization is the location within the Master File Table of the + Bitmap File. + +Arguments: + + Mft -- Supplies the volume MasterFile Table. + +Return Value: + + TRUE upon successful completion + +Notes: + + This class is reinitializable. + + +--*/ +{ + Destroy(); + + return( NTFS_FILE_RECORD_SEGMENT::Initialize( BIT_MAP_FILE_NUMBER, + Mft ) ); +} + diff --git a/untfs/src/clusrun.cxx b/untfs/src/clusrun.cxx new file mode 100644 index 0000000..f785e02 --- /dev/null +++ b/untfs/src/clusrun.cxx @@ -0,0 +1,161 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + clusrun.cxx + +Abstract: + + This class models a run of clusters on an NTFS volume. Its + principle purpose is to mediate between the cluster-oriented + NTFS volume and the sector-oriented drive object. + +--*/ + +#include "ulib.hxx" + +#include "untfs.hxx" +#include "clusrun.hxx" + +DEFINE_CONSTRUCTOR( NTFS_CLUSTER_RUN, SECRUN ); + + +NTFS_CLUSTER_RUN::~NTFS_CLUSTER_RUN( + ) +{ + Destroy(); +} + + +VOID +NTFS_CLUSTER_RUN::Construct( + ) +/*++ + +Routine Description: + + Worker function for construction of the NTFS_CLUSTER_RUN object. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _StartLcn = 0; + _ClusterFactor = 0; + _Drive = NULL; + _IsModified = FALSE; +} + +VOID +NTFS_CLUSTER_RUN::Destroy( + ) +/*++ + +Routine Description: + + Worker function for destruction of the NTFS_CLUSTER_RUN object. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _StartLcn = 0; + _ClusterFactor = 0; + _Drive = NULL; + _IsModified = FALSE; +} + + + +BOOLEAN +NTFS_CLUSTER_RUN::Initialize ( + IN OUT PMEM Mem, + IN OUT PLOG_IO_DP_DRIVE Drive, + IN LCN Lcn, + IN ULONG ClusterFactor, + IN ULONG RunLength + ) +/*++ + +Routine Description: + + Initialize an NTFS_CLUSTER_RUN object. + +Arguments: + + Mem -- supplies the memory object for this cluster run. + Drive -- supplies the drive on which this cluster run resides. + Lcn -- supplies the staring Lcn of the cluster run. + Cluster Factor -- supplies the cluster factor for the drive. + RunLength -- supplies the length of the run in clusters. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This class is reinitializable. + +--*/ +{ + DebugPtrAssert( Mem ); + DebugPtrAssert( Drive ); + + Destroy(); + + if( !SECRUN::Initialize( Mem, Drive, + Lcn * ClusterFactor, + RunLength * ClusterFactor ) ) { + + return FALSE; + } + + _StartLcn = Lcn; + _Drive = Drive; + _ClusterFactor = ClusterFactor; + + return TRUE; +} + + + +VOID +NTFS_CLUSTER_RUN::Relocate ( + IN LCN NewLcn + ) +/*++ + +Routine Description: + + Move the cluster run to point at a different section of + the volume. + +Arguments: + + NewLcn -- supplies the first Lcn of the new location. + +Return Value: + + None. + +--*/ +{ + _StartLcn = NewLcn; + + SECRUN::Relocate( NewLcn * _ClusterFactor ); +} diff --git a/untfs/src/extents.cxx b/untfs/src/extents.cxx new file mode 100644 index 0000000..cf940dc --- /dev/null +++ b/untfs/src/extents.cxx @@ -0,0 +1,1826 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + extents.cxx + +Abstract: + + This module contains the definitions for NTFS_EXTENT_LIST, which + models a set of NTFS extents. + + An extent is a contiguous run of clusters; a non-resident + attribute's value is made up of a list of extents. The + NTFS_EXTENT_LIST object can be used to describe the disk space + allocated to a non-resident attribute. + + This class also encapsulates the knowledge of mapping pairs + and their compression, i.e. of the representation of extent + lists in attribute records. + + The extent list is kept sorted by VCN. Since extent lists are + typically quite short, linear search is used. + + +--*/ + + + +#include "ulib.hxx" + +#include "untfs.hxx" +#include "ntfsbit.hxx" +#include "intstack.hxx" +#include "iterator.hxx" + +#include "extents.hxx" +#include "ntfssa.hxx" + +extern "C" { +#include "fsrtlp.h" +} + +DEFINE_CONSTRUCTOR( NTFS_EXTENT_LIST, OBJECT ); +DEFINE_CONSTRUCTOR( NTFS_EXTENT, OBJECT ); + +// +// These routines are to support the NTFS MCB package. +// + +extern "C" +PVOID +MemAlloc( + IN ULONG Size + ); + +PVOID +MemAlloc( + IN ULONG Size + ) +{ + return NEW BYTE[Size]; +} + +extern "C" +PVOID +MemAllocOrRaise( + IN ULONG Size + ); + +PVOID +MemAllocOrRaise( + IN ULONG Size + ) +{ + PVOID p; + + p = MemAlloc(Size); + + if (NULL == p) + { + RtlRaiseStatus(STATUS_INSUFFICIENT_RESOURCES); + } + return p; +} + +extern "C" +VOID +MemFree( + IN PVOID Addr + ); + +VOID +MemFree( + IN PVOID Addr + ) +{ + delete[] Addr; +} + + + +NTFS_EXTENT_LIST::~NTFS_EXTENT_LIST( + ) +/*++ + +Routine Description: + + Destructor for NTFS_EXTENT_LIST class. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + +VOID +NTFS_EXTENT_LIST::Construct( + ) +/*++ + +Routine Description: + + Worker method for NTFS_EXTENT_LIST construction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _LowestVcn = 0; + _NextVcn = 0; + _McbInitialized = FALSE; + _Mcb = NULL; +} + +VOID +NTFS_EXTENT_LIST::Destroy( + ) +/*++ + +Routine Description: + + Worker method for NTFS_EXTENT_LIST destruction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _LowestVcn = 0; + _NextVcn = 0; + + if (_McbInitialized) { + + FsRtlUninitializeLargeMcb(_Mcb); + _McbInitialized = FALSE; + } + + DELETE(_Mcb); +} + + + +BOOLEAN +NTFS_EXTENT_LIST::Initialize( + IN VCN LowestVcn, + IN VCN NextVcn + ) +/*++ + +Routine Description: + + Initializes an empty extent list. + +Arguments: + + LowestVcn -- supplies the lowest VCN mapped by this extent list + NextVcn -- supplies the next VCN following this extent list + +Return Value: + + TRUE upon successful completion. + +Notes: + + Highest and Lowest VCN are typically zero; they are provided + to permit creation of sparse files. + + This class is reinitializable. + +--*/ +{ + Destroy(); + + _LowestVcn = LowestVcn; + _NextVcn = (NextVcn < LowestVcn) ? LowestVcn : NextVcn; + + if (NULL == (_Mcb = NEW LARGE_MCB)) { + Destroy(); + return FALSE; + } + + __try { + + FsRtlInitializeLargeMcb(_Mcb, (POOL_TYPE)0); + + } __except (EXCEPTION_EXECUTE_HANDLER) { + + Destroy(); + return FALSE; + } + + _McbInitialized = TRUE; + + return TRUE; +} + + +BOOLEAN +NTFS_EXTENT_LIST::Initialize( + IN VCN StartingVcn, + IN PCVOID CompressedMappingPairs, + IN ULONG MappingPairsMaximumLength, + OUT PBOOLEAN BadMappingPairs + ) +/*++ + +Routine Description: + + Initialize an extent list based on a set of compressed mapping + pairs (presumably taken from an attribute record). + +Arguments: + + StartingVcn -- Supplies the starting VCN of the + mapping pairs list + CompressedMappingPairs -- Supplies a pointer to the compressed + list of mapping pairs + MappingPairsMaximumLength -- Supplies the length (in bytes) of + the buffer in which the mapping + pairs list resides + BadMappingPairs -- If non-NULL, receives TRUE if this + method failed because the mapping + pairs list could not be expanded. + If this method returns TRUE, then + *BadMappingPairs should be ignored. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method does not assume that the mapping pairs list can be + correctly expanded. It does assume that the MappingPairsMaximumLength + is correct, i.e. that it can reference that many bytes of memory + starting at CompressedMappingPairs. + + Clients who trust the mapping pairs list which they pass in may omit + the BadMappingPairs parameter; those (like Chkdsk) who do not trust + the list can use BadMappingPairs to determine whether Initialize failed + because of a bad mapping pairs list. + +--*/ +{ + DebugPtrAssert( CompressedMappingPairs ); + + return( Initialize( StartingVcn, StartingVcn ) && + AddExtents( StartingVcn, + CompressedMappingPairs, + MappingPairsMaximumLength, + BadMappingPairs ) ); +} + + + +BOOLEAN +NTFS_EXTENT_LIST::Initialize( + IN PCNTFS_EXTENT_LIST ExtentsToCopy + ) +/*++ + +Routine Description: + + Initializes an extent list based on another extent list. + +Arguments: + + ExtentsToCopy - Supplies the other list of extents. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This class is reinitializable. + +--*/ +{ + VCN Vcn; + LCN Lcn; + BIG_INT RunLength; + ULONG num_extents; + + Destroy(); + + if (!Initialize(ExtentsToCopy->_LowestVcn, + ExtentsToCopy->_NextVcn)) { + + Destroy(); + return FALSE; + } + + num_extents = ExtentsToCopy->QueryNumberOfExtents(); + + for (ULONG i = 0; i < num_extents; ++i) { + if (!ExtentsToCopy->QueryExtent(i, &Vcn, &Lcn, &RunLength)) { + Destroy(); + return FALSE; + } + if (LCN_NOT_PRESENT == Lcn) { + continue; + } + + if (!AddExtent(Vcn, Lcn, RunLength)) { + Destroy(); + return FALSE; + } + } + + SetLowestVcn(ExtentsToCopy->_LowestVcn); + SetNextVcn(ExtentsToCopy->_NextVcn); + + return TRUE; +} + + + +BOOLEAN +NTFS_EXTENT_LIST::IsSparse( + ) CONST +/*++ + +Routine Description: + + This method determines whether the extent list has holes + (ie. if there are not-present-on-disk runs in the attribute). + +Arguments: + + None. + +Return Value: + + TRUE if the extent list is sparse. + +--*/ +{ + BIG_INT previous_vcn, previous_lcn, previous_runlength; + BIG_INT current_vcn, current_lcn, current_runlength; + ULONG i = 0; + + if (!QueryExtent(i, &previous_vcn, &previous_lcn, &previous_runlength)) { + + if (_LowestVcn != _NextVcn) { + + // This extent list is one big hole. + + return TRUE; + + } else { + + // This extent list is empty. + + return FALSE; + } + } + + if (previous_vcn != _LowestVcn) { + + // This extent list starts with a hole. + + return TRUE; + } + + // Check the rest of the extents, to see if there's a hole + // before any of them: + + while (QueryExtent(++i, ¤t_vcn, ¤t_lcn, ¤t_runlength)) { + + if (LCN_NOT_PRESENT == current_lcn) { + continue; + } + + if (previous_vcn + previous_runlength != current_vcn) { + return TRUE; + } + + previous_vcn = current_vcn; + previous_lcn = current_lcn; + previous_runlength = current_runlength; + } + + // Check to see if there's a hole after the last extent: + + if (previous_vcn + previous_runlength != _NextVcn) { + return TRUE; + } + + // Didn't find any holes. + + return FALSE; +} + + + +BOOLEAN +NTFS_EXTENT_LIST::AddExtent( + IN VCN Vcn, + IN LCN Lcn, + IN BIG_INT RunLength + ) +/*++ + +Routine Description: + + This method adds an extent, specified by its Virtual Cluster Number, + Logical Cluster Number, and Run Length, to the extent list. + + NTFS_EXTENT_LIST may, at its discretion, merge contiguous extents, + but it does not guarrantee this behavior. + +Arguments: + + Vcn -- Supplies the starting VCN of the extent. + Lcn -- Supplies the starting LCN of the extent. + RunLength -- Supplies the number of clusters in the extent. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + VCN TempVcn; + BOOLEAN b; + + if (RunLength <= 0) { + + // zero-length runs are not valid. Neither are negative ones. + + return FALSE; + } + if (LCN_NOT_PRESENT == Lcn) { + + // We ignore attempts to explicitly add holes. + + return TRUE; + } + + // + // Currently, Mcb routines cannot handle number beyond 32 bits + // + + if (Vcn.GetLargeInteger().HighPart) { + return FALSE; + } + + __try { + + b = FsRtlAddLargeMcbEntry(_Mcb, + Vcn.GetLargeInteger().QuadPart, + Lcn.GetLargeInteger().QuadPart, + RunLength.GetLargeInteger().QuadPart); + + } __except (EXCEPTION_EXECUTE_HANDLER) { + + return FALSE; + } + + if (!b) { + return FALSE; + } + + if (_LowestVcn == _NextVcn) { + _LowestVcn = Vcn; + _NextVcn = Vcn + RunLength; + } + + + // If this extent changes the lowest and highest VCNs mapped + // by this extent list, update those values. + + if (Vcn < _LowestVcn) { + + _LowestVcn = Vcn; + } + + TempVcn = Vcn + RunLength; + + if (TempVcn > _NextVcn) { + + _NextVcn = TempVcn; + } + + return TRUE; +} + + + +VOID +NTFS_EXTENT_LIST::Coalesce( + ) +/*++ + +Routine Description: + + This routine coalesces adjacent extents in the extent list. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + // The MCB package does this for us. + + return; +} + + +BOOLEAN +NTFS_EXTENT_LIST::AddExtents( + IN VCN StartingVcn, + IN PCVOID CompressedMappingPairs, + IN ULONG MappingPairsMaximumLength, + OUT PBOOLEAN BadMappingPairs + ) +/*++ + +Routine Description: + + This method adds a set of extents defined by a compressed mapping + pairs list (presumably taken from an Attribute Record). + +Arguments: + + StartingVcn -- supplies the starting VCN of the mapping pairs list + CompressedMappingPairs -- supplies a pointer to the compressed + list of mapping pairs + MappingPairsMaximumLengt -- supplies the length (in bytes) of the buffer + in which the mapping pairs list resides + BadMappingPairs -- if non-NULL, receives TRUE if an error occurrs + while processing the mapping pairs list. + +Return Value: + + TRUE upon successful completion. + +Notes: + + BadMappingPairs will be set to TRUE if the compressed mapping + pairs list cannot be expanded or if any extent derived from + this list overlaps with another extent in the list. In either + of these cases, AddExtents will return FALSE. + + If this method encounters an error after processing part of + the list, it will leave the extent list in an undefined (but + valid, as an object) state. + + Clients who trust their mapping pairs list may omit the + BadMappingPairs parameter. + +--*/ +{ + VCN CurrentVcn; + ULONG LengthOfCompressedPairs; + ULONG NumberOfPairs; + PMAPPING_PAIR MappingPairs; + ULONG i; + + // Assume innocent until found guilty + // + if( BadMappingPairs != NULL ) { + + *BadMappingPairs = FALSE; + } + + // Determine how many mapping pairs we actually have, so + // we can allocate the correct size of an expanded mapping + // pairs buffer. + + if( !QueryMappingPairsLength( CompressedMappingPairs, + MappingPairsMaximumLength, + &LengthOfCompressedPairs, + &NumberOfPairs ) ) { + + if( BadMappingPairs != NULL ) { + + *BadMappingPairs = TRUE; + } + + DebugPrint( "Can't determine length of mapping pairs.\n" ); + return FALSE; + } + + // Allocate a buffer to hold the expanded mapping pairs. + + MappingPairs = (PMAPPING_PAIR)MALLOC( sizeof(MAPPING_PAIR) * + (UINT) NumberOfPairs ); + + if( MappingPairs == NULL ) { + + return FALSE; + } + + + if( !ExpandMappingPairs( CompressedMappingPairs, + StartingVcn, + MappingPairsMaximumLength, + NumberOfPairs, + MappingPairs, + &NumberOfPairs ) ) { + + DebugPrint( "Cannot expand mapping pairs.\n" ); + + if( BadMappingPairs != NULL ) { + + *BadMappingPairs = TRUE; + } + + FREE( MappingPairs ); + return FALSE; + } + + + // Convert the mapping pairs into extents. + + CurrentVcn = StartingVcn; + + for( i = 0; i < NumberOfPairs; i++ ) { + + if( MappingPairs[i].CurrentLcn != LCN_NOT_PRESENT ) { + + if( !AddExtent( CurrentVcn, + MappingPairs[i].CurrentLcn, + MappingPairs[i].NextVcn - CurrentVcn ) ) { + + FREE( MappingPairs ); + return FALSE; + } + } + + CurrentVcn = MappingPairs[i].NextVcn; + } + + // Set _LowestVcn to the client-supplied value, if necessary. + // (This is required for mapping pair lists that begin with + // a hole.) + // + if( StartingVcn < _LowestVcn ) { + + _LowestVcn = StartingVcn; + } + + + // Update _NextVcn if neccessary. + // + if( CurrentVcn > _NextVcn ) { + + _NextVcn = CurrentVcn; + } + + FREE( MappingPairs ); + return TRUE; +} + + + +VOID +NTFS_EXTENT_LIST::DeleteExtent( + IN ULONG ExtentNumber + ) +/*++ + +Routine Description: + + This method removes an extent from the list. + +Arguments: + + ExtentNumber -- supplies the (zero-based) extent number to remove + +Return Value: + + None. + +--*/ +{ + VCN Vcn; + LCN Lcn; + BIG_INT RunLength; + + // + // Find the VCN for the extent number. + // + + if (!QueryExtent(ExtentNumber, &Vcn, &Lcn, &RunLength)) { + DebugAbort("Shouldn't get here."); + return; + } + + FsRtlRemoveLargeMcbEntry(_Mcb, + Vcn.GetLargeInteger().QuadPart, + RunLength.GetLargeInteger().QuadPart); + + return; +} + + + +BOOLEAN +NTFS_EXTENT_LIST::Resize( + IN BIG_INT NewNumberOfClusters, + IN OUT PNTFS_BITMAP Bitmap + ) +/*++ + +Routine Description: + + This method either extends or truncates the disk allocation + covered by this extent list. + +Arguments: + + NewNumberOfClusters -- supplies the number of clusters in + in the new allocation. + + Bitmap -- supplies the volume bitmap. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method is only meaningful is the extent list covers an entire + attribute instance. In particular, if the extent list's LowestVcn + is not zero, this method does nothing (and returns FALSE). + + If this method fails, it leaves the extent list in its original + state. + +--*/ +{ + BIG_INT OldNumberOfClusters; + BIG_INT ClustersToAdd; + LCN NewLcn, NearLcn; + ULONG ThisLump; + + if( _LowestVcn != 0 ) { + + // This extent list does not cover the entire attribute + // instance, so it cannot be resized. + + return FALSE; + } + + // Determine the number of clusters in the current size. + + OldNumberOfClusters = _NextVcn; + + + if( OldNumberOfClusters == NewNumberOfClusters ) { + + // The extent list is already the size we want. + + return TRUE; + + } else if( NewNumberOfClusters < OldNumberOfClusters ) { + + // We're shrinking the extent list. Note that Truncate + // always succeeds, and does not have a return value. + + Truncate( NewNumberOfClusters, Bitmap ); + return TRUE; + + } else { + + // We are extending the allocation. + + ClustersToAdd = NewNumberOfClusters - OldNumberOfClusters; + + if( ClustersToAdd.GetHighPart() != 0 ) { + + DebugPrint( "Trying to allocate more than 4G clusters.\n" ); + return FALSE; + } + + ThisLump = ClustersToAdd.GetLowPart(); + NearLcn = QueryLastLcn(); + + while( ClustersToAdd != 0 ) { + + if (ClustersToAdd.GetLowPart() < ThisLump) { + + ThisLump = ClustersToAdd.GetLowPart(); + } + + if( !Bitmap->AllocateClusters( NearLcn, + ThisLump, + &NewLcn ) ) { + + // We can't allocate a chunk this size; cut it + // in half and try again. + // + ThisLump /= 2; + + if( ThisLump == 0 ) { + + // We're out of disk space. Restore the extent + // list to its original state and exit with an + // error. + + Truncate( OldNumberOfClusters, Bitmap ); + return FALSE; + } + + } else { + + // We allocated a chunk. Add it on to the end. + + if( !AddExtent( _NextVcn, NewLcn, ThisLump ) ) { + + // We hit an internal error trying to add + // this extent. Restore the extent list to + // its original state and return failure. + + Truncate( OldNumberOfClusters, Bitmap ); + return FALSE; + } + + ClustersToAdd -= ThisLump; + + // If there's more to get, we won't be able to get + // it contiguous; instead, set NearLcn to 0 to take + // advantage of the roving pointer. + // + NearLcn = 0; + } + } + + return TRUE; + } +} + +BOOLEAN +NTFS_EXTENT_LIST::SetSparse( + IN BIG_INT NewNumberOfClusters + ) +/*++ + +Routine Description: + + This method sets up an extent lists to represents a sparse + allocation (a hole). + +Arguments: + + NewNumberOfClusters -- supplies the number of clusters in + the sparse extent list. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method is only meaningful if the extent list covers an entire + attribute instance. In particular, if the extent list's LowestVcn + is not zero, this method does nothing (and returns FALSE). + + If this method fails, it leaves the extent list in its original + state. + +--*/ +{ + if( _LowestVcn != 0 || _NextVcn != 0) { + + // This extent list does not cover the entire attribute + // instance or is not empty, so it cannot be set sparse. + + return FALSE; + } + + // Determine the number of clusters in the current size. + + _NextVcn = NewNumberOfClusters; + + return TRUE; +} + + + +BOOLEAN +NTFS_EXTENT_LIST::QueryExtent( + IN ULONG ExtentNumber, + OUT PVCN Vcn, + OUT PLCN Lcn, + OUT PBIG_INT RunLength + ) CONST +/*++ + +Routine Description: + + This methods gives the client information on an extent in + the list. + +Arguments: + + ExtentNumber -- supplies the number of the extent to return (zero-based). + Vcn -- receives the starting VCN of the extent. + Lcn -- receives the starting LCN of the extent. + RunLength -- receives the number of clusters in the extent. + +Return Value: + + TRUE if ExtentNumber is less than the number of extents in the list; + FALSE if it is out of range. + +--*/ +{ + BOOLEAN b; + LONGLONG Vbn, Lbn, SectorCount; + LARGE_INTEGER li; + + b = FsRtlGetNextLargeMcbEntry((PLARGE_MCB)_Mcb, + ExtentNumber, &Vbn, &Lbn, &SectorCount); + if (!b) { + return FALSE; + } + + li.QuadPart = Vbn; + *Vcn = li; + + li.QuadPart = Lbn; + *Lcn = li; + + li.QuadPart = SectorCount; + *RunLength = li; + + return TRUE; +} + + +BOOLEAN +NTFS_EXTENT_LIST::QueryLcnFromVcn( + IN VCN Vcn, + OUT PLCN Lcn, + OUT PBIG_INT RunLength + ) CONST +/*++ + +Routine Description: + + This method converts a VCN within the allocation described by + this extent list into an LCN. + +Arguments: + + Vcn -- supplies the VCN to be converted. + Lcn -- receives the LCN that corresponds to the supplied Vcn. + RunLength -- if non-NULL, receives the remaining length in the + extent from the supplied Vcn. + +Return Value: + + TRUE upon successful completion. + + If the specified VCN is outside the range [_LowestVcn, _NextVcn), + this method returns FALSE. + + If the extent list is sparse and the requested VCN is in the range + of this extent list but falls in a hole, this method will return TRUE; + *Lcn is set to LCN_NOT_PRESENT, and *RunLength is set to the remaining + run length to the next actual extent. + +--*/ +{ + BOOLEAN b; + LONGLONG Lbn = -1, SectorCount; + LARGE_INTEGER li; + VCN vcn; + LCN lcn; + BIG_INT runlength; + + if (Vcn < _LowestVcn || Vcn >= _NextVcn) { + + return FALSE; + } + + b = FsRtlLookupLargeMcbEntry( + (PLARGE_MCB)_Mcb, + Vcn.GetLargeInteger().QuadPart, + &Lbn, + &SectorCount, /* SectorCountFromLbn */ + NULL, /* StartingLbn */ + NULL, /* SectorCountFromStartingLbn */ + NULL /* Index */ + ); + if (!b) { + + // This VCN fell into a hole. See if it comes after the last + // real extent in the list, otherwise maybe it comes before the + // first. + // + + *Lcn = LCN_NOT_PRESENT; + + if (0 == QueryNumberOfExtents()) { + if (NULL != RunLength) { + *RunLength = _NextVcn - Vcn; + } + return TRUE; + } + + if (!QueryExtent(QueryNumberOfExtents() - 1, &vcn, &lcn, &runlength)) { + return FALSE; + } + + if (Vcn > vcn) { + if (NULL != RunLength) { + *RunLength = _NextVcn - Vcn; + } + return TRUE; + } + + if (!QueryExtent(0, &vcn, &lcn, &runlength)) { + return FALSE; + } + + if (Vcn < vcn) { + if (NULL != RunLength) { + *RunLength = Vcn - _LowestVcn; + } + return TRUE; + } + + return FALSE; + } + + if (NULL != RunLength) { + li.QuadPart = SectorCount; + *RunLength = li; + } + + if (-1 == Lbn) { + *Lcn = LCN_NOT_PRESENT; + return TRUE; + } + + li.QuadPart = Lbn; + *Lcn = li; + + return TRUE; +} + + + +BOOLEAN +NTFS_EXTENT_LIST::QueryCompressedMappingPairs( + OUT PVCN LowestVcn, + OUT PVCN NextVcn, + OUT PULONG Length, + IN ULONG BufferSize, + IN OUT PVOID Buffer, + OUT PBOOLEAN HasHoleInFront + ) CONST +/*++ + +Routine Description: + + This method produces a set of compressed mapping pairs + corresponding to this extent list. + +Arguments: + + LowestVcn -- receives the lowest VCN covered by this extent list. + NextVcn -- receives the VCN following this extent list. + Length -- receives the length of the compressed mapping pairs list. + BufferSize -- supplies the size of the mapping pairs buffer provided + by the caller + Buffer -- supplies the buffer into which the compressed mapping + pairs list is written. + HasHoleInFront + -- receives TRUE if we skipped a hole at the very beginning + of the extent list during compression; otherwise FALSE. + This is useful for code that counts the compression + pairs and calls QueryExtent with the count. + +Return Value: + + A pointer to the compressed mapping pairs list. Note that the client + must free this memory (using FREE). NULL indicates an error. + +--*/ +{ + ULONG MaximumMappingPairs; + PMAPPING_PAIR MappingPairs; + PMAPPING_PAIR CurrentMappingPair; + ULONG NumberOfPairs; + VCN TheNextVcn; + BOOLEAN Result; + ULONG num_extents; + + if (HasHoleInFront) + *HasHoleInFront = FALSE; + + // First, let's handle the degenerate case--if the list + // has no extents, it's compresses to a single zero byte. + // + if (IsEmpty()) { + + if (BufferSize == 0) { + + return FALSE; + + } else { + + *LowestVcn = 0; + *NextVcn = 0; + *Length = 1; + + *(PBYTE)Buffer = 0; + + return TRUE; + } + } + + // Massage the extent list into a mapping pairs list and compress it. + // In the worst case, no two extents are VCN-contiguous, and + // so the number of mapping pairs would be one more than twice + // the number of extents (gap extent gap extent gap ... extent gap). + + // This upper bound formula may be too much as QueryNumberOfExtents will + // return all entries including gaps except the last one if it is a gap. + + num_extents = QueryNumberOfExtents(); + + MaximumMappingPairs = 2 * num_extents + 1; + + MappingPairs = (PMAPPING_PAIR)MALLOC( (UINT) (sizeof(MAPPING_PAIR) * + MaximumMappingPairs) ); + + if( MappingPairs == NULL ) { + + return FALSE; + } + + TheNextVcn = _LowestVcn; + NumberOfPairs = 0; + + CurrentMappingPair = MappingPairs; + + for (ULONG i = 0; i < num_extents; ++i) { + + VCN Vcn; + LCN Lcn; + BIG_INT RunLength; + + DebugAssert(NumberOfPairs < MaximumMappingPairs); + + if (!QueryExtent(i, &Vcn, &Lcn, &RunLength)) { + DebugAbort("This shouldn't happen\n"); + return FALSE; + } + if (LCN_NOT_PRESENT == Lcn) { + if ((i == 0) && HasHoleInFront) + *HasHoleInFront = TRUE; + continue; + } + + if (Vcn != TheNextVcn) { + + // This extent is preceded by a gap, so we create + // a mapping pair with the LCN equal to LCN_NOT_PRESENT. + + CurrentMappingPair->NextVcn = Vcn; + CurrentMappingPair->CurrentLcn = LCN_NOT_PRESENT; + + CurrentMappingPair++; + NumberOfPairs++; + } + + // Create a mapping pair for the extent represented by + // the current node. At the same time, compute NextVcn + // so we can check to see if there's a gap before the + // next extent. + + TheNextVcn = Vcn + RunLength; + + CurrentMappingPair->NextVcn = TheNextVcn; + CurrentMappingPair->CurrentLcn = Lcn; + + CurrentMappingPair++; + NumberOfPairs++; + } + + DebugAssert(NumberOfPairs < MaximumMappingPairs); + + if (TheNextVcn != _NextVcn) { + + // The last extent is followed by a gap. Add a mapping pair + // (with CurrentLcn of LCN_NOT_PRESENT) to cover that gap. + + CurrentMappingPair->NextVcn = _NextVcn; + CurrentMappingPair->CurrentLcn = LCN_NOT_PRESENT; + + NumberOfPairs++; + CurrentMappingPair++; + } + + // We now have a properly set-up array of mapping pairs. Compress + // it into the user's buffer. + + Result = CompressMappingPairs( MappingPairs, + NumberOfPairs, + _LowestVcn, + Buffer, + BufferSize, + Length ); + + FREE( MappingPairs ); + + *LowestVcn = _LowestVcn; + *NextVcn = _NextVcn; + + return Result; +} + +VOID +NTFS_EXTENT_LIST::Truncate( + IN BIG_INT NewNumberOfClusters, + IN OUT PNTFS_BITMAP Bitmap + ) +/*++ + +Routine Description: + + This method truncates the extent list. + +Arguments: + + NewNumberOfClusters -- supplies the number of clusters to keep. + Bitmap -- supplies the volume bitmap (optional). + +Return Value: + + None. + +Notes: + + If the number of clusters covered by this extent list is already + less than or equal to NewNumberOfClusters, then this method does + nothing. + +--*/ +{ + BIG_INT new_run_length; + ULONG num_extents; + + DebugAssert(_LowestVcn == 0); + + if (NewNumberOfClusters >= _NextVcn) { + + return; + } + + num_extents = QueryNumberOfExtents(); + + for (ULONG i = 0; i < num_extents; ++i) { + + VCN Vcn; + LCN Lcn; + BIG_INT RunLength; + + if (!QueryExtent(i, &Vcn, &Lcn, &RunLength)) { + DebugAbort("This shouldn't happen\n"); + return; + } + if (LCN_NOT_PRESENT == Lcn) { + continue; + } + + if (Vcn >= NewNumberOfClusters) { + + if (NULL != Bitmap) { + Bitmap->SetFree(Lcn, RunLength); + } + } else if (Vcn + RunLength > NewNumberOfClusters) { + + new_run_length = NewNumberOfClusters - Vcn; + + if (NULL != Bitmap) { + Bitmap->SetFree(Lcn + new_run_length, + RunLength - new_run_length); + } + } + } + + _NextVcn = NewNumberOfClusters; + FsRtlTruncateLargeMcb(_Mcb, _NextVcn.GetLargeInteger().QuadPart); +} + + +BOOLEAN +NTFS_EXTENT_LIST::QueryMappingPairsLength( + IN PCVOID CompressedPairs, + IN ULONG MaximumLength, + OUT PULONG Length, + OUT PULONG NumberOfPairs + ) +/*++ + +Routine Description: + + This function determines the length of a compressed + mapping pairs list. + +Arguments: + + CompressedPairs -- supplies the pointer to the compressed list + MaximumLength -- supplies the size of the buffer containing the + compressed list. + Length -- receives the length of the compressed list + NumberOfPairs -- receieves the number of pairs in the list + +Return Value: + + TRUE upon successful completion. FALSE indicates that the list + overflows the supplied buffer. + +--*/ +{ + PBYTE CurrentCountByte; + ULONG CurrentLength; + + CurrentCountByte = (PBYTE)CompressedPairs; + + *NumberOfPairs = 0; + *Length = 0; + + while( *Length <= MaximumLength && + *CurrentCountByte != 0 ) { + + // The length for this pair is the number of LCN bytes, plus + // the number of VCN bytes, plus one for the count byte. + + CurrentLength = LcnBytesFromCountByte( *CurrentCountByte ) + + VcnBytesFromCountByte( *CurrentCountByte ) + + 1; + + (*NumberOfPairs)++; + *Length += CurrentLength; + + CurrentCountByte += CurrentLength; + } + + (*Length)++; // For the final 0 byte. + + return( *Length <= MaximumLength ); +} + + +BOOLEAN +NTFS_EXTENT_LIST::ExpandMappingPairs( + IN PCVOID CompressedPairs, + IN VCN StartingVcn, + IN ULONG BufferSize, + IN ULONG MaximumNumberOfPairs, + IN OUT PMAPPING_PAIR MappingPairs, + OUT PULONG NumberOfPairs + ) +/*++ + +Routine Description: + + This function expands a compressed list of mapping pairs into + a client-supplied buffer. + +Arguments: + + CompressedPairs -- supplies the compressed mapping pairs + StartingVcn -- supplies the lowest VCN mapped by these + mapping pairs + BufferSize -- supplies the maximum size of the buffer from + which the compressed pairs are expanded + MaximumNumberOfPairs -- supplies the maximum number of expanded + mapping pairs the output buffer can accept + MappingPairs -- receives the expanded pairs + NumberOfPairs -- receives the number of pairs + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + PBYTE CurrentData; + VCN CurrentVcn; + LCN CurrentLcn; + UCHAR v, l; + ULONG CurrentLength; + VCN DeltaVcn; + LCN DeltaLcn; + ULONG PairIndex; + + + CurrentData = (PBYTE)CompressedPairs; + CurrentVcn = StartingVcn; + CurrentLcn = 0; + CurrentLength = 0; + PairIndex = 0; + + while( CurrentLength < BufferSize && + *CurrentData != 0 && + PairIndex < MaximumNumberOfPairs + ) { + + // Get the count byte. Note that whenever we advance the + // current data pointer, we first increment the length count, + // to make sure our access is valid. + + CurrentLength ++; + + if( CurrentLength > BufferSize ) { + + return FALSE; + } + + v = VcnBytesFromCountByte( *CurrentData ); + l = LcnBytesFromCountByte( *CurrentData ); + + CurrentData ++; + + + // Unpack DeltaVcn and compute the current VCN: + + CurrentLength += v; + + if( v > 8 || CurrentLength > BufferSize ) { + + return FALSE; + } + + DeltaVcn.Set( v, CurrentData ); + CurrentData += v; + + CurrentVcn += DeltaVcn; + MappingPairs[PairIndex].NextVcn = CurrentVcn; + + // Unpack DeltaLcn and compute the current LCN: + // + CurrentLength += l; + + if( l > 8 || CurrentLength > BufferSize ) { + + return FALSE; + } + + if( l == 0 ) { + + // a delta-LCN count value of 0 indicates a + // non-present run. + // + MappingPairs[PairIndex].CurrentLcn = LCN_NOT_PRESENT; + + } else { + + DeltaLcn.Set( l, CurrentData ); + CurrentLcn += DeltaLcn; + MappingPairs[PairIndex].CurrentLcn = CurrentLcn; + } + + CurrentData += l; + PairIndex ++; + } + + *NumberOfPairs = PairIndex; + + return( CurrentLength <= BufferSize && + *CurrentData == 0 && + PairIndex <= MaximumNumberOfPairs ); +} + + +BOOLEAN +NTFS_EXTENT_LIST::CompressMappingPairs( + IN PCMAPPING_PAIR MappingPairs, + IN ULONG NumberOfPairs, + IN VCN StartingVcn, + IN OUT PVOID CompressedPairs, + IN ULONG MaximumCompressedLength, + OUT PULONG CompressedLength + ) +/*++ + +Notes: + + The returned length includes the terminating NULL count byte. + +--*/ +{ + PBYTE CurrentData; + VCN CurrentVcn; + LCN CurrentLcn; + ULONG CurrentLength; + VCN DeltaVcn; + LCN DeltaLcn; + ULONG i; + UCHAR ComDeltaVcn[sizeof(VCN)]; + UCHAR ComDeltaLcn[sizeof(LCN)]; + UCHAR VcnLength; + UCHAR LcnLength; + UCHAR Major, Minor; + BOOLEAN NewSparseFormat; + + // Determine whether to use the old or new format for + // representing sparse files. + // + NTFS_SA::QueryVersionNumber( &Major, &Minor ); + NewSparseFormat = (Major > 1) || (Major == 1 && Minor > 1); + + // A mapping pair is (NextVcn, CurrentLcn); however, the compressed + // form is a list of deltas. + + CurrentData = (PBYTE)CompressedPairs; + CurrentVcn = StartingVcn; + CurrentLcn = 0; + CurrentLength = 0; + + for( i = 0; i < NumberOfPairs; i++ ) { + + DeltaVcn = MappingPairs[i].NextVcn - CurrentVcn; + DeltaLcn = MappingPairs[i].CurrentLcn - CurrentLcn; + + DeltaVcn.QueryCompressedInteger(&VcnLength, ComDeltaVcn); + + if( NewSparseFormat && MappingPairs[i].CurrentLcn == LCN_NOT_PRESENT ) { + + LcnLength = 0; + DeltaLcn = 0; + + } else { + + DeltaLcn.QueryCompressedInteger(&LcnLength, ComDeltaLcn); + } + + // Fill in the count byte and step over it. + + CurrentLength ++; + + if( CurrentLength > MaximumCompressedLength ) { + + return FALSE; + } + + *CurrentData = ComputeMappingPairCountByte( VcnLength, LcnLength ); + CurrentData ++; + + + // Copy DeltaVcn and advance the pointer + + CurrentLength += VcnLength; + + if( CurrentLength > MaximumCompressedLength ) { + + return FALSE; + } + + memcpy( CurrentData, ComDeltaVcn, VcnLength ); + CurrentData += VcnLength; + + + // Copy DeltaLcn and advance the pointer + + CurrentLength += LcnLength; + + if( CurrentLength > MaximumCompressedLength ) { + + return FALSE; + } + + memcpy( CurrentData, ComDeltaLcn, LcnLength ); + CurrentData += LcnLength; + + CurrentVcn += DeltaVcn; + CurrentLcn += DeltaLcn; + } + + // Terminate the compressed list with a zero count byte + + CurrentLength ++; + + if( CurrentLength > MaximumCompressedLength ) { + + return FALSE; + } + + *CurrentData = 0; + CurrentData ++; + + *CompressedLength = CurrentLength; + return TRUE; +} + + +LCN +NTFS_EXTENT_LIST::QueryLastLcn( + ) CONST +/*++ + +Routine Description: + + This method returns the last LCN associated with this allocation. + If it cannot determine that LCN, it returns an LCN of zero. + +Arguments: + + None. + +Return Value: + + The LCN of the last cluster in the extent list (or zero, if + the list is empty or we can't determine the last cluster). + +--*/ +{ + LCN TempLcn; + + if( QueryLcnFromVcn( _NextVcn - 1, &TempLcn ) && + TempLcn != LCN_NOT_PRESENT ) { + + return TempLcn; + + } else { + + return( 0 ); + } +} + + + +BOOLEAN +NTFS_EXTENT_LIST::DeleteRange( + IN VCN Vcn, + IN BIG_INT RunLength + ) +/*++ + +Routine Description: + + This routine will remove any vcn's in the range specified from + the extent list. If this does not exist or exists only partially + then those parts that exist will be removed. + +Arguments: + + Vcn - Supplies the first Vcn of the range. + RunLength - Supplies the length of the range. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + FsRtlRemoveLargeMcbEntry(_Mcb, + Vcn.GetLargeInteger().QuadPart, + RunLength.GetLargeInteger().QuadPart + ); + + return TRUE; +} + + + +BIG_INT +NTFS_EXTENT_LIST::QueryClustersAllocated( + ) CONST +/*++ + +Routine Description: + + This routine computes the number of clusters allocated for this + attribute. + +Arguments: + + None. + +Return Value: + + The number of clusters allocated by this attribute. + +--*/ +{ + ULONG i; + VCN vcn; + LCN lcn; + BIG_INT run_length; + BIG_INT r; + + r = 0; + for (i = 0; QueryExtent(i, &vcn, &lcn, &run_length); i++) { + + if (LCN_NOT_PRESENT == lcn) { + continue; + } + r += run_length; + } + + return r; +} + + +VOID +NTFS_EXTENT_LIST::SetLowestVcn( + IN BIG_INT LowestVcn + ) +/*++ + +Routine Description: + + This method sets the lowest VCN covered by this extent + list. Note that for a sparse file, this is not necessarily + the same as the VCN of the first extent in the list. + +Arguments: + + The lowest VCN mapped by this extent list. Note that this must + be less than or equal to the starting VCN of the first entry + in the list. + +Return Value: + + None. + +--*/ +{ + _LowestVcn = LowestVcn; +} + +VOID +NTFS_EXTENT_LIST::SetNextVcn( + IN BIG_INT NextVcn + ) +/*++ + +Routine Description: + + This method sets the highest VCN covered by this extent + list. Note that for a sparse file, this is not necessarily + the same as the last VCN of the last extent in the list. + +Arguments: + + The highest VCN mapped by this extent list. + +Return Value: + + None. + +--*/ +{ + _NextVcn = NextVcn; +} + + +ULONG +NTFS_EXTENT_LIST::QueryNumberOfExtents( + ) CONST +/*++ + +Routine Description: + + This methods tells the client how many extents are in the + extent list. + +Arguments: + + None. + +Return Value: + + The number of extents in the list. + +--*/ +{ + return FsRtlNumberOfRunsInLargeMcb((PLARGE_MCB)_Mcb); +} diff --git a/untfs/src/frs.cxx b/untfs/src/frs.cxx new file mode 100644 index 0000000..f86b844 --- /dev/null +++ b/untfs/src/frs.cxx @@ -0,0 +1,4147 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + frs.cxx + +Abstract: + + This module contains the member function definitions for the + NTFS_FILE_RECORD_SEGMENT class. This class models File + Record Segments in the NTFS Master File Table; it is the + object through which a file's attributes may be accessed. + +--*/ + +#include "ulib.hxx" + +#include "untfs.hxx" +#include "list.hxx" +#include "numset.hxx" +#include "ifssys.hxx" + +#include "drive.hxx" +#include "mft.hxx" +#include "mftfile.hxx" +#include "attrrec.hxx" +#include "attrib.hxx" +#include "attrlist.hxx" +#include "frs.hxx" +#include "badfile.hxx" +#include "ntfsbit.hxx" +#include "indxtree.hxx" + + +#include "indxroot.hxx" +#include "upcase.hxx" +#include "indxbuff.hxx" + + +DEFINE_CONSTRUCTOR(NTFS_FILE_RECORD_SEGMENT, NTFS_FRS_STRUCTURE); + + + +NTFS_FILE_RECORD_SEGMENT::~NTFS_FILE_RECORD_SEGMENT ( + ) +{ + Destroy(); +} + +VOID +NTFS_FILE_RECORD_SEGMENT::Construct ( + ) +/*++ + +Routine Description: + + Worker method for NTFS_FILE_RECORD_SEGMENT construction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _Mft = NULL; + _AttributeList = NULL; + _ChildIterator = NULL; +} + + +inline +VOID +NTFS_FILE_RECORD_SEGMENT::Destroy2 ( + ) +/*++ + +Routine Description: + + Worker method for NTFS_FILE_RECORD_SEGMENT partial destruction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + // If the child iterator is not NULL, then the + // list of children has been initialized. + + if( _ChildIterator != NULL ) { + + DELETE( _ChildIterator ); + _Children.DeleteAllMembers(); + } + + DELETE(_AttributeList); + _AttributeList = NULL; +} + +VOID +NTFS_FILE_RECORD_SEGMENT::Destroy ( + ) +/*++ + +Routine Description: + + Worker method for NTFS_FILE_RECORD_SEGMENT destruction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _Mft = NULL; + + Destroy2(); +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::Initialize( + IN VCN FileNumber, + IN OUT PNTFS_MASTER_FILE_TABLE Mft + ) +/*++ + +Routine Description: + + Initialize a File Record Segment object. Note that this will not + cause the FRS to be read. + +Arguments: + + FileNumber -- Supplies the FRS's cluster number within the MFT. + Mft -- Supplies the volume MasterFile Table. + +Return Value: + + TRUE upon successful completion + +Notes: + + This class is reinitializable. + +--*/ +{ + Destroy(); + + DebugAssert(Mft); + + _Mft = Mft; + + if( !Mft->GetDataAttribute() || + !_Mem.Initialize() || + !_Children.Initialize() || + (_ChildIterator = _Children.QueryIterator()) == NULL || + !NTFS_FRS_STRUCTURE::Initialize(&_Mem, + Mft->GetDataAttribute(), + FileNumber, + Mft->QueryClusterFactor(), + Mft->QueryVolumeSectors(), + Mft->QueryFrsSize(), + Mft->GetUpcaseTable() ) ) { + Destroy(); + return FALSE; + } + + return TRUE; +} + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::Initialize( + IN VCN FirstFileNumber, + IN ULONG FrsCount, + IN OUT PNTFS_MASTER_FILE_TABLE Mft + ) +/*++ + +Routine Description: + + Initialize a File Record Segment object. Note that this will not + cause the FRS to be read. This is meant to use together with + Initialize(), ReadSet(), and ReadNext(). + +Arguments: + + FirstFileNumber - Supplies the first file number for this FRS block. + FrsCount - Supplies the number of FRS'es in this FRS block. + Mft - Supplies the volume MasterFile Table. + +Return Value: + + TRUE upon successful completion + +Notes: + + This class is reinitializable. + +--*/ +{ + Destroy(); + + DebugAssert(Mft); + + _Mft = Mft; + + if( !Mft->GetDataAttribute() || + !_Mem.Initialize() || + !_Children.Initialize() || + (_ChildIterator = _Children.QueryIterator()) == NULL || + !NTFS_FRS_STRUCTURE::Initialize(&_Mem, + Mft->GetDataAttribute(), + FirstFileNumber, + FrsCount, + Mft->QueryClusterFactor(), + Mft->QueryVolumeSectors(), + Mft->QueryFrsSize(), + Mft->GetUpcaseTable() ) ) { + Destroy(); + return FALSE; + } + + return TRUE; +} + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::Initialize( + ) +/*++ + +Routine Description: + + Partially initialize a File Record Segment object. Note that this + will not cause the FRS to be read. This is meant to use together + with SetFrsData() or ReadFrsData(). + +Arguments: + +Return Value: + + TRUE upon successful completion + +Notes: + + This class is reinitializable. + +--*/ +{ + Destroy2(); + + if (!_Children.Initialize() || + (_ChildIterator = _Children.QueryIterator()) == NULL) { + Destroy(); + return FALSE; + } + + return TRUE; +} + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::Initialize( + IN VCN FileNumber, + IN OUT PNTFS_MFT_FILE MftFile + ) +/*++ + +Routine Description: + + This routine initializes this class to a valid initial state. + +Arguments: + + FileNumber - Supplies the file number for this FRS. + MftFile - Supplies the MFT file. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +Notes: + + The upcase-table may be NULL; in this case, attributes with + names cannot be manipulated until the upcase table is set. + +--*/ +{ + return Initialize(FileNumber, + MftFile->GetMasterFileTable()); +} + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::Initialize( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN LCN StartOfMft, + IN OUT PNTFS_MASTER_FILE_TABLE Mft + ) +/*++ + +Routine Description: + + Initialize a File Record Segment object. Note that this will not + cause the FRS to be read. + +Arguments: + + Drive - Supplies the drive object. + StartOfMft - Supplies the starting cluster for the MFT. + Mft - Supplies the master file table. + +Return Value: + + TRUE upon successful completion + +Notes: + + This class is reinitializable. + +--*/ +{ + Destroy(); + + DebugAssert(Mft); + + _Mft = Mft; + _AttributeList = NULL; + + return _Children.Initialize() && + (_ChildIterator = _Children.QueryIterator()) != NULL && + _Mem.Initialize() && + NTFS_FRS_STRUCTURE::Initialize(&_Mem, + Drive, + StartOfMft, + Mft->QueryClusterFactor(), + Mft->QueryVolumeSectors(), + Mft->QueryFrsSize(), + Mft->GetUpcaseTable()); +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::Create ( + IN USHORT Flags + ) +/*++ + +Routine Description: + + Create (i.e. Format) a file record segment. This is a private + worker method which is called by the other Create methods. It + creates a File Record Segment which has no attribute records. + +Arguments: + + Flags -- Supplies the FILE_xxx flags which should be set in this + File Record Set. (Note that FILE_RECORD_SEGMENT_IN_USE + will also be set, whether or not is is specified.) + +Return value: + + TRUE upon successful completion. + +--*/ +{ + PATTRIBUTE_TYPE_CODE FirstAttribute; + + DebugPtrAssert( _FrsData ); + + memset( _FrsData, 0, (UINT) QuerySize() ); + + _FrsData->Lsn.LowPart = 0; + _FrsData->Lsn.HighPart = 0; + + _FrsData->SequenceNumber = (USHORT) max(QueryFileNumber().GetLowPart(),1); + _FrsData->ReferenceCount = 0; + _FrsData->Flags = FILE_RECORD_SEGMENT_IN_USE | Flags; + _FrsData->BytesAvailable = QuerySize(); + _FrsData->NextAttributeInstance = 0; + + memset( &_FrsData->BaseFileRecordSegment, + 0, + sizeof(MFT_SEGMENT_REFERENCE) ); + + + // Write the 'FILE' signature in the MultiSectorHeader. + + memcpy( _FrsData->MultiSectorHeader.Signature, + "FILE", + 4 ); + + + // Compute the number of Update Sequence Numbers in the + // update array. This number is (see ntos\inc\cache.h): + // + // n/SEQUENCE_NUMBER_STRIDE + 1 + // + // where n is the number of bytes in the protected structure + // (in this case, a cluster). + + _FrsData->MultiSectorHeader.UpdateSequenceArraySize = + (USHORT)(QuerySize()/SEQUENCE_NUMBER_STRIDE + 1); + + // The update sequence array starts at the field + // UpdateArrayForCreateOnly. (In other words, create locates + // it using this field, all other methods locate it using + // the offset that Create computes.) + // + _FrsData->MultiSectorHeader.UpdateSequenceArrayOffset = + FIELD_OFFSET( FILE_RECORD_SEGMENT_HEADER, + UpdateArrayForCreateOnly ); + + _FrsData->FirstAttributeOffset = + QuadAlign( _FrsData->MultiSectorHeader.UpdateSequenceArrayOffset + + _FrsData->MultiSectorHeader.UpdateSequenceArraySize * + sizeof( UPDATE_SEQUENCE_NUMBER ) ); + + _FrsData->SegmentNumberHighPart = (USHORT)QueryFileNumber().GetHighPart(); + _FrsData->SegmentNumberLowPart = QueryFileNumber().GetLowPart(); + + // Make sure that the offset of the first attribute is in range: + // + if( _FrsData->FirstAttributeOffset + sizeof(ULONG) > QuerySize() ) { + + return FALSE; + } + + // Put an END attribute at the first-attribute offset. (Note + // that this attribute doesn't have an attribute header, it just + // consists of an Attribute Type Code of $END.) + // + FirstAttribute = (PATTRIBUTE_TYPE_CODE) + ((PBYTE)_FrsData + + _FrsData->FirstAttributeOffset); + + *FirstAttribute = $END; + + // The first free byte comes after the END attribute, which + // consists of a single ATTRIBUTE_TYPE_CODE. + // + _FrsData->FirstFreeByte = _FrsData->FirstAttributeOffset + + QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE ) ); + + return TRUE; +} + + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::Create ( + IN PCSTANDARD_INFORMATION StandardInformation, + IN USHORT Flags + ) +/*++ + +Routine Description: + + Create (i.e. Format) a base file record segment. The base + file record segment is the primary FRS for a file; it contains + any resident attributes (and hence any indexed attributes) of the + file and the External Attributes List, if any. + + Note that Create will not cause the FRS to be written, only to + be formatted in memory. + +Arguments: + + StandardInformation -- supplies the standard information for the + file record segment. + Flags -- Supplies the FILE_xxx flags which should + be set in this File Record Set. (Note + that FILE_RECORD_SEGMENT_IN_USE will also + be set, whether or not is is specified.) + + +Return value: + + TRUE upon successful completion. + +--*/ +{ + NTFS_ATTRIBUTE StandardInfoAttribute; + + DebugPtrAssert( _FrsData ); + + if( Create( Flags ) && + StandardInfoAttribute.Initialize( GetDrive(), + QueryClusterFactor(), + StandardInformation, + sizeof( STANDARD_INFORMATION ), + $STANDARD_INFORMATION ) && + StandardInfoAttribute.InsertIntoFile( this, NULL ) ) { + + return TRUE; + + } else { + + return FALSE; + } +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::Create ( + IN PCMFT_SEGMENT_REFERENCE BaseSegment, + IN USHORT Flags + ) +/*++ + +Routine Description: + + Create (i.e. Format) a secondary file record segment. A secondary + file record segment contains external attributes; it is referenced + in the file's External Attributes List, which is in the file's + base file record segment. + +Arguments: + + BaseSegment -- supplies a reference to the base file + record segment for this file. + Flags -- Supplies the FILE_xxx flags which should + be set in this File Record Set. (Note + that FILE_RECORD_SEGMENT_IN_USE will also + be set, whether or not is is specified.) + +Return value: + + TRUE upon successful completion. + +--*/ +{ + DebugPtrAssert( _FrsData ); + + if( Create( Flags ) ) { + + memcpy( &_FrsData->BaseFileRecordSegment, + BaseSegment, + sizeof(MFT_SEGMENT_REFERENCE) ); + + return TRUE; + + } else { + + return FALSE; + } +} + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::AddDataAttribute( + IN ULONG InitialSize, + IN OUT PNTFS_BITMAP VolumeBitmap, + IN BOOLEAN Fill, + IN CHAR FillCharacter + ) +/*++ + +Routine Description: + + This method adds a nonresident data attribute to the File Record Segment. + +Arguments: + + InitialSize -- Supplies the number of bytes to allocate for + the data attribute. + VolumeBitmap -- Supplies the volume bitmap. + Fill -- Supplies a flag which, if TRUE, indicates that + the value of the attribute should be set to + the supplied fill character. + FillCharacter -- Supplies a fill character. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + NTFS_ATTRIBUTE DataAttribute; + NTFS_EXTENT_LIST Extents; + + // Initialize the data attribute [type is $DATA, no name] with an + // empty extent list, and then resize it to the desired size. + + if( !Extents.Initialize( 0, 0 ) || + !DataAttribute.Initialize( GetDrive(), + QueryClusterFactor(), + &Extents, + 0, + 0, + $DATA, + NULL ) || + !DataAttribute.Resize( InitialSize, VolumeBitmap ) ) { + + return FALSE; + } + + if( Fill && + !DataAttribute.Fill( 0, FillCharacter ) ) { + + return FALSE; + } + + // Insert the data attribute into this File Record Segment. If that + // operation fails, we need to free up the space allocated by + // resizing the attribute back to zero. + + if( !DataAttribute.InsertIntoFile( this, VolumeBitmap ) ) { + + DataAttribute.Resize( 0, VolumeBitmap ); + return FALSE; + } + + return TRUE; +} + + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::AddFileNameAttribute( + IN PFILE_NAME FileName + ) +/*++ + +Routine Description: + + This method adds a File Name attribute to the File Record Segment. + Note that it assumes that the File Name is indexed. + +Arguments: + + FileName -- Supplies the value of the File Name attribute, which + is a FILE_NAME structure. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + NTFS_ATTRIBUTE FileNameAttribute; + + // Initialize a file-name attribute and insert it into this FRS. + + if( FileNameAttribute.Initialize( GetDrive(), + QueryClusterFactor(), + FileName, + NtfsFileNameGetLength( FileName ), + $FILE_NAME ) ) { + + FileNameAttribute.SetIsIndexed(); + + return( FileNameAttribute.InsertIntoFile( this, NULL ) ); + + } else { + + return FALSE; + } +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::AddAttribute( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name, + IN PCVOID Value, + IN ULONG Length, + IN OUT PNTFS_BITMAP Bitmap, + IN BOOLEAN IsIndexed + ) +/*++ + +Routine Description: + + This method adds an attribute of the specified type to the file. + +Arguments: + + Type -- Supplies the type of the attribute. + Name -- Supplies the name of the attribute--may be NULL, + which is interpreted as no name. + Value -- Supplies a pointer to the attribute's value. May + be NULL if the attribute value length is zero. + Length -- Supplies the length of the attribute value. + Bitmap -- Supplies the volume bitmap. May be NULL, + in which case the attribute cannot be made + nonresident. + IsIndexed -- Supplies a flag which indicates whether the + attribute is indexed. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + NTFS_ATTRIBUTE Attribute; + + // Initialize a resident attribute with the desired + // characteristics. If it is to be indexed, mark + // it as such. + // + if( !Attribute.Initialize( GetDrive(), + QueryClusterFactor(), + Value, + Length, + Type, + Name ) ) { + + return FALSE; + } + + if( IsIndexed ) { + + Attribute.SetIsIndexed(); + } + + // Insert the attribute into the file. If it cannot be inserted + // as a resident attribute, make it non-resident and try again. + // + if( Attribute.InsertIntoFile( this, NULL ) ) { + + // Success! + // + return TRUE; + } + + // Couldn't insert it as a resident attribute; if it isn't + // indexed, make it nonresident and try again. Note that + // we can't make it nonresident if the client did not provide + // a bitmap, and that indexed attributes cannot be made + // nonresident. + // + if( !IsIndexed && + Bitmap != NULL && + Attribute.MakeNonresident( Bitmap ) && + Attribute.InsertIntoFile( this, Bitmap ) ) { + + // Second time lucky. + // + return TRUE; + } + + // Can't insert this attribute into this FRS. If the attribute + // is nonresident, truncate it to zero length to free up the + // space allocated to it. + // + if( !Attribute.IsResident() ) { + + DebugPtrAssert( Bitmap ); + Attribute.Resize( 0, Bitmap ); + } + + // return failure. + // + return FALSE; +} + + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::IsAttributePresent ( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name, + IN BOOLEAN IgnoreExternal + ) +/*++ + +Routine Description: + + This function determines whether a specified attribute is present + in the attributes associated with the File Record Segment. + +Arguments: + + Type -- supplies the type code of the attribute in question. + Name -- supplies the name of the attribute in question. + (may be NULL, in which case the attribute has no name.) + IgnoreExternal -- supplies a flag indicating that the FRS should + not look for external attributes. + +Return Value: + + TRUE if the File Record Segment has an attribute which matches + the type (and name, if given). + +Notes: + + This method assumes that the file record segment is consistent, + and that it has been read. + + Note that we can determine what attribute records are present, + and whether they are unique, without reading the child FRS's, + since the information we need is in the Attribute List. + + The NoExternal flag is provided mainly to allow us to check for + the presence of the ATTRIBUTES_LIST attribute without falling + into infinite recursion. + +--*/ +{ + ULONG CurrentRecordOffset; + NTFS_ATTRIBUTE_RECORD CurrentRecord; + NTFS_ATTRIBUTE AttributeList; + BOOLEAN Found = FALSE; + + DebugPtrAssert( _FrsData ); + + if( !IgnoreExternal && + Type != $ATTRIBUTE_LIST && + (_AttributeList != NULL || + IsAttributePresent( $ATTRIBUTE_LIST, NULL, TRUE )) ) { + + // This File Record Segment has an ATTRIBUTE_LIST attribute, + // and the caller wants to include external attributes, so + // we can traverse that list. + + if( !SetupAttributeList() ) { + + return FALSE; + } + + return( _AttributeList->IsInList( Type, Name ) ); + + } else { + + // Either the caller has asked us to ignore external + // attributes or there is no ATTRIBUTE_LIST attribute, + // or we're looking for the attribute list itself, + // so we'll go through the list of attribute records + // in this File Record Segment. + + CurrentRecordOffset = _FrsData->FirstAttributeOffset; + + while( CurrentRecordOffset < QuerySize() && + CurrentRecord.Initialize( GetDrive(), + (PBYTE)_FrsData + CurrentRecordOffset, + QuerySize() - CurrentRecordOffset ) && + CurrentRecord.QueryTypeCode() != $END ) { + + if( CurrentRecord.IsMatch( Type, Name ) ) { + + Found = TRUE; + break; + } + + // If this record has a zero length, then this FRS + // is corrupt. Otherwise, just go on to the next + // attribute record. + // + if( CurrentRecord.QueryRecordLength() == 0 ) { + Found = FALSE; + break; + } + + CurrentRecordOffset += CurrentRecord.QueryRecordLength(); + } + } + + return Found; +} + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::QueryAttribute ( + OUT PNTFS_ATTRIBUTE Attribute, + OUT PBOOLEAN Error, + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name + ) +/*++ + +Routine Description: + + This function fetches an attribute associated with the + File Record Segment. + + This method cannot be used to fetch the ATTRIBUTE_LIST attribute. + +Arguments: + + Attribute -- Receives (ie. is initialized to) the attribute. Note + that this parameter may be uninitialized on entry, and + may be left uninitialized if this method fails. + Error -- Receives TRUE if the method fails because of an error. + Type -- Supplies the type of the desired attribute + Name -- Supplies the name of the desired attribute (NULL if + the attribute has no name). + +Return Value: + + TRUE upon successful completion. + +Notes: + + If the method returns TRUE, *Error should be ignored. If it + returns FALSE, *Error will be set to TRUE if the failure resulted + from an error (out of memory, corrupt structure); otherwise, the + caller may assume that the attribute is not present. + + This method will check both internal and external attributes, + reading child File Record Segments as necessary to access their + attribute records. + +--*/ +{ + MFT_SEGMENT_REFERENCE SegmentReference; + NTFS_ATTRIBUTE_RECORD Record; + PNTFS_FILE_RECORD_SEGMENT ChildFrs = NULL; + VCN TargetFileNumber; + ULONG Index; + ATTRIBUTE_TYPE_CODE FetchType; + VCN LowestVcn; + DSTRING FetchName; + USHORT Instance; + PNTFS_EXTENT_LIST backup_extent_list = NULL; + + DebugPtrAssert( Attribute ); + DebugPtrAssert( Error ); + + DebugPtrAssert( _FrsData ); + + // Assume innocent until proven guilty: + + *Error = FALSE; + + // This method cannot be used to fetch the ATTRIBUTE_LIST + // attribute. + + if( Type == $ATTRIBUTE_LIST ) { + + *Error = TRUE; + return FALSE; + } + + if( !IsAttributePresent( Type, Name, FALSE ) ) { + + // there is no matching attribute. + + return FALSE; + } + + // Now that we've determined that the attribute is present, + // this method can only fail because of an error. + + *Error = TRUE; + + + if( !SetupAttributeList() ) { + + return FALSE; + } + + + // Get the TargetFileNumber. + + if ( _AttributeList ) { + + if (!_AttributeList->QueryExternalReference( Type, + &SegmentReference, + &Index, + Name )) { + + return FALSE; + } + + // We've found the first entry in the Attribute List + // for this attribute. We'll use that entry to + // initialize the attribute object. But first, + // we have to find it... + + TargetFileNumber.Set( SegmentReference.LowPart, + (LONG) SegmentReference.HighPart ); + + } else { + + TargetFileNumber = QueryFileNumber(); + } + + + // Get the first attribute record. + + if ( TargetFileNumber == QueryFileNumber() ) { + + if (!QueryAttributeRecord(&Record, Type, Name)) { + + return FALSE; + } + + } else { + + // The record we want is in a child record segment. + // Fetch the child. (Note that SetupChild will construct + // an FRS for the child and read it, if it's not already + // in the list of children.) + + if( (ChildFrs = SetupChild( TargetFileNumber )) == NULL ) { + + return FALSE; + } + + if (!ChildFrs->QueryAttributeRecord(&Record, Type, Name)) { + + return FALSE; + } + } + + + // Initialize the Attribute with the first attribute record. + + if ( !Attribute->Initialize( GetDrive(), + QueryClusterFactor(), + &Record) ) { + return FALSE; + } + + if( Attribute->IsResident() ) { + + // A resident attribute can only have one attribute record; + // since we've found it, we can return now. + // + *Error = FALSE; + return TRUE; + } + + // Add any other attribute records to the attribute. + + + if (_AttributeList) { + + ATTR_LIST_CURR_ENTRY Entry; + ULONG i = 0; + + Entry.CurrentEntry = NULL; + while (_AttributeList->QueryNextEntry(&Entry, + &FetchType, + &LowestVcn, + &SegmentReference, + &Instance, + &FetchName)) { + if (i++ == Index) { + while (_AttributeList->QueryNextEntry(&Entry, + &FetchType, + &LowestVcn, + &SegmentReference, + &Instance, + &FetchName) && + FetchType == Type && + ((!Name && !FetchName.QueryChCount()) || + (Name && !Name->Strcmp(&FetchName)))) { + + TargetFileNumber.Set( SegmentReference.LowPart, + (LONG) SegmentReference.HighPart ); + + // Get attribute record from file record segment. + + if ( TargetFileNumber == QueryFileNumber() ) { + + if (!QueryAttributeRecord(&Record, Type, Name)) { + + DELETE(backup_extent_list); + + return FALSE; + } + + } else { + + // The record we want is in a child record segment. + // Fetch the child. (Note that SetupChild will construct + // an FRS for the child and read it, if it's not already + // in the list of children.) + // + if( (ChildFrs = SetupChild( TargetFileNumber )) == NULL ) { + + DELETE(backup_extent_list); + + return FALSE; + + } + + if (!ChildFrs->QueryAttributeRecord(&Record, Type, Name)) { + + DELETE(backup_extent_list); + + return FALSE; + } + } + + + // Add attribute record to attribute. + + if (!Attribute->AddAttributeRecord(&Record, &backup_extent_list)) { + + DELETE(backup_extent_list); + DebugAbort("Couldn't do an Add attribute record."); + return FALSE; + } + } + break; + } + } + } + + *Error = FALSE; + DELETE(backup_extent_list); + return TRUE; +} + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::QueryFileSizes ( + OUT PBIG_INT AllocatedLength, + OUT PBIG_INT FileSize, + OUT PBOOLEAN Error + ) +/*++ + +Routine Description: + + This function fetches the allocated length and file size of a + file associated with a base File Record Segment. + +Arguments: + + AllocatedLength -- receives the allocated length of the file. + FileSize -- receives the valid length of the file. + Error -- receives TRUE if the method fails because of an error. + +Return Value: + + TRUE upon successful completion. + +Notes: + + If the method returns TRUE, *Error should be ignored. If it + returns FALSE, *Error will be set to TRUE if the failure resulted + from an error (out of memory, corrupt structure); otherwise, the + caller may assume that the attribute is not present. + + This method will check both internal and external attributes, + reading child File Record Segments as necessary to access their + attribute records. + + Please note that the file size and allocated length of the file + is entire in the base frs or the first child frs. There is no + need to read in all the child frs in order to compute the desired + values. + +--*/ +{ + NTFS_ATTRIBUTE Attribute; + BIG_INT TotalAllocated; + MFT_SEGMENT_REFERENCE SegmentReference; + NTFS_ATTRIBUTE_RECORD Record; + PNTFS_FILE_RECORD_SEGMENT ChildFrs = NULL; + VCN TargetFileNumber; + ULONG Index; + VCN LowestVcn; + DSTRING FetchName; + + DebugPtrAssert( Error ); + + DebugPtrAssert( _FrsData ); + + // Assume innocent until proven guilty: + + *Error = FALSE; + + // This method cannot be used to fetch the ATTRIBUTE_LIST + // attribute. + + if( !IsAttributePresent( $DATA, NULL, FALSE ) ) { + + // there is no matching attribute. + + return FALSE; + } + + // Now that we've determined that the attribute is present, + // this method can only fail because of an error. + + *Error = TRUE; + + + if( !SetupAttributeList() ) { + + return FALSE; + } + + + // Get the TargetFileNumber. + + if ( _AttributeList ) { + + if (!_AttributeList->QueryExternalReference( $DATA, + &SegmentReference, + &Index, + NULL )) { + + return FALSE; + } + + // We've found the first entry in the Attribute List + // for this attribute. We'll use that entry to + // initialize the attribute object. But first, + // we have to find it... + + TargetFileNumber.Set( SegmentReference.LowPart, + (LONG) SegmentReference.HighPart ); + + } else { + + TargetFileNumber = QueryFileNumber(); + } + + + // Get the first attribute record. + + if ( TargetFileNumber == QueryFileNumber() ) { + + if (!QueryAttributeRecord(&Record, $DATA, NULL)) { + + return FALSE; + } + + } else { + + // The record we want is in a child record segment. + // Fetch the child. (Note that SetupChild will construct + // an FRS for the child and read it, if it's not already + // in the list of children.) + + if( (ChildFrs = SetupChild( TargetFileNumber )) == NULL ) { + + return FALSE; + } + + if (!ChildFrs->QueryAttributeRecord(&Record, $DATA, NULL)) { + + return FALSE; + } + } + + Record.QueryValueLength(FileSize, + AllocatedLength, + NULL, + &TotalAllocated); + + // for uncompressed file, the total allocated length does + // not exist + + if (Record.IsResident()) { + + *AllocatedLength = QuadAlign(AllocatedLength->GetLowPart()); + + } else if((Record.QueryFlags() & (ATTRIBUTE_FLAG_COMPRESSION_MASK | + ATTRIBUTE_FLAG_SPARSE))) { + + // for compressed file, the allocated length is the + // total allocated length + *AllocatedLength = TotalAllocated; + + } + + *Error = FALSE; + return TRUE; +} + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::QueryAttributeByOrdinal ( + OUT PNTFS_ATTRIBUTE Attribute, + OUT PBOOLEAN Error, + IN ATTRIBUTE_TYPE_CODE Type, + IN ULONG Ordinal + ) +/*++ + +Routine Description: + + This method returns the n-th attribute of the specified + type. Note that it ignores attribute names (but, of course, + the client can query the name of the returned attribute from + that attribute). + +Arguments: + + Attribute -- Receives (ie. is initialized to) the attribute. Note + that this parameter may be uninitialized on entry, and + may be left uninitialized if this method fails. + Error -- Receives TRUE if the method fails because of an error. + Type -- Supplies the type of the desired attribute + Ordinal -- Supplies the (zero-based) ordinal number of the + attribute to return. + +Return Value: + + TRUE upon successful completion. + + If this method succeeds, the client should ignore *Error. + + If this method fails because of error, *Error will be set; if + this method returns FALSE and *Error is FALSE, the client may + assume that there is no such matching attribute. + +--*/ +{ + NTFS_ATTRIBUTE_RECORD CurrentRecord; + MFT_SEGMENT_REFERENCE SegmentReference, NextSegmentReference; + DSTRING Name; + VCN LowestVcn; + VCN TargetFileNumber; + ATTRIBUTE_TYPE_CODE CurrentType; + + PVOID CurrentRecordData; + PNTFS_FILE_RECORD_SEGMENT TargetFrs = NULL; + + USHORT InstanceTag, NextInstanceTag; + + + DebugPtrAssert( Attribute ); + DebugPtrAssert( Error ); + + // If the attribute list is present, force it into memory. + // + if( !SetupAttributeList() ) { + + *Error = TRUE; + return FALSE; + } + + + if( _AttributeList != NULL ) { + + ATTR_LIST_CURR_ENTRY Entry; + + // Spin through the attribute list until we find the nth + // entry with the requested type and a LowestVcn of zero. + // + Entry.CurrentEntry = NULL; + while( TRUE ) { + + if( !_AttributeList->QueryNextEntry( &Entry, + &CurrentType, + &LowestVcn, + &SegmentReference, + &InstanceTag, + &Name ) ) { + + // Out of entries in the attribute list, so there + // is no matching attribute type. + + *Error = FALSE; + return FALSE; + } + + if( CurrentType == Type && LowestVcn == 0 ) { + + // This entry has the desired type code; check + // to see if we want to skip it or grab it. + // + if( Ordinal == 0 ) { + + // Found the entry we want. + // + break; + + } else { + + // Skip this one. + // + Ordinal--; + } + } + } + + // Now we have an entry for the attribute we want. If there's + // only one entry for this attribute, we can initialize an + // attribute with that record and return; if there are multiple + // + // If the next entry does not have a LowestVcn of zero, it + // is another entry for this attribute. Note that we need + // SegmentReference and Instance tag for later use. + // + + if( _AttributeList->QueryNextEntry( &Entry, + &CurrentType, + &LowestVcn, + &NextSegmentReference, + &NextInstanceTag, + &Name ) && + CurrentType == Type && + LowestVcn != 0 ) { + + // This is a multi-record attribute, which means it is + // uniquely identified by Type and Name. + // + return( QueryAttribute( Attribute, + Error, + Type, + &Name ) ); + + } else { + + // There are no more entries for this attribute, + // so we can initialize an the attribute with + // this record. We'll let QueryAttributeByTag + // do the work for us. + // + TargetFileNumber.Set( SegmentReference.LowPart, + (LONG) SegmentReference.HighPart ); + + if( TargetFileNumber == QueryFileNumber() ) { + + // The record is in this FRS. + // + TargetFrs = this; + + } else { + + // The record we want is in a child FRS; get that + // child and squeeze the attribute out of it. + // + if( (TargetFrs = SetupChild( TargetFileNumber)) == NULL ) { + + // Something is wrong--we can't get the child. + // + + *Error = TRUE; + return FALSE; + } + } + + + if( !TargetFrs->QueryAttributeByTag( Attribute, + Error, + InstanceTag ) ) { + + // We know the attribute is there, but we can't + // get it. + // + *Error = TRUE; + return FALSE; + + } else { + + return TRUE; + } + } + + } else { + + // This File Record Segment does not have an attribute list, + // so we only have to grope through this FRS. + + // First, skip over attribute records with a type-code + // less than the one we're looking for: + + CurrentRecordData = NULL; + *Error = FALSE; + + do { + + if( (CurrentRecordData = + GetNextAttributeRecord( CurrentRecordData, + NULL, + Error )) == NULL || + *Error ) { + + // No more, or an error was found. + return FALSE; + } + + if( !CurrentRecord.Initialize( GetDrive(), CurrentRecordData ) ) { + + *Error = TRUE; + return FALSE; + } + + } while( CurrentRecord.QueryTypeCode() != $END && + CurrentRecord.QueryTypeCode() < Type ); + + if( CurrentRecord.QueryTypeCode() == $END || + CurrentRecord.QueryTypeCode() > Type ) { + + // There are no attributes of the specified type in + // this FRS. Note that *Error has already been set + // to FALSE. + + return FALSE; + } + + // Now step through the attributes of this type code to + // find the one we want. + + while( Ordinal != 0 ) { + + // In determining the ordinal of an attribute, we only + // count attribute records which are resident or have + // a LowestVcn of zero. + + if( CurrentRecord.IsResident() || + CurrentRecord.QueryLowestVcn() == 0 ) { + + Ordinal--; + } + + if( (CurrentRecordData = + GetNextAttributeRecord( CurrentRecordData, + NULL, + Error )) == NULL || + *Error ) { + + // No more, or an error was found. + return FALSE; + } + + if( !CurrentRecord.Initialize( GetDrive(), CurrentRecordData ) ) { + + *Error = TRUE; + return FALSE; + } + + if( CurrentRecord.QueryTypeCode() == $END || + CurrentRecord.QueryTypeCode() > Type ) { + + // Ran out of matching records. + + return FALSE; + } + } + + // We've found our baby. Initialize the attribute and return. + + if( CurrentRecord.IsResident() ) { + + // Since this attribute record is resident, it is + // the only attribute record for this attribute, and + // so it suffices to initialize the attribute with + // this record. + + if( Attribute->Initialize( GetDrive(), + QueryClusterFactor(), + &CurrentRecord ) ) { + + // Everything is just fine. + + return TRUE; + + } else { + + // Foiled at the last minute by some dastardly error. + + *Error = TRUE; + return FALSE; + } + + } else { + + // Since there may be other attribute records associated + // with this attribute, we have to invoke QueryAttribute + // to do our work for us. Get the name from the attribute + // record, and then query this FRS for the attribute with + // the requested type and that name. + + if( !CurrentRecord.QueryName( &Name ) ) { + + *Error = TRUE; + return FALSE; + } + + return( QueryAttribute( Attribute, + Error, + Type, + &Name ) ); + } + + } +} + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::QueryAttributeByTag ( + OUT PNTFS_ATTRIBUTE Attribute, + OUT PBOOLEAN Error, + IN ULONG Tag + ) +/*++ + +Routine Description: + + This method initializes an attribute based on the single attribute + record with the specified instance tag. Note that it only examines + records in this File Record Segment; it does not look at child FRS's. + +Arguments: + + Attribute -- Receives (ie. is initialized to) the attribute + in question. Note that this parameter may be + uninitialized on entry, and may be left in that + state if this method fails. + Error -- Receives TRUE if the method fails because of + an error. + Tag -- Supplies the attribute record instance tag of + the desired record. + +--*/ +{ + NTFS_ATTRIBUTE_RECORD CurrentRecord; + PVOID CurrentRecordData = NULL; + BOOLEAN Found = FALSE; + + + while( !Found ) { + + CurrentRecordData = GetNextAttributeRecord( CurrentRecordData ); + + if( CurrentRecordData == NULL ) { + + // No more records. + // + *Error = FALSE; + return FALSE; + } + + if( !CurrentRecord.Initialize( GetDrive(), CurrentRecordData ) ) { + + // Error initializing object. + // + *Error = TRUE; + return FALSE; + } + + if( CurrentRecord.QueryInstanceTag() == Tag ) { + + Found = TRUE; + } + } + + if( !Attribute->Initialize( GetDrive(), + QueryClusterFactor(), + &CurrentRecord ) ) { + + *Error = TRUE; + return FALSE; + } + + return TRUE; +} + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::PurgeAttribute ( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name, + IN BOOLEAN IgnoreExternal + ) +/*++ + +Routine Description: + + This method removes all attribute records for the given attribute + type and name from the File Record Segment and its children. + +Arguments: + + Type -- supplies the type of the attribute to purge + Name -- supplies the name of the attribute to purge (NULL if + the attribute has no name). + IgnoreExternal -- supplies a flag that, if TRUE, indicates we + should ignore the attribute list and only delete + matching records found in this File Record Segment + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + NTFS_ATTRIBUTE_RECORD CurrentRecord; + MFT_SEGMENT_REFERENCE SegmentReference; + PNTFS_FILE_RECORD_SEGMENT ChildFrs; + VCN TargetFileNumber; + ULONG EntryIndex; + ULONG CurrentRecordOffset; + ULONG NextRecordOffset; + + if( !IgnoreExternal && + Type != $ATTRIBUTE_LIST && + (_AttributeList != NULL || + IsAttributePresent($ATTRIBUTE_LIST, NULL, TRUE) ) ) { + + // This File Record Segment has an attribute list, so + // we should consult it. + + if( !SetupAttributeList() ) { + + return FALSE; + } + + + while( _AttributeList->QueryExternalReference( Type, + &SegmentReference, + &EntryIndex, + Name ) ) { + + TargetFileNumber.Set( SegmentReference.LowPart, + (LONG) SegmentReference.HighPart ); + + if( TargetFileNumber == QueryFileNumber() ) { + + // The record we want to delete is in this File + // Record Segment. The easiest way to get at it + // is to recurse back into this function with + // IgnoreExternal equal to TRUE (which prevents + // further recursion). + + PurgeAttribute( Type, Name, TRUE ); + + } else { + + // The record we want to delete is in a child record + // segment. Note that SetupChild will construct and + // read a new FRS if the child isn't already in the list. + + if( (ChildFrs = SetupChild( TargetFileNumber )) == NULL ) { + + return FALSE; + } + + // Now we've got the child; purge any matching attributes + // from it. (It's OK if we purge more than one; we'll + // catch up on later iterations.) + + if( !ChildFrs->PurgeAttribute( Type, Name ) ) { + + return FALSE; + } + } + + _AttributeList->DeleteEntry( EntryIndex ); + } + + } else { + + // Either there is no Attribute List, or we're deleting + // the Attribute List itself, or we've been instructed to + // ignore it, so we can just go through this File Record + // Segment and blow away any matching records we find. + + CurrentRecordOffset = _FrsData->FirstAttributeOffset; + + while( CurrentRecordOffset < QuerySize() && + CurrentRecord.Initialize( GetDrive(), + (PBYTE)_FrsData + CurrentRecordOffset, + QuerySize() - CurrentRecordOffset ) && + CurrentRecord.QueryTypeCode() != $END ) { + + if( CurrentRecord.QueryRecordLength() == 0 ) { + + return FALSE; + } + + if( CurrentRecord.IsMatch( Type, Name ) ) { + + // This record matches, so away it goes! + + NextRecordOffset =CurrentRecordOffset + + CurrentRecord.QueryRecordLength(); + + DebugAssert( NextRecordOffset < QuerySize() ); + + _FrsData->FirstFreeByte -= CurrentRecord.QueryRecordLength(); + + memmove( (PBYTE)_FrsData + CurrentRecordOffset, + (PBYTE)_FrsData + NextRecordOffset, + (UINT) (QuerySize() - NextRecordOffset) ); + + + + // Note that, since we've brought the next record to + // CurrentRecordOffset, there's no need to adjust + // CurrentRecordOffset. + + } else { + + // This record doesn't match, so we won't purge it. + + CurrentRecordOffset += CurrentRecord.QueryRecordLength(); + } + } + } + + return TRUE; +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::DeleteResidentAttribute( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name, + IN PCVOID Value, + IN ULONG ValueLength, + OUT PBOOLEAN Deleted, + IN BOOLEAN IgnoreExternal + ) +/*++ + +Routine Description: + + This method will delete any attribute record associated with the + File Record Segment which represents a resident attribute of the + specified type and name with a value equal to the supplied value. + +Arguments: + + Type -- Supplies the attribute type code. + Name -- Supplies the attribute name. May be NULL, which + indicates that the attribute has no name. + Value -- Supplies the value of the attribute to delete. + ValueLength -- Supplies the length of the value. + IgnoreExternal -- Supplies a flag which indicates, if TRUE, + that this method should only examine records + in this FRS (ie. it should ignore external + attributes). + Deleted -- Receives TRUE if the method found and deleted + a matching record. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + DSTRING CurrentName; + NTFS_ATTRIBUTE_RECORD CurrentRecord; + MFT_SEGMENT_REFERENCE SegmentReference, FoundSegmentReference; + + PNTFS_FILE_RECORD_SEGMENT ChildFrs; + + ATTRIBUTE_TYPE_CODE CurrentType; + VCN LowestVcn, TargetFileNumber; + USHORT Instance, FoundInstance; + BOOLEAN IsIndexed; + + DebugPtrAssert( Value ); + DebugPtrAssert( Deleted ); + + *Deleted = FALSE; + + if( !SetupAttributeList() ) { + + return FALSE; + } + + if( !IgnoreExternal && + _AttributeList != NULL ) { + + // First, traverse the list to force all the children into + // memory. + // + + ATTR_LIST_CURR_ENTRY Entry; + + Entry.CurrentEntry = NULL; + while (_AttributeList->QueryNextEntry( &Entry, + &CurrentType, + &LowestVcn, + &SegmentReference, + &Instance, + &CurrentName )) { + + TargetFileNumber.Set( SegmentReference.LowPart, + (LONG)SegmentReference.HighPart ); + + if( TargetFileNumber != QueryFileNumber() && + SetupChild( TargetFileNumber ) == NULL ) { + + return FALSE; + } + } + + // Now traverse the list of children, trying to delete + // the record in question. + // + _ChildIterator->Reset(); + + while( (ChildFrs = (PNTFS_FILE_RECORD_SEGMENT) + _ChildIterator->GetNext()) != NULL ) { + + if( !ChildFrs->DeleteResidentAttributeLocal( Type, + Name, + Value, + ValueLength, + Deleted, + &IsIndexed, + &Instance ) ) { + + return FALSE; + } + + if( *Deleted ) { + + // We found our victim. Remember the segment reference + // and instance tag, so we can delete the attribute list + // entry. + // + FoundSegmentReference = ChildFrs->QuerySegmentReference(); + FoundInstance = Instance; + + break; + } + } + + if( !*Deleted ) { + + // We didn't find the target attribute record in any + // of the children; see if it's in this FRS itself. + // + if( !DeleteResidentAttributeLocal( Type, + Name, + Value, + ValueLength, + Deleted, + &IsIndexed, + &Instance ) ) { + + return FALSE; + } + + if( *Deleted ) { + + // Found it in this FRS. + // + FoundSegmentReference = QuerySegmentReference(); + FoundInstance = Instance; + } + } + + if( *Deleted ) { + + // We found and deleted a matching attribute record. + // Find the corresponding entry in the attribute list + // and delete it. Note that the Segment Reference and + // Instance Tag are sufficient to identify that entry. + // + + ATTR_LIST_CURR_ENTRY Entry; + + Entry.CurrentEntry = NULL; + while (_AttributeList->QueryNextEntry( &Entry, + &CurrentType, + &LowestVcn, + &SegmentReference, + &Instance, + &CurrentName )) { + + if( SegmentReference == FoundSegmentReference && + Instance == FoundInstance ) { + + _AttributeList->DeleteCurrentEntry( &Entry ); + break; + } + } + } + + } else { + + // This FRS does not have an attribute list (or the client + // wants to ignore it), so we just have to examine the records + // in this FRS. + // + if( !DeleteResidentAttributeLocal( Type, + Name, + Value, + ValueLength, + Deleted, + &IsIndexed, + &Instance ) ) { + + return FALSE; + } + + } + + // If we successfully deleted an indexed attribute record + // from a Base File Record Segment, we need to adjust the + // reference count. + // + if( *Deleted && IsBase() && IsIndexed ) { + SetReferenceCount(QueryReferenceCount() - 1); + } + + return TRUE; +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::DeleteResidentAttributeLocal( + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name OPTIONAL, + IN PCVOID Value, + IN ULONG ValueLength, + OUT PBOOLEAN Deleted, + OUT PBOOLEAN IsIndexed, + OUT PUSHORT InstanceTag + ) +/*++ + +Routine Description: + + This method deletes a resident attribute from the FRS. Note + that it will not affect external attributes. + +Arguments: + + Type -- Supplies the attribute type code. + Name -- Supplies the attribute name. May be NULL, which + indicates that the attribute has no name. + Value -- Supplies the value of the attribute to delete. + ValueLength -- Supplies the length of the value. + Deleted -- Receives TRUE if the method found and deleted + a matching record. + IsIndexed -- Receives TRUE if the deleted record was indexed. + If no matching record was found, *Deleted is + FALSE and *IsIndexed is undefined. + InstanceTag -- Receives the instance tag of the deleted record; + if no matching record was found, *Deleted is + FALSE and *InstanceTag is undefined. + +Return Value: + + TRUE upon successful completion. + + +--*/ +{ + NTFS_ATTRIBUTE_RECORD CurrentRecord; + PVOID CurrentRecordData; + PCWSTR NameBuffer = NULL; + ULONG NameLength; // Length of Name in characters + + DebugPtrAssert( Value ); + DebugPtrAssert( Deleted ); + + *Deleted = FALSE; + + // Get the search name into a WSTR buffer, for + // easier comparison: + // + if( Name == NULL ) { + + NameLength = 0; + + } else { + + NameLength = Name->QueryChCount(); + NameBuffer = Name->GetWSTR(); + } + + // Go through the attribute records in this FRS until + // we find one that matches. If we find one, it is + // unique (unless the FRS is corrupt), so we delete + // it and return. + + CurrentRecordData = NULL; + + while( (CurrentRecordData = + GetNextAttributeRecord( CurrentRecordData )) != NULL ) { + + if( !CurrentRecord.Initialize( GetDrive(), CurrentRecordData ) ) { + + return FALSE; + } + + // This record matches if the type codes are the same, + // the record is resident, the names are the same length + // and compare exactly, and the values are the same length + // and compare exactly. + // + if( CurrentRecord.QueryTypeCode() == Type && + CurrentRecord.IsResident() && + CurrentRecord.QueryNameLength() == NameLength && + memcmp( NameBuffer, + CurrentRecord.GetName(), + NameLength * sizeof(WCHAR) ) == 0 && + CurrentRecord.QueryResidentValueLength() == ValueLength && + memcmp( CurrentRecord.GetResidentValue(), + Value, + ValueLength ) == 0 ) { + + // This is the record we want to delete. + // + *IsIndexed = CurrentRecord.IsIndexed(); + *InstanceTag = CurrentRecord.QueryInstanceTag(); + + DeleteAttributeRecord( CurrentRecordData ); + *Deleted = TRUE; + + return TRUE; + } + } + + return TRUE; +} + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::SetupAttributeList( + ) +/*++ + +Routine Description: + + This method makes sure that the attribute list, if present, + has been properly set up. If the attribute list is present + but cannot be initialized, this method returns FALSE. + +Arguments: + + None. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + if ( _AttributeList == NULL && + IsAttributePresent( $ATTRIBUTE_LIST, NULL, TRUE ) ) { + + if (!(_AttributeList = NEW NTFS_ATTRIBUTE_LIST) || + !QueryAttributeList(_AttributeList) || + !_AttributeList->ReadList()) { + + // This File Record Segment has an Attribute List, and I + // can't get it. Return failure. + + DELETE(_AttributeList); + return FALSE; + } + } + + return TRUE; +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::CreateAttributeList( + OUT PNTFS_ATTRIBUTE_LIST AttributeList + ) +/*++ + +Routine Description: + + This method generates an Attribute List Attribute for this + File Record Segment. + +Arguments: + + AttributeList - Returns a newly-create Attribute List. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +Notes: + + It is an error to create an Attribute List Attribute for a + File Record Segment that already has one. + +--*/ +{ + NTFS_ATTRIBUTE_RECORD CurrentRecord; + MFT_SEGMENT_REFERENCE SegmentReference; + ULONG CurrentRecordOffset; + DSTRING CurrentName; + + // Construct an attribute list object: + + if( !AttributeList->Initialize( GetDrive(), + QueryClusterFactor(), + GetUpcaseTable() ) ) { + + return FALSE; + } + + // Walk through the record entries in this File Record Segment, + // adding an entry to the Attribute List for each record we find. + // Note that we can use the same Segment Reference for every entry, + // since they are all in this File Record Segment. + + SegmentReference = QuerySegmentReference(); + + CurrentRecordOffset = _FrsData->FirstAttributeOffset; + + while( CurrentRecordOffset < QuerySize() && + CurrentRecord.Initialize( GetDrive(), + (PBYTE)_FrsData + CurrentRecordOffset, + QuerySize() - CurrentRecordOffset ) && + CurrentRecord.QueryTypeCode() != $END ) { + + if( CurrentRecord.QueryRecordLength() == 0 ) { + + // Corrupt FRS. + // + return FALSE; + } + + if (!CurrentRecord.QueryName(&CurrentName) || + !AttributeList->AddEntry( CurrentRecord.QueryTypeCode(), + CurrentRecord.QueryLowestVcn(), + &SegmentReference, + CurrentRecord.QueryInstanceTag(), + &CurrentName ) ) { + + return FALSE; + } + + CurrentRecordOffset += CurrentRecord.QueryRecordLength(); + } + + return TRUE; +} + + +PVOID +GetBiggestLocalAttributeRecord( + IN PNTFS_FRS_STRUCTURE Frs + ) +/*++ + +Routine Description: + + This routine returns the biggest attribute record in this + file record segment. + +Arguments: + + Frs - Supplies a Frs. + +Return Value: + + A pointer to the biggest attribute record or NULL. + +--*/ +{ + PATTRIBUTE_RECORD_HEADER pattr, biggest; + + biggest = NULL; + while (biggest = (PATTRIBUTE_RECORD_HEADER) + Frs->GetNextAttributeRecord(biggest)) { + + if (biggest->TypeCode != $STANDARD_INFORMATION) { + break; + } + } + + if (!biggest) { + return NULL; + } + + pattr = biggest; + while (pattr = (PATTRIBUTE_RECORD_HEADER) + Frs->GetNextAttributeRecord(pattr)) { + + if (pattr->TypeCode != $STANDARD_INFORMATION && + pattr->RecordLength > biggest->RecordLength) { + biggest = pattr; + } + } + + return biggest; +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::SaveAttributeList( + PNTFS_BITMAP VolumeBitmap + ) +/*++ + +Routine Description: + + This method instructs the File Record Segment to save its + attribute record list. + +Arguments: + + VolumeBitmap -- supplies the volume bitmap. + +Notes: + + If _AttributesList is NULL, then the File Record Segment has not + modified its attribute list (if it has one) and therefore does + not need to save it. + + If this method fails, it will leave the File Record Segment in + a consistent state (it will leave the old attribute list, if any, + in place). + +--*/ +{ + NTFS_ATTRIBUTE_RECORD OldAttributeListRecord; + NTFS_ATTRIBUTE_RECORD TemporaryRecord; + BOOLEAN InsertSucceeded, OldPresent; + PATTRIBUTE_RECORD_HEADER Biggest; + ATTRIBUTE_TYPE_CODE type; + VCN lowest_vcn; + MFT_SEGMENT_REFERENCE seg_ref; + USHORT instance; + DSTRING name, tmp_name; + BOOLEAN found_entry; + + + if( _AttributeList == NULL ) { + + return TRUE; + } + + // Note that there is no attribute list entry for the + // attribute list's own attribute record. + // + // Write the attribute list. + // + if( !_AttributeList->WriteList( VolumeBitmap ) ) { + + return FALSE; + } + + // We'll hedge our bets by squirreling away a copy of the old + // attribute list's attribute record, if it exists. Note that + // this is a two-step process; first, we query the attribute + // list's record from the File Record Segment; however, since + // this record's data is actually owned by the File Record Segment, + // we need to copy it into a record that has its own data. + + if( IsAttributePresent( $ATTRIBUTE_LIST, NULL, TRUE ) ) { + + OldPresent = TRUE; + + if( !QueryAttributeRecord(&TemporaryRecord, $ATTRIBUTE_LIST) || + !OldAttributeListRecord.Initialize( GetDrive(), + (PVOID) TemporaryRecord.GetData(), + TemporaryRecord.QueryRecordLength(), + TRUE ) ) { + + // This File Record Segment has an attribute list record, + // but we weren't able to copy it to a safe place. Stop + // right here, instead of going forward into a mess from + // which we can't recover. + + return FALSE; + } + + } else { + + OldPresent = FALSE; + } + + + // Now that we have our own copy of the old record (if it existed), + // we can safely delete it from the File Record Segment. We could + // rely on _AttributeList->InsertIntoFile to do that for us, but + // that would complicate our error handling further down. + + if( !PurgeAttribute( $ATTRIBUTE_LIST ) ) { + + return FALSE; + } + + + // Now we try every trick we've got to insert the new attribute + // list into the File Record Segment. + + InsertSucceeded = FALSE; + + while( !InsertSucceeded ) { + + InsertSucceeded = _AttributeList->InsertIntoFile( this, + VolumeBitmap ); + + if( !InsertSucceeded ) { + + // We weren't able to insert the attribute. Try different + // strategems to jam it in. First, if the attribute list + // is resident, we can make it non-resident. + + if( _AttributeList->IsResident() ) { + + if( !_AttributeList->MakeNonresident( VolumeBitmap ) ) { + + // We failed trying to make the attribute list + // nonresident. Give up. + + break; + } + + } else { + + // It's nonresident, so we have to move to our next + // contingency plan: start moving records out of the + // base File Record Segment and into children. + + // Find the biggest attribute record in the base + // and then eliminate it from the attribute list + // and the base file record segment but save it. + + Biggest = (PATTRIBUTE_RECORD_HEADER) + GetBiggestLocalAttributeRecord(this); + if (!Biggest || + !TemporaryRecord.Initialize(GetDrive(), + Biggest, + Biggest->RecordLength, + TRUE)) { + + // Serious problems. + break; + } + + found_entry = FALSE; + + ATTR_LIST_CURR_ENTRY entry; + + entry.CurrentEntry = NULL; + while (_AttributeList->QueryNextEntry(&entry, + &type, + &lowest_vcn, + &seg_ref, + &instance, + &name)) { + + if (type == TemporaryRecord.QueryTypeCode() && + lowest_vcn == TemporaryRecord.QueryLowestVcn() && + seg_ref == QuerySegmentReference() && + instance == TemporaryRecord.QueryInstanceTag() && + TemporaryRecord.QueryName(&tmp_name) && + !tmp_name.Strcmp(&name)) { + + _AttributeList->DeleteCurrentEntry(&entry); + found_entry = TRUE; + break; + } + } + + if (found_entry) { + DeleteAttributeRecord(Biggest); + } else { + DebugAbort("Could not find attribute list entry for big"); + break; + } + + // Now just pull an insert external on this record to + // finish the task. + + if (!InsertExternalAttributeRecord(&TemporaryRecord) || + !_AttributeList->WriteList(VolumeBitmap)) { + + // Out of memory or out of disk space. + break; + } + } + } + } + + + if( !InsertSucceeded && OldPresent ) { + + // Since we were unable to insert the new attribute + // record, we reinsert the old one. Note that we can + // be sure that there's room for it, since we haven't + // added anything to the File Record Segment since we + // deleted this record. + + InsertAttributeRecord( &OldAttributeListRecord ); + } + + // For good or ill, we're done. + + return( InsertSucceeded ); +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::QueryAttributeRecord ( + OUT PNTFS_ATTRIBUTE_RECORD AttributeRecord, + IN ATTRIBUTE_TYPE_CODE Type, + IN PCWSTRING Name + ) +/*++ + +Routine Description: + + This method finds an attribute record in the FRS. Note that it only + searches this FRS, it will not look for external attribute records. + +Arguments: + + AttributeRecord -- returns the Attribute Record object. + Type -- supplies the type of the desired attribute record + Name -- supplies the name of the desired attribute (NULL if + the attribute has no name). + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + ULONG CurrentRecordOffset; + BOOLEAN Found = FALSE; + + DebugPtrAssert( _FrsData ); + DebugPtrAssert( AttributeRecord ); + + // Spin through the records in this File Record Segment + // looking for a match. If we find one, set the Found + // flag and break out. + + CurrentRecordOffset = _FrsData->FirstAttributeOffset; + + while( CurrentRecordOffset < QuerySize() && + AttributeRecord->Initialize( GetDrive(), + (PBYTE)_FrsData + CurrentRecordOffset, + QuerySize() - CurrentRecordOffset ) && + AttributeRecord->QueryTypeCode() != $END ) { + + if( AttributeRecord->IsMatch( Type, Name ) ) { + + Found = TRUE; + break; + } + + // Go on to the next record + + CurrentRecordOffset += AttributeRecord->QueryRecordLength(); + } + + // If Found is TRUE, then CurrentRecord is the attribute + // record we want, so we'll return it to the caller. + + return Found; +} + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::InsertAttributeRecord ( + IN PNTFS_ATTRIBUTE_RECORD NewRecord, + IN BOOLEAN ForceExternal + ) +/*++ + +Routine Description: + + This method inserts an attribute record into the File Record + Segment. + + If the File Record Segment has room, it will just insert the + record. If not, it may make the attribute external, or it + may make room for the record by making other attributes nonresident + or external. + + The Attribute List attribute and the Standard Information attribute + cannot be made external. + + This method is virtual because classes which derive from + File Record Segment may have preferences about how they manage + their attribute records. + +Arguments: + + AttributeRecord -- supplies the attribute record to insert + + ForceExternal -- supplies a flag telling whether to force + this record into a child File Record Segment; + if this flag is TRUE, we force this record + to be external. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method may also update the Instance tag in the attribute record. + +--*/ +{ + NTFS_ATTRIBUTE_RECORD CurrentRecord; + MFT_SEGMENT_REFERENCE SegmentReference, CheckSegment; + DSTRING NewName, CheckName; + NTFS_ATTRIBUTE_RECORD TempRecord; + PATTRIBUTE_RECORD_HEADER EvicteeData, TempRecordData; + DSTRING NewRecordName; + VCN CheckVcn; + ULONG CurrentRecordOffset; + ULONG FreeSpace; + ATTRIBUTE_TYPE_CODE CheckType; + BOOLEAN Result, MatchPresent; + USHORT CheckInstance; + + // The reservation for the attribute list is enought for + // a nonresident attribute with no name and two worst-case + // extents. Since this reservation is only required for + // FRS 0, this should be plenty. + // + CONST int AttributeListReservation = SIZE_OF_NONRESIDENT_HEADER + + 3 * ( 1 + 2 * sizeof(VCN) ); + + + DebugPtrAssert( _FrsData ); + DebugPtrAssert( NewRecord ); + + // The utilities will never insert any non-$DATA records into + // the reserved MFT-overflow FRS when it is being used as + // a child of the MFT. + // + if( !IsBase() && + QueryFileNumber() == MFT_OVERFLOW_FRS_NUMBER && + NewRecord->QueryTypeCode() != $DATA ) { + + return FALSE; + } + + // If this File Record Segment has an attribute list, we have + // to make sure that we've read it. + + if( !SetupAttributeList() ) { + + return FALSE; + } + + // Determine whether an attribute record of the same type and + // name is already present in this precise FRS (ie. in this + // FRS and not one of its children). If this is the case, + // then if this + // + if( !NewRecord->QueryName( &NewRecordName ) ) { + + DebugPrint( "UNTFS: Can't get name from attribute record.\n" ); + return FALSE; + } + + MatchPresent = IsAttributePresent( NewRecord->QueryTypeCode(), + &NewRecordName, + TRUE ); + + // Determine how much space is available in the File Record Segment. + // If there's enough, find the correct insertion point for this + // attribute record and put it there. + // + FreeSpace = QueryFreeSpace(); + + if( QueryFileNumber() == MASTER_FILE_TABLE_NUMBER && + IsBase() && + NewRecord->QueryTypeCode() != $ATTRIBUTE_LIST ) { + + // Reserve space for the Attribute List. + // + if ( FreeSpace < AttributeListReservation ) { + + FreeSpace = 0; + + } else{ + + FreeSpace -= AttributeListReservation; + } + } + + // The new record can go in this FRS if the client has not + // requested that it be made external, if there's sufficient + // room for it, and if there is no collision with other records + // for this attribute. + // + if( !ForceExternal && + NewRecord->QueryRecordLength() <= FreeSpace && + ( NewRecord->IsResident() || !MatchPresent ) ) { + + // There's enough free space for the new record, so it'll + // go in this FRS. Attach the current Next Instance tag + // to this attribute record and increment the tag. + // + NewRecord->SetInstanceTag( QueryNextInstance() ); + IncrementNextInstance(); + + // Scan through the list of records to find the point + // at which we should insert it. + // + CurrentRecordOffset = _FrsData->FirstAttributeOffset; + + while( CurrentRecordOffset < QuerySize() && + CurrentRecord.Initialize( GetDrive(), + (PBYTE)_FrsData + CurrentRecordOffset, + QuerySize() - CurrentRecordOffset ) && + CurrentRecord.QueryTypeCode() != $END && + CompareAttributeRecords( &CurrentRecord, + NewRecord, + GetUpcaseTable() ) <= 0 ) { + + if( CurrentRecord.QueryRecordLength() == 0 ) { + + // Corrupt FRS. + // + return FALSE; + } + + // Go on to the next record + + CurrentRecordOffset += CurrentRecord.QueryRecordLength(); + } + + // We want to insert the new record at CurrentRecordOffset; + // make room for it there and copy it in. + + memmove( (PBYTE)_FrsData + CurrentRecordOffset + + NewRecord->QueryRecordLength(), + (PBYTE)_FrsData + CurrentRecordOffset, + _FrsData->FirstFreeByte - CurrentRecordOffset); + + _FrsData->FirstFreeByte += NewRecord->QueryRecordLength(); + + memcpy( (PBYTE)_FrsData + CurrentRecordOffset, + NewRecord->GetData(), + (UINT) NewRecord->QueryRecordLength() ); + + // if this File Record Segment has an attribute list, then + // we add an entry to it for the attribute record we just added. + // Note that we can safely assume that an attribute list is + // present if and only if _AttributeList is non-NULL because + // we made sure we had read it at the beginning of this method. + + if( _AttributeList != NULL && + NewRecord->QueryTypeCode() != $ATTRIBUTE_LIST ) { + + SegmentReference = QuerySegmentReference(); + + if (!NewRecord->QueryName(&NewName) || + !_AttributeList->AddEntry(NewRecord->QueryTypeCode(), + NewRecord->QueryLowestVcn(), + &SegmentReference, + NewRecord->QueryInstanceTag(), + &NewName)) { + + // So near and yet so far. We have to back all the + // way out because we couldn't add an entry for this + // record to the attribute list. + + memmove( (PBYTE)_FrsData + CurrentRecordOffset, + (PBYTE)_FrsData + CurrentRecordOffset + + NewRecord->QueryRecordLength(), + _FrsData->FirstFreeByte - + (CurrentRecordOffset + + NewRecord->QueryRecordLength()) ); + + _FrsData->FirstFreeByte -= NewRecord->QueryRecordLength(); + + return FALSE; + } + } + + Result = TRUE; + + } else if( QueryFileNumber() == MASTER_FILE_TABLE_NUMBER && + NewRecord->QueryTypeCode() == $DATA && + NewRecord->QueryLowestVcn() == 0 ) { + + // The first chunk of the MFT's $DATA attribute must + // be in the base File Record Segment. Evict as many + // other records as possible. + // + if( (TempRecordData = (PATTRIBUTE_RECORD_HEADER) + MALLOC( QuerySize() )) == NULL ) { + + DebugPrint( "UNTFS: Can't allocate memory for temporary attribute record.\n" ); + return FALSE; + } + + EvicteeData = (PATTRIBUTE_RECORD_HEADER)GetNextAttributeRecord( NULL ); + + while( EvicteeData != NULL && + EvicteeData->TypeCode != $END ) { + + // If this is not an attribute record we can evict, + // then skip over it; otherwise, move it to a child + // FRS. Note that we don't check for the first chunk + // of the $DATA attribute becuase, after all, that's + // what we're inserting. + // + if( EvicteeData->TypeCode == $STANDARD_INFORMATION || + EvicteeData->TypeCode == $ATTRIBUTE_LIST ) { + + EvicteeData = (PATTRIBUTE_RECORD_HEADER) + GetNextAttributeRecord( EvicteeData ); + + } else { + + // Copy this record to the temporary buffer and + // delete it from the FRS. + // + memcpy( TempRecordData, + EvicteeData, + EvicteeData->RecordLength ); + + if( !TempRecord.Initialize( GetDrive(), TempRecordData ) ) { + + FREE( TempRecordData ); + return FALSE; + } + + DeleteAttributeRecord( EvicteeData ); + + // Delete the attribute list entry that corresponds + // to this attribute record. + // + if( _AttributeList != NULL ) { + + ATTR_LIST_CURR_ENTRY Entry; + + Entry.CurrentEntry = NULL; + while( _AttributeList->QueryNextEntry( &Entry, + &CheckType, + &CheckVcn, + &CheckSegment, + &CheckInstance, + &CheckName ) ) { + + // It's enough to establish a match if the + // segment reference and instance tag match, + // but we'll throw in a check for the type + // code just to be safe. + // + if( CheckType == TempRecord.QueryTypeCode() && + CheckInstance == TempRecord.QueryInstanceTag() && + CheckSegment == QuerySegmentReference() ) { + + _AttributeList->DeleteCurrentEntry( &Entry ); + break; + } + } + } + + // Insert this record into a child FRS. + // + if( !InsertExternalAttributeRecord( &TempRecord ) ) { + + FREE( TempRecordData ); + return FALSE; + } + + // No need to advance EvicteeData, since + // DeleteAttributeRecord will bring the next + // one down to us. + } + + } + + FREE( TempRecordData ); + + // OK, we've made as much free space as possible. Recompute + // the free space; if it's enough, insert the record recursively. + // Since we only recurse if there's enough free space, and + // we only fall into this branch if there isn't enough space, + // we won't get infinite recursion. + // + FreeSpace = QueryFreeSpace(); + + if( FreeSpace < AttributeListReservation ) { + + FreeSpace = 0; + + } else { + + FreeSpace -= AttributeListReservation; + } + + if( FreeSpace > NewRecord->QueryRecordLength() ) { + + // It'll fit--go ahead and insert it. + // + return( InsertAttributeRecord( NewRecord, FALSE ) ); + + } else { + + return FALSE; + + } + + } else { + + // This attribute will be external. Call the private worker + // method. + + Result = InsertExternalAttributeRecord( NewRecord ); + } + + // If this is a base file record segment and we've successfully + // inserted an indexed attribute, increment the reference count. + + if( Result && + IsBase() && + NewRecord->IsResident() && + (NewRecord->QueryResidentFlags() & RESIDENT_FORM_INDEXED ) ) { + + _FrsData->ReferenceCount += 1; + } + + return Result; +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::InsertExternalAttributeRecord( + IN PNTFS_ATTRIBUTE_RECORD NewRecord + ) +/*++ + +Routine Description: + + This method adds an external attribute record to the File Record + Segment. It is a private worker for InsertAttributeRecord. (It + can also be used to move attribute records out of the base segment). + + Note that it is nonvirtual--once the File Record Segment has decided + to make the attribute record external, it's done the same way for + all types of File Record Segments. + +Arguments: + + AttributeRecord -- supplies the attribute record to insert + +Return Value: + + TRUE upon successful completion. + +Notes: + + The Attribute List and Standard Information attributes cannot + be made external; this method enforces that restriction. + + +--*/ +{ + PNTFS_FILE_RECORD_SEGMENT ChildFrs; + MFT_SEGMENT_REFERENCE SegmentReference; + VCN ChildFileNumber; + ATTRIBUTE_TYPE_CODE TypeCode; + VCN LowestVcn; + DSTRING Name; + USHORT Instance; + BOOLEAN IsMft, IsMftData; + ATTR_LIST_CURR_ENTRY Entry; + + ULONG cluster_size = QueryClusterFactor() * GetDrive()->QuerySectorSize(); + + + IsMft = ( QueryFileNumber() == MASTER_FILE_TABLE_NUMBER ); + IsMftData = IsMft && ( NewRecord->QueryTypeCode() == $DATA ); + + // the standard information and attribute list attributes cannot + // be made external. + + if( NewRecord->QueryTypeCode() == $STANDARD_INFORMATION || + NewRecord->QueryTypeCode() == $ATTRIBUTE_LIST ) { + + return FALSE; + } + + // The Log File cannot have any external attributes. + // + if( !IsBase() && + QueryFileNumber() == LOG_FILE_NUMBER ) { + + return FALSE; + } + + // Note that only Base File Record Segments can have attribute lists. + + if( !IsBase() || !SetupAttributeList() ) { + + return FALSE; + } + + // OK, at this point, _AttributeList is NULL if and only if + // the File Record Segment has no attribute list. If the File + // Record Segment has no attribute list, we need to create one. + + if (_AttributeList == NULL) { + + if (!(_AttributeList = NEW NTFS_ATTRIBUTE_LIST) || + !CreateAttributeList(_AttributeList)) { + + // We can't create an attribute list for this File Record + // Segment. + + DELETE(_AttributeList); + return FALSE; + } + } + + // Since this is a somewhat rare case, we can indulge ourselves + // a bit. Go through the Attribute List and force all the + // children of this File Record Segment into memory. + + Entry.CurrentEntry = NULL; + while (_AttributeList->QueryNextEntry( &Entry, + &TypeCode, + &LowestVcn, + &SegmentReference, + &Instance, + &Name )) { + + ChildFileNumber.Set( SegmentReference.LowPart, + (LONG) SegmentReference.HighPart ); + + if( ChildFileNumber != QueryFileNumber() && + SetupChild( ChildFileNumber ) == NULL ) { + + // Error. + return FALSE; + } + } + + + // Now go down the list of children and see if any of them + // will accept this record. Note that if this is the MFT + // $DATA attribute, we must check each child before we try + // to use it to make sure we don't break the bootstrap. + // we much check each child + // + _ChildIterator->Reset(); + + while( (ChildFrs = + (PNTFS_FILE_RECORD_SEGMENT)_ChildIterator->GetNext()) != NULL ) { + + // The MFT requires special handling. Records for the data + // attribute must be inserted into child FRS' in a way that + // preserves the MFT's bootstrapping, ie. the starting VCN of + // the record must be greater than the VCN (file number times + // clusters per FRS) of the child. In addition, $DATA records + // cannot share a child FRS with any other attribute records. + // + if( IsMft ) { + + if (IsMftData && + ( ChildFrs->GetNextAttributeRecord( NULL ) != NULL || + NewRecord->QueryLowestVcn() * cluster_size <= + ChildFrs->QueryFileNumber() * QuerySize())) { + + // Either this child FRS is not empty, or + // inserting this record into it will break + // the MFT's bootstrapping. Either way, + // it can't accept this record. + // + continue; + } + + if( ChildFrs->IsAttributePresent( $DATA ) ) { + + // This child FRS already has a $DATA attribute + // record, so it can't accept any other records. + // + continue; + } + } + + // This child FRS is eligible to hold this attribute record. + // + if( ChildFrs->InsertAttributeRecord( NewRecord ) ) { + + // Success! Add an appropriate entry to the + // attribute list. + // + SegmentReference = ChildFrs->QuerySegmentReference(); + + if( !NewRecord->QueryName( &Name ) || + !_AttributeList->AddEntry( NewRecord->QueryTypeCode(), + NewRecord->QueryLowestVcn(), + &SegmentReference, + NewRecord->QueryInstanceTag(), + &Name ) ) { + + DebugPrintTrace(( "UNTFS: Can't add entry to attribute list." )); + return FALSE; + } + + return TRUE; + } + } + + // We have to allocate a new child File Record Segment. If + // This is the MFT File, see if we can grab the reserved FRS. + // + if( IsMftData && + GetChild( MFT_OVERFLOW_FRS_NUMBER ) == NULL ) { + + // This FRS is the MFT, and it hasn't already grabbed + // the reserved FRS--grab it now. + // + ChildFileNumber = MFT_OVERFLOW_FRS_NUMBER; + + } else if( !_Mft->AllocateFileRecordSegment( &ChildFileNumber, IsMftData ) ) { + + // Can't get a new child File Record Segment. + + return FALSE; + } + + // Set up the Segment Reference to refer to the Base File + // Record Segment, ie. this File Record Segment. + // + SegmentReference = QuerySegmentReference(); + + // Construct the new child File Record Segment and insert + // the attribute record into it. Again, make sure we don't + // violate the MFT bootstrapping requirements. (Note that + // we don't have to check the sharing rule for the MFT data + // attribute because this is a new FRS--it can't contain + // any conflicting records. + // + if( (ChildFrs = NEW NTFS_FILE_RECORD_SEGMENT) == NULL || + !ChildFrs->Initialize( ChildFileNumber, _Mft ) || + !ChildFrs->Create( &SegmentReference ) || + ( IsMftData && + NewRecord->QueryLowestVcn() * cluster_size <= + ChildFrs->QueryFileNumber() * QuerySize() ) || + !ChildFrs->InsertAttributeRecord( NewRecord ) || + !AddChild( ChildFrs ) ) { + + // That didn't do us any good at all. Clean up the child + // and return our failure. + + DELETE( ChildFrs ); + _Mft->FreeFileRecordSegment( ChildFileNumber ); + + return FALSE; + } + + // Note that the Child File Record Segment has passed into the + // keeping of the children list, so we don't delete it. + // + // Add an entry to the attribute list for the new record. + // + SegmentReference = ChildFrs->QuerySegmentReference(); + + if( !NewRecord->QueryName( &Name ) || + !_AttributeList->AddEntry( NewRecord->QueryTypeCode(), + NewRecord->QueryLowestVcn(), + &SegmentReference, + NewRecord->QueryInstanceTag(), + &Name ) ) { + + DebugPrintTrace(( "UNTFS: Can't add entry to attribute list." )); + return FALSE; + } + + return TRUE; +} + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::Write( + ) +/*++ + +Routine Description: + + This method writes the File Record Segment. It does not affect the + attribute list or the child record segments. + +Arguments: + + None. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + return( NTFS_FRS_STRUCTURE::Write() ); +} + + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::Flush( + IN OUT PNTFS_BITMAP VolumeBitmap, + IN OUT PNTFS_INDEX_TREE ParentIndex, + IN BOOLEAN FrsIsEmpty + ) +/*++ + +Routine Description: + + This method is used to commit a file to disk. It saves the attribute + list, writes any child record segments that have been brought into + memory, and writes the File Record Segment itself. + +Arguments: + + VolumeBitmap -- Supplies the volume bitmap. This parameter may + be NULL, in which case non-resident attributes + cannot be resized. + ParentIndex -- Supplies the directory which indexes this FRS + over $FILE_NAME. May be NULL. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + DUPLICATED_INFORMATION DuplicatedInformation; + PNTFS_FILE_RECORD_SEGMENT CurrentChild, ChildToDelete; + + // If this is a child FRS, just write it. + // + if( !IsBase() ) { + + return( Write() ); + } + + // This FRS is a Base File Record Segment--it may have + // an attribute list and children, and we have to update + // the file-name information in the parent index. + // + + if( !FrsIsEmpty && _AttributeList != NULL && + !SaveAttributeList( VolumeBitmap ) ) + { + return FALSE; + } + + // Update the file name attributes: + + if ( !FrsIsEmpty ) + if( !QueryDuplicatedInformation( &DuplicatedInformation ) || + !UpdateFileNames( &DuplicatedInformation, ParentIndex, FALSE ) ) { + + DebugAbort( "Can't update file names in Flush.\n" ); + return FALSE; + } + + // Flush all the children. If a child is empty, mark it as + // unused. + // + _ChildIterator->Reset(); + + while( (CurrentChild = (PNTFS_FILE_RECORD_SEGMENT) + _ChildIterator->GetNext()) != NULL ) { + + if( !CurrentChild->GetNextAttributeRecord( NULL ) ) { + + + // This child has no attribute records--we don't + // need it anymore. + // + CurrentChild->ClearInUse(); + CurrentChild->Write(); + _Mft->FreeFileRecordSegment( CurrentChild->QueryFileNumber() ); + ChildToDelete = (PNTFS_FILE_RECORD_SEGMENT)_Children.Remove(_ChildIterator); + DebugAssert(ChildToDelete == CurrentChild); + DELETE(ChildToDelete); + CurrentChild = NULL; + _ChildIterator->Reset(); + + } else if( !CurrentChild->Flush( VolumeBitmap ) ) { + + return FALSE; + } + } + + return( Write() ); +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::QueryDuplicatedInformation( + OUT PDUPLICATED_INFORMATION DuplicatedInformation + ) +/*++ + +Routine Description: + + This method queries the FRS for the information which is + duplicated in File Name attributes. + +Arguments: + + DuplicatedInformation -- Receives the duplicated information. + +Return Value: + + TRUE upon successful completion. + + +--*/ +{ + NTFS_ATTRIBUTE Attribute; + STANDARD_INFORMATION StandardInformation; + EA_INFORMATION EaInformation; + REPARSE_DATA_BUFFER reparse_point; + ULONG BytesRead; + BOOLEAN Error; + + // Start with a clean slate: + // + memset( DuplicatedInformation, 0, sizeof( DUPLICATED_INFORMATION ) ); + + // Most of the duplicated information comes from the + // Standard Information attribute. + // + if( !QueryAttribute( &Attribute, + &Error, + $STANDARD_INFORMATION ) || + !Attribute.Read( &StandardInformation, + 0, + sizeof( STANDARD_INFORMATION ), + &BytesRead ) || + BytesRead != sizeof( STANDARD_INFORMATION ) ) { + + DebugPrintTrace(( "Can't fetch standard information.\n" )); + return FALSE; + } + + DuplicatedInformation->CreationTime = + StandardInformation.CreationTime; + DuplicatedInformation->LastModificationTime = + StandardInformation.LastModificationTime; + DuplicatedInformation->LastChangeTime = + StandardInformation.LastChangeTime; + DuplicatedInformation->LastAccessTime = + StandardInformation.LastAccessTime; + + DuplicatedInformation->FileAttributes = StandardInformation.FileAttributes; + + if( _FrsData->Flags & FILE_FILE_NAME_INDEX_PRESENT ) { + + DuplicatedInformation->FileAttributes |= DUP_FILE_NAME_INDEX_PRESENT; + } + + if( _FrsData->Flags & FILE_VIEW_INDEX_PRESENT ) { + + DuplicatedInformation->FileAttributes |= DUP_VIEW_INDEX_PRESENT; + } + + // We also need one field from the EA_INFORMATION attribute + // or the REPARSE_POINT attribute. + // + if( !QueryAttribute( &Attribute, &Error, $EA_INFORMATION )) { + + if( Error ) { + + // The Ea Information attribute is present, but we + // couldn't get it. Bail out. + // + DebugAbort( "Error fetching Ea Information attribute.\n" ); + return FALSE; + + } else if (!QueryAttribute( &Attribute, &Error, $REPARSE_POINT )) { + + if( Error ) { + + // The Reparse Point attribute is present, but we + // couldn't get it. Bail out. + // + DebugAbort( "Error fetching Reparse Point attribute.\n" ); + return FALSE; + } else { + + // The Ea Information attribute is not present and there + // is no Reparse Point, which means this file has no EAs. + + DuplicatedInformation->PackedEaSize = 0; + } + } else if ( !Attribute.Read( &reparse_point, + 0, + FIELD_OFFSET(_REPARSE_DATA_BUFFER, + GenericReparseBuffer.DataBuffer), + &BytesRead ) || + BytesRead != FIELD_OFFSET(_REPARSE_DATA_BUFFER, + GenericReparseBuffer.DataBuffer)) { + + // Couldn't read the Reparse Point data. + // + DebugAbort( "Can't read Reparse Point.\n" ); + return FALSE; + + } else { + + // We've got the Reparse Point. + // + DuplicatedInformation->ReparsePointTag = reparse_point.ReparseTag; + } + + } else if( !Attribute.Read( &EaInformation, + 0, + sizeof( EA_INFORMATION ), + &BytesRead ) || + BytesRead != sizeof( EA_INFORMATION ) ) { + + // Couldn't read the Ea Information data. + // + DebugAbort( "Can't read EA Information.\n" ); + return FALSE; + + } else { + + // We've got the Ea Information. + // + DuplicatedInformation->PackedEaSize = EaInformation.PackedEaSize; + } + + + // Now we grope the size of the unnamed $DATA attribute and we + // are done. + // + + if( !QueryFileSizes( &(DuplicatedInformation->AllocatedLength), + &(DuplicatedInformation->FileSize), + &Error ) ) { + + if( Error ) { + + // The Data attribute is present, but we couldn't get it. + // + DebugAbort( "Error fetching $DATA attribute.\n" ); + return FALSE; + + } else { + + // This file has no unnamed $DATA attribute. + // + DuplicatedInformation->AllocatedLength = 0; + DuplicatedInformation->FileSize = 0; + } + + } + + return TRUE; +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::UpdateFileNames( + IN PDUPLICATED_INFORMATION DuplicatedInformation, + IN OUT PNTFS_INDEX_TREE Index, + IN BOOLEAN IgnoreExternal + ) +/*++ + +Routine Description: + + This method propagates + +Arguments: + + DuplicatedInformation -- Supplies the duplicated information which + is to be propagated into the File Names. + Index -- Supplies the index for the directory which + contains this FRS. It may be NULL, in which + case changes are not propagated to the index. + IgnoreExternal -- Supplies a flag which, if TRUE, indicates + that this method should only update file + names in this FRS, and not in its children. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method only propagates changes to the supplied index; thus, + if a file is indexed by more than one directory, changes can + only be propagated to one directory. However, all entries for + this file in the supplied index will be updated. + +--*/ +{ + NTFS_ATTRIBUTE_RECORD CurrentRecord; + DSTRING Name; + MFT_SEGMENT_REFERENCE SegmentReference; + + PNTFS_FILE_RECORD_SEGMENT ChildFrs; + LCN TargetFileNumber; + VCN LowestVcn; + ATTRIBUTE_TYPE_CODE CurrentType; + PFILE_NAME CurrentName; + PVOID CurrentRecordData; + ULONG ValueLength; + USHORT Tag; + + // Make sure that the supplied index, if any, is constructed + // over the FILE_NAME attribute: + + if( Index != NULL && + Index->QueryIndexedAttributeType() != $FILE_NAME ) { + + DebugAbort( "Updating file names in an index that's not over $FILE_NAME.\n" ); + return FALSE; + } + + // Force the attribute list, if we have one, into memory. + // + if( !SetupAttributeList() ) { + + return FALSE; + } + + if( !IgnoreExternal && + _AttributeList != NULL ) { + + ATTR_LIST_CURR_ENTRY Entry; + + Entry.CurrentEntry = NULL; + while (_AttributeList->QueryNextEntry( &Entry, + &CurrentType, + &LowestVcn, + &SegmentReference, + &Tag, + &Name )) { + + if( CurrentType == $FILE_NAME ) { + + // The current entry represents a record for a + // File Name attribute, which we may wish to + // update. Figure out what FRS it's in: + + TargetFileNumber.Set( SegmentReference.LowPart, + (LONG) SegmentReference.HighPart ); + + if( TargetFileNumber == QueryFileNumber() ) { + + // This file name is in the base FRS, ie. in + // this FRS. Call this method recursively, + // with IgnoreExternal TRUE to limit the + // recursion. + + if( !UpdateFileNames( DuplicatedInformation, + Index, + TRUE ) ) { + return FALSE; + } + + } else { + + // This attribute is in a child FRS. Get the + // child and update any file names in it. Note + // Note that we can ignore external names in + // the child, since child FRS's cannot have + // children. (Note also that this will preemptively + // update any other names in that child, which is + // fine. + // + if( (ChildFrs = SetupChild( TargetFileNumber )) == NULL || + !ChildFrs->UpdateFileNames( DuplicatedInformation, + Index, + TRUE ) ) { + + return FALSE; + } + } + } + } + + } else { + + // Crawl through the records in this FRS looking for File Names. + // + CurrentRecordData = NULL; + + while( (CurrentRecordData = + GetNextAttributeRecord( CurrentRecordData )) != NULL ) { + + // Note that GetNextAttributeRecord always returns a + // structurally-sound attribute record, which we can + // pass directly to this flavor of Initialize, which + // in turn will always succeed. It is also important + // that this flavor of initialize will cause the + // attribute record to use directly the data in the + // File Record Segment, so we can twiddle it there. + // + if( !CurrentRecord.Initialize( GetDrive(), CurrentRecordData ) ) { + + DebugAbort( "Can't initialize attribute record.\n" ); + return FALSE; + } + + if( CurrentRecord.QueryTypeCode() == $FILE_NAME ) { + + // It's a file name. + + CurrentName = (PFILE_NAME) + (CurrentRecord.GetResidentValue() ); + + ValueLength = CurrentRecord.QueryResidentValueLength(); + + // Perform sanity checks--the attribute must be resident, + // big enough to be a File Name, and big enough to hold + // the name it claims to be. + // + if( CurrentName == NULL || + ValueLength < sizeof( FILE_NAME ) || + ValueLength < (ULONG)FIELD_OFFSET( FILE_NAME, FileName ) + + CurrentName->FileNameLength ) { + + DebugAbort( "Corrupt file name.\n" ); + return FALSE; + } + + // OK, it's a valid file name. Update the duplicated + // information and propagate duplicated information and + // file name bits back to the index entry. + // + memcpy( &(CurrentName->Info), + DuplicatedInformation, + sizeof( DUPLICATED_INFORMATION ) ); + + // Update the corresponding entry in the index. + // + + SegmentReference = ( IsBase() ) ? + QuerySegmentReference() : + QueryBaseFileRecordSegment(); + + + if( Index != NULL && + !Index->UpdateFileName( CurrentName, + SegmentReference ) ) { + + return FALSE; + } + } + } + } + + return TRUE; +} + + + +VOID +NTFS_FILE_RECORD_SEGMENT::SetLsn( + IN BIG_INT NewLsn + ) +/*++ + +Routine Description: + + This method sets the Lsn for the File Record Segment + and any of its children which are in memory. + +Arguments: + + NewLsn -- Supplies the new LSN for this File Record + Segment and any available children. + +Return Value: + + None. + +--*/ +{ + PNTFS_FILE_RECORD_SEGMENT CurrentChild; + + _FrsData->Lsn = NewLsn.GetLargeInteger(); + + _ChildIterator->Reset(); + + while( (CurrentChild = (PNTFS_FILE_RECORD_SEGMENT) + _ChildIterator->GetNext()) != NULL ) { + + CurrentChild->SetLsn( NewLsn ); + } +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::Backtrack( + OUT PWSTRING Path + ) +/*++ + +Routine Description: + + This function finds a path from the root to this FRS. Note + that it does not detect cycles, and may enter into an infinite + loop if there is a cycle in the logical directory structure. + + Note that the client must read this FRS before calling Backtrack. + +Arguments: + + Path -- Receives the path. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + // First, check to see if this is the Root + // + if( QueryFileNumber() == ROOT_FILE_NAME_INDEX_NUMBER ) { + + return( Path->Initialize( "\\" ) ); + } + + // Initialize the path to the empty string and then pass it + // to the worker routine. + // + return( Path->Initialize( "" ) && + BacktrackWorker( Path ) ); +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::BacktrackWorker( + IN OUT PWSTRING Path + ) +/*++ + +Routine Description: + + This member function is a private worker routine for Backtrack; + it performs the actual work of constructing the path from the + root to this FRS. + +Arguments: + + Path -- Receives the path to this FRS. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + NTFS_ATTRIBUTE FileNameAttribute; + NTFS_FILE_RECORD_SEGMENT ParentFrs; + VCN ParentFileNumber; + PCFILE_NAME FileName; + DSTRING FileNameString, Backslash; + BOOLEAN Error; + + // Short circuit if this is the root--it doesn't add + // anything to the path. + // + if( QueryFileNumber() == ROOT_FILE_NAME_INDEX_NUMBER ) { + + return TRUE; + } + + // Extract a name from the FRS. Any name will do. + // + if( !QueryAttribute( &FileNameAttribute, &Error, $FILE_NAME ) || + !FileNameAttribute.IsResident() ) { + + return FALSE; + } + + FileName = (PCFILE_NAME)FileNameAttribute.GetResidentValue(); + + ParentFileNumber.Set( FileName->ParentDirectory.LowPart, + (LONG)FileName->ParentDirectory.HighPart ); + + // If the parent FRS is not the root, initialize and read it, + // and then recurse into it. + // + if( ParentFileNumber != ROOT_FILE_NAME_INDEX_NUMBER && + (!ParentFrs.Initialize( ParentFileNumber, _Mft ) || + !ParentFrs.Read() || + !ParentFrs.Backtrack( Path ) ) ) { + + return FALSE; + } + + // Now add this FRS's name to the path. + // + if( !Backslash.Initialize( "\\" ) || + !FileNameString.Initialize( NtfsFileNameGetName( FileName ), + FileName->FileNameLength ) || + !Path->Strcat( &Backslash ) || + !Path->Strcat( &FileNameString ) ) { + + return FALSE; + } + + return TRUE; +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::AddChild( + PNTFS_FILE_RECORD_SEGMENT ChildFrs + ) +/*++ + +Routine Description: + + This method adds a child File Record Segment to the list of + children. Management of that File Record Segment then passes + to the list. + +Arguments: + + ChildFrs -- supplies a pointer to the child File Record Segment. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + _ChildIterator->Reset(); + + return( _Children.Insert( ChildFrs, _ChildIterator ) ); +} + + + +PNTFS_FILE_RECORD_SEGMENT +NTFS_FILE_RECORD_SEGMENT::GetChild( + VCN FileNumber + ) +/*++ + +Routine Description: + + This method finds a File Record Segment in the list of children + based on its File Number. + +Arguments: + + FileNumber -- supplies the file number of the desired child. + +Return Value: + + A pointer to the desired File Record Segment. Note that this + object belongs to the child list, and should not be deleted by + the client. + + NULL if the desired child could not be found in the list. + +--*/ +{ + PNTFS_FILE_RECORD_SEGMENT CurrentChild; + + // Spin through the list of children until we run out or find + // one with the appropriate VCN. + + _ChildIterator->Reset(); + + while( (CurrentChild = + (PNTFS_FILE_RECORD_SEGMENT)_ChildIterator->GetNext()) != NULL && + CurrentChild->QueryFileNumber() != FileNumber ); + + // If there is a matching child in the list, CurrentChild now points + // at it; otherwise, CurrentChild is NULL. + + return CurrentChild; +} + + +PNTFS_FILE_RECORD_SEGMENT +NTFS_FILE_RECORD_SEGMENT::SetupChild( + IN VCN FileNumber + ) +/*++ + +Routine Description: + + This method sets up a child FRS. If the desired child is already + in the list, it is returned; otherwise, it is allocated and read + and added to the list, and the returned. + +Arguments: + + FileNumber -- Supplies the file number of the desired child. + +Return Value: + + A pointer to the child FRS, or NULL to indicate error. + +--*/ +{ + PNTFS_FILE_RECORD_SEGMENT ChildFrs; + PNTFS_MFT_FILE MftFile; + + if( (ChildFrs = GetChild( FileNumber )) != NULL ) { + + // The child is already in the list. + // + return ChildFrs; + } + + // Allocate a new FRS object, initialize it to be the + // child we want, read it in, and add it to the list. + // + if( (ChildFrs = NEW NTFS_FILE_RECORD_SEGMENT) == NULL ) { + + return NULL; + } + + if( _Mft != NULL ) { + + // This is an ordinary, run-of-the-mill File Record + // Segment, so just initialize the child with the + // same MFT as this object was initialized with. + // + if( !ChildFrs->Initialize( FileNumber, _Mft ) || + !ChildFrs->Read() || + !AddChild( ChildFrs ) ) { + + DELETE( ChildFrs ); + return NULL; + } + + } else { + + // This File Record Segment is really the + // MFT file itself, so we have to do some + // arcane gesticulation. Since we know this + // is really an NTFS_MFT_FILE object, we'll + // dynamically cast it to that class, and + // then pass it in as the NTFS_MFT_FILE to + // initialize the child. + // + if( QueryClassId() != NTFS_MFT_FILE_cd->QueryClassId() ) { + + DELETE( ChildFrs ); + return FALSE; + } + + MftFile = (PNTFS_MFT_FILE)( this ); + + if( !ChildFrs->Initialize( FileNumber, MftFile ) || + !ChildFrs->Read() || + !AddChild( ChildFrs ) ) { + + DELETE( ChildFrs ); + return FALSE; + } + } + + return ChildFrs; +} + + +VOID +NTFS_FILE_RECORD_SEGMENT::DeleteChild( + VCN FileNumber + ) +/*++ + +Routine Description: + + This method removes a File Record Segment from the list of + children based on its File Number. + +Arguments: + + FileNumber -- supplies the file number of the child to delete. + +Return Value: + + None. + +Notes: + + Since the list manages the File Record Segments which it has been + given, it deletes the File Record Segment in question. + + This method assumes that only one matching child exists (since the + same File Record Segment should not appear twice in the list). + +--*/ +{ + PNTFS_FILE_RECORD_SEGMENT CurrentChild, ChildToDelete; + + // Spin through the list of children until we run out or find + // one with the appropriate VCN. + + _ChildIterator->Reset(); + + while( (CurrentChild = + (PNTFS_FILE_RECORD_SEGMENT)_ChildIterator->GetNext()) != NULL && + CurrentChild->QueryFileNumber() != FileNumber ); + + // If there is a matching child in the list, the iterator's current + // state points at it and CurrentChild is non-NULL. + + if( CurrentChild != NULL ) { + + // A matching child was found; remove it from the list + // and delete it. + + ChildToDelete = (PNTFS_FILE_RECORD_SEGMENT) + _Children.Remove( _ChildIterator ); + + DebugAssert( ChildToDelete == CurrentChild ); + + DELETE( ChildToDelete ); + } +} + + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::QueryAttributeListAttribute ( + OUT PNTFS_ATTRIBUTE AttrList, + OUT PBOOLEAN Error +) +/*++ + +Routine Description: + + This function fetches the attribute list associated with the + File Record Segment. + +Arguments: + + AttrList -- Receives (ie. is initialized to) the attribute. Note + that this parameter may be uninitialized on entry, and + may be left uninitialized if this method fails. + Error -- Receives TRUE if the method fails because of an error. + +Return Value: + + TRUE upon successful completion. + +Notes: + + If the method returns TRUE, *Error should be ignored. If it + returns FALSE, *Error will be set to TRUE if the failure resulted + from an error (out of memory, corrupt structure); otherwise, the + caller may assume that the attribute is not present. + + This method assumes the attribute list is local to the File Record Segment. + +--*/ +{ + NTFS_ATTRIBUTE_RECORD Record; + + DebugPtrAssert( AttrList ); + DebugPtrAssert( Error ); + + DebugPtrAssert( _FrsData ); + + // Assume innocent until proven guilty: + + *Error = FALSE; + + if( !IsAttributePresent( $ATTRIBUTE_LIST, NULL, TRUE ) ) { + + // there is no matching attribute. + + return FALSE; + } + + // Now that we've determined that the attribute is present, + // this method can only fail because of an error. + + *Error = TRUE; + + + if( !SetupAttributeList() ) { + + return FALSE; + } + + // Get the first attribute record. + + if (!QueryAttributeRecord(&Record, $ATTRIBUTE_LIST)) { + + return FALSE; + } + + // Initialize the Attribute with the first attribute record. + + if ( !AttrList->Initialize( GetDrive(), + QueryClusterFactor(), + &Record ) ) { + + return FALSE; + } + + *Error = FALSE; + return TRUE; +} + +BOOLEAN +NTFS_FILE_RECORD_SEGMENT::PurgeAttributeList ( +) +/*++ + +Routine Description: + + This function purges the attribute list off the File Record Segment. + +Arguments: + + N/A + +Return Value: + + TRUE upon successful completion. + +Notes: + It is the user's responsibility to make sure that all segment reference + in the attribute list points back to the base frs. + +--*/ +{ + DELETE(_AttributeList); + return PurgeAttribute($ATTRIBUTE_LIST, NULL, TRUE); +} diff --git a/untfs/src/frsstruc.cxx b/untfs/src/frsstruc.cxx new file mode 100644 index 0000000..efb1468 --- /dev/null +++ b/untfs/src/frsstruc.cxx @@ -0,0 +1,769 @@ +#include "stdafx.h" + + + + + +#include "ulib.hxx" + +#include "untfs.hxx" +#include "frsstruc.hxx" +#include "mem.hxx" +#include "attrib.hxx" +#include "drive.hxx" +#include "clusrun.hxx" +#include "wstring.hxx" +#include "message.hxx" + +#include "attrrec.hxx" +#include "attrlist.hxx" +#include "ntfsbit.hxx" +#include "bigint.hxx" +#include "numset.hxx" + + +DEFINE_CONSTRUCTOR( NTFS_FRS_STRUCTURE, OBJECT ); + + + +NTFS_FRS_STRUCTURE::~NTFS_FRS_STRUCTURE( + ) +/*++ + +Routine Description: + + Destructor for NTFS_FRS_STRUCTURE. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + +VOID +NTFS_FRS_STRUCTURE::Construct( + ) +/*++ + +Routine Description: + + This routine initialize this class to a default state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _FrsData = NULL; + _secrun = NULL; + _mftdata = NULL; + _file_number = 0; + _cluster_factor = 0; + _size = 0; + _drive = NULL; + _volume_sectors = 0; + _upcase_table = NULL; + _first_file_number = 0; + _frs_count = 0; + _frs_state = _read_status = FALSE; + _usa_check = UpdateSequenceArrayCheckValueOk; +} + + + +VOID +NTFS_FRS_STRUCTURE::Destroy( + ) +/*++ + +Routine Description: + + This routine returns this class to a default state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _FrsData = NULL; + DELETE(_secrun); + _mftdata = NULL; + _file_number = 0; + _cluster_factor = 0; + _size = 0; + _drive = NULL; + _volume_sectors = 0; + _upcase_table = NULL; + _first_file_number = 0; + _frs_count = 0; + _frs_state = _read_status = FALSE; +} + + + +BOOLEAN +NTFS_FRS_STRUCTURE::Initialize( + IN OUT PMEM Mem, + IN OUT PNTFS_ATTRIBUTE MftData, + IN VCN FileNumber, + IN ULONG ClusterFactor, + IN BIG_INT VolumeSectors, + IN ULONG FrsSize, + IN PNTFS_UPCASE_TABLE UpcaseTable + ) +/*++ + +Routine Description: + + This routine initializes a NTFS_FRS_STRUCTURE to a valid + initial state. + +Arguments: + + Mem - Supplies memory for the FRS. + MftData - Supplies the $DATA attribute of the MFT. + FileNumber - Supplies the file number for this FRS. + ClusterFactor - Supplies the number of sectors per cluster. + VolumeSectors - Supplies the number of volume sectors. + FrsSize - Supplies the size of each frs, in bytes. + UpcaseTable - Supplies the volume upcase table. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +Notes: + + The client may supply NULL for the upcase table, but then + it cannot manipulate named attributes until the ucpase + table is set. + +--*/ +{ + Destroy(); + + DebugAssert(Mem); + DebugAssert(MftData); + DebugAssert(ClusterFactor); + + _mftdata = MftData; + _file_number = FileNumber; + _cluster_factor = ClusterFactor; + _drive = MftData->GetDrive(); + _size = FrsSize; + _volume_sectors = VolumeSectors; + _upcase_table = UpcaseTable; + _usa_check = UpdateSequenceArrayCheckValueOk; + + DebugAssert(_drive); + DebugAssert(_drive->QuerySectorSize()); + + _FrsData = (PFILE_RECORD_SEGMENT_HEADER) + Mem->Acquire(QuerySize(), _drive->QueryAlignmentMask()); + + if (!_FrsData) { + Destroy(); + return FALSE; + } + + return TRUE; +} + + + +BOOLEAN +NTFS_FRS_STRUCTURE::Initialize( + IN OUT PMEM Mem, + IN OUT PNTFS_ATTRIBUTE MftData, + IN VCN FirstFileNumber, + IN ULONG FrsCount, + IN ULONG ClusterFactor, + IN BIG_INT VolumeSectors, + IN ULONG FrsSize, + IN PNTFS_UPCASE_TABLE UpcaseTable + ) +/*++ + +Routine Description: + + This routine initializes a NTFS_FRS_STRUCTURE to a valid + initial state in preparation for reading a block of FRS'es. + +Arguments: + + Mem - Supplies memory for the FRS. + MftData - Supplies the $DATA attribute of the MFT. + FirstFileNumber - Supplies the first file number for this FRS block. + FrsCount - Supplies the number of FRS'es in this FRS block. + ClusterFactor - Supplies the number of sectors per cluster. + VolumeSectors - Supplies the number of volume sectors. + FrsSize - Supplies the size of each frs, in bytes. + UpcaseTable - Supplies the volume upcase table. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +Notes: + + The client may supply NULL for the upcase table, but then + it cannot manipulate named attributes until the ucpase + table is set. + +--*/ +{ + Destroy(); + + DebugAssert(Mem); + DebugAssert(MftData); + DebugAssert(ClusterFactor); + + _mftdata = MftData; + _file_number = FirstFileNumber; + _first_file_number = FirstFileNumber; + _frs_count = FrsCount; + _frs_state = _read_status = FALSE; + _cluster_factor = ClusterFactor; + _drive = MftData->GetDrive(); + _size = FrsSize; + _volume_sectors = VolumeSectors; + _upcase_table = UpcaseTable; + _usa_check = UpdateSequenceArrayCheckValueOk; + + DebugAssert(_drive); + DebugAssert(_drive->QuerySectorSize()); + + _FrsData = (PFILE_RECORD_SEGMENT_HEADER) + Mem->Acquire(QuerySize()*FrsCount, _drive->QueryAlignmentMask()); + + if (!_FrsData) { + Destroy(); + return FALSE; + } + + return TRUE; +} + + + +BOOLEAN +NTFS_FRS_STRUCTURE::Initialize( + IN OUT PMEM Mem, + IN OUT PLOG_IO_DP_DRIVE Drive, + IN LCN StartOfFrs, + IN ULONG ClusterFactor, + IN BIG_INT VolumeSectors, + IN ULONG FrsSize, + IN PNTFS_UPCASE_TABLE UpcaseTable, + IN ULONG Offset + ) +/*++ + +Routine Description: + + This routine initializes an NTFS_FRS_STRUCTURE to point at one + of the low frs's. + +Arguments: + + Mem - Supplies memory for the FRS. + Drive - Supplies the drive. + StartOfFrs - Supplies the starting LCN for the frs. + ClusterFactor - Supplies the number of sectors per cluster. + UpcaseTable - Supplies the volume upcase table. + FrsSize - Supplies the size of frs 0 in bytes. + Offset - Supplies the offset in the cluster for the frs. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +Notes: + + The client may supply NULL for the upcase table; in that case, + the FRS cannot manipulate named attributes until the upcase + table is set. + +--*/ +{ + Destroy(); + + DebugAssert(Mem); + DebugAssert(Drive); + + _file_number = 0; + _cluster_factor = ClusterFactor; + _drive = Drive; + _size = FrsSize; + _volume_sectors = VolumeSectors; + _upcase_table = UpcaseTable; + _usa_check = UpdateSequenceArrayCheckValueOk; + + // + // Our SECRUN will need to hold the one or more sectors occupied + // by this frs. + // + +#define BYTES_TO_SECTORS(bytes, sector_size) \ + (((bytes) + ((sector_size) - 1))/(sector_size)) + + ULONG sectors_per_frs = BYTES_TO_SECTORS(FrsSize, + Drive->QuerySectorSize()); + + if (!(_secrun = NEW SECRUN) || + !_secrun->Initialize(Mem, + Drive, + StartOfFrs * QueryClusterFactor() + + Offset/Drive->QuerySectorSize(), + sectors_per_frs)) { + + Destroy(); + return FALSE; + } + + _FrsData = (PFILE_RECORD_SEGMENT_HEADER)_secrun->GetBuf(); + + DebugAssert(_FrsData); + + return TRUE; +} + + +BOOLEAN +NTFS_FRS_STRUCTURE::Read( + ) +/*++ + +Routine Description: + + This routine reads the FRS in from disk. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + ULONG bytes; + BOOLEAN r; + PIO_DP_DRIVE drive; + + DebugAssert(_mftdata || _secrun); + + if (_mftdata) { + r = _mftdata->Read(_FrsData, + _file_number*QuerySize(), + QuerySize(), + &bytes) && + bytes == QuerySize(); + + drive = _mftdata->GetDrive(); + } else { + r = _secrun->Read(); + drive = _secrun->GetDrive(); + } + + _read_status = r && + (_usa_check = + NTFS_SA::PostReadMultiSectorFixup(_FrsData, + QuerySize(), + drive, + _FrsData->FirstFreeByte)); + + return _read_status; +} + + +BOOLEAN +NTFS_FRS_STRUCTURE::Write( + ) +/*++ + +Routine Description: + + This routine writes the FRS to disk. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + ULONG bytes; + BOOLEAN r; + + DebugAssert(_mftdata || _secrun); + + NTFS_SA::PreWriteMultiSectorFixup(_FrsData, QuerySize()); + + if (_mftdata) { + + r = _mftdata->Write(_FrsData, + _file_number*QuerySize(), + QuerySize(), + &bytes, + NULL) && + bytes == QuerySize(); + + } else { + r = _secrun->Write(); + } + + NTFS_SA::PostReadMultiSectorFixup(_FrsData, QuerySize(), NULL); + + return r; +} + + + +PVOID +NTFS_FRS_STRUCTURE::GetNextAttributeRecord( + IN PCVOID AttributeRecord, + IN OUT PMESSAGE Message, + OUT PBOOLEAN ErrorsFound + ) +/*++ + +Routine Description: + + This routine gets the next attribute record in the file record + segment assuming that 'AttributeRecord' points to a valid + attribute record. If NULL is given as the first argument then + the first attribute record is returned. + +Arguments: + + AttributeRecord - Supplies a pointer to the current attribute record. + Message - Supplies an outlet for error processing. + ErrorsFound - Supplies whether or not errors were found and + corrected in the FRS. + +Return Value: + + A pointer to the next attribute record or NULL if there are no more. + +--*/ +{ + PATTRIBUTE_RECORD_HEADER p; + PCHAR q; + PCHAR next_frs; + ULONG bytes_free; + BOOLEAN error; + + DebugAssert(_FrsData); + + if (ErrorsFound) { + *ErrorsFound = FALSE; + } + + next_frs = (PCHAR) _FrsData + QuerySize(); + + if (!AttributeRecord) { + + // Make sure the FirstAttributeOffset field will give us a properly + // aligned pointer. If not, bail. + // + + if (_FrsData->FirstAttributeOffset % 4 != 0) { + + return NULL; + } + + AttributeRecord = (PCHAR) _FrsData + _FrsData->FirstAttributeOffset; + + p = (PATTRIBUTE_RECORD_HEADER) AttributeRecord; + q = (PCHAR) AttributeRecord; + + if (q + QuadAlign(sizeof(ATTRIBUTE_TYPE_CODE)) > next_frs) { + + // There is no way to correct this error. + // The FRS is totally hosed, this will also be detected + // by VerifyAndFix. I can't really say *ErrorsFound = TRUE + // because the error was not corrected. I also cannot + // update the firstfreebyte and bytesfree fields. + + return NULL; + } + + if (p->TypeCode != $END) { + + error = FALSE; + if (q + sizeof(ATTRIBUTE_TYPE_CODE) + sizeof(ULONG) > next_frs) { + + error = TRUE; + } else if (!p->RecordLength) { + + error = TRUE; + } else if (!IsQuadAligned(p->RecordLength)) { + + error = TRUE; + } else if (q + p->RecordLength + sizeof(ATTRIBUTE_TYPE_CODE) > next_frs) { + + error = TRUE; + } + + if (error) { + + p->TypeCode = $END; + + bytes_free = (ULONG)(next_frs - q) - + QuadAlign(sizeof(ATTRIBUTE_TYPE_CODE)); + + _FrsData->FirstFreeByte = QuerySize() - bytes_free; + + if (ErrorsFound) { + *ErrorsFound = TRUE; + } + + if (Message) + { + Message->OutIncorrectStructure(); + } + + return NULL; + } + } + + } else { + + // Assume that the attribute record passed in is good. + + p = (PATTRIBUTE_RECORD_HEADER) AttributeRecord; + q = (PCHAR) AttributeRecord; + + q += p->RecordLength; + p = (PATTRIBUTE_RECORD_HEADER) q; + } + + + if (p->TypeCode == $END) + { + // Update the bytes free and first free fields. + + bytes_free = (ULONG)(next_frs - q) - QuadAlign(sizeof(ATTRIBUTE_TYPE_CODE)); + + if (_FrsData->FirstFreeByte + bytes_free != QuerySize()) + { + _FrsData->FirstFreeByte = QuerySize() - bytes_free; + + if (ErrorsFound) + { + *ErrorsFound = TRUE; + } + + if (Message) + { + Message->OutIncorrectStructure(); + } + } + + return NULL; + } + + + // Make sure the attribute record is good. + + error = FALSE; + if (q + sizeof(ATTRIBUTE_TYPE_CODE) + sizeof(ULONG) > next_frs) { + + error = TRUE; + } else if (!p->RecordLength) { + + error = TRUE; + } else if (!IsQuadAligned(p->RecordLength)) { + + error = TRUE; + } else if (q + p->RecordLength + QuadAlign(sizeof(ATTRIBUTE_TYPE_CODE)) > next_frs) { + + error = TRUE; + } + + if (error) { + + p->TypeCode = $END; + + bytes_free = (ULONG)(next_frs - q) - QuadAlign(sizeof(ATTRIBUTE_TYPE_CODE)); + + _FrsData->FirstFreeByte = QuerySize() - bytes_free; + + if (ErrorsFound) { + *ErrorsFound = TRUE; + } + + return NULL; + } + + return p; +} + + +VOID +NTFS_FRS_STRUCTURE::DeleteAttributeRecord( + IN OUT PVOID AttributeRecord + ) +/*++ + +Routine Description: + + This routine removes the pointed to attribute record from the + file record segment. The pointer passed in will point to + the next attribute record + +Arguments: + + AttributeRecord - Supplies a valid pointer to an attribute record. + +Return Value: + + None. + +--*/ +{ + PATTRIBUTE_RECORD_HEADER p; + PCHAR end; + PCHAR frs_end; + + DebugAssert(AttributeRecord); + + p = (PATTRIBUTE_RECORD_HEADER) AttributeRecord; + + DebugAssert(p->TypeCode != $END); + + end = ((PCHAR) p) + p->RecordLength; + frs_end = ((PCHAR) _FrsData) + QuerySize(); + + DebugAssert(end < frs_end); + + memmove(p, end, (unsigned int)(frs_end - end)); + + // This loop is here to straighten out the attribute records. + p = NULL; + while (p = (PATTRIBUTE_RECORD_HEADER) GetNextAttributeRecord(p)) { + } +} + + +BOOLEAN +NTFS_FRS_STRUCTURE::QueryAttributeList( + OUT PNTFS_ATTRIBUTE_LIST AttributeList + ) +/*++ + +Routine Description: + + This method fetches the Attribute List Attribute from this + File Record Segment. + +Arguments: + + AttributeList - Returns A pointer to the Attribute List Attribute. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + PATTRIBUTE_RECORD_HEADER prec; + NTFS_ATTRIBUTE_RECORD record; + + return (prec = (PATTRIBUTE_RECORD_HEADER) GetAttributeList()) && + record.Initialize(GetDrive(), prec) && + AttributeList->Initialize(GetDrive(), QueryClusterFactor(), + &record, + GetUpcaseTable()); +} + + +PVOID +NTFS_FRS_STRUCTURE::GetAttribute( + IN ULONG TypeCode + ) +/*++ + +Routine Description: + + This routine returns a pointer to the unnamed attribute with the + given type code or NULL if this attribute does not exist. + +Arguments: + + TypeCode - Supplies the type code of the attribute to search for. + +Return Value: + + A pointer to an attribute or NULL if there none was found. + +--*/ +{ + PATTRIBUTE_RECORD_HEADER prec; + + prec = NULL; + while (prec = (PATTRIBUTE_RECORD_HEADER) GetNextAttributeRecord(prec)) { + + if (prec->TypeCode == TypeCode && + prec->NameLength == 0) { + break; + } + } + + return prec; +} + + + +PVOID +NTFS_FRS_STRUCTURE::GetAttributeList( + ) +/*++ + +Routine Description: + + This routine returns a pointer to the attribute list or NULL if + there is no attribute list. + +Arguments: + + None. + +Return Value: + + A pointer to the attribute list or NULL if there is no attribute list. + +--*/ +{ + return GetAttribute($ATTRIBUTE_LIST); +} + diff --git a/untfs/src/indxbuff.cxx b/untfs/src/indxbuff.cxx new file mode 100644 index 0000000..dc3420d --- /dev/null +++ b/untfs/src/indxbuff.cxx @@ -0,0 +1,888 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + indxbuff.hxx + +Abstract: + + This module contains the member function definitions for + NTFS_INDEX_BUFFER, which models index buffers in an Index + Allocation attribute. + + These buffers are the component blocks of a b-tree, which + is rooted in the matching Index Root attribute. + +--*/ + + +#include "ulib.hxx" + +#include "untfs.hxx" +#include "ntfssa.hxx" + +#include "drive.hxx" + +#include "attrib.hxx" +#include "indxbuff.hxx" + +#include "indxroot.hxx" + +DEFINE_CONSTRUCTOR(NTFS_INDEX_BUFFER, OBJECT); + +NTFS_INDEX_BUFFER::~NTFS_INDEX_BUFFER( + ) +{ + Destroy(); +} + + +VOID +NTFS_INDEX_BUFFER::Construct( + ) +/*++ + +Routine Description: + + This method is the worker function for object construction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _ClustersPerBuffer = 0; + _BufferSize = 0; + _ClusterSize = 0; + _ThisBufferVcn = 0; + _CollationRule = COLLATION_NUMBER_RULES; + _UpcaseTable = NULL; +} + + +VOID +NTFS_INDEX_BUFFER::Destroy( + ) +/*++ + +Routine Description: + + This method cleans up the object in preparation for destruction + or reinitialization. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _ClustersPerBuffer = 0; + _ThisBufferVcn = 0; + _ClusterSize = 0; + _BufferSize = 0; + _CollationRule = COLLATION_NUMBER_RULES; + _UpcaseTable = NULL; +} + + +BOOLEAN +NTFS_INDEX_BUFFER::Initialize( + IN PCLOG_IO_DP_DRIVE Drive, + IN VCN ThisBufferVcn, + IN ULONG ClusterSize, + IN ULONG ClustersPerBuffer, + IN ULONG BufferSize, + IN COLLATION_RULE CollationRule, + IN PNTFS_UPCASE_TABLE UpcaseTable + ) +/*++ + +Routine Description: + + This method initializes an NTFS_INDEX_BUFFER. Note that this class + is reinitializable. + +Arguments: + + Drive -- supplies the drive on which the index resides. + ThisBufferVcn -- supplies the this buffer's VCN within the + index allocation attribute for the containing + index. + ClusterSize -- supplies the size of a cluster on this volume. + ClustersPerBuffer -- supplies the number of clusters per index + allocation buffer in this index b-tree. + BufferSize -- size of the buffer in bytes. + CollationRule -- supplies the collation rule for this index. + UpcaseTable -- supplies the volume upcase table. + +Either the ClustersPerBuffer or the BufferSize may be zero, but not +both. If BufferSize is zero, then we're doing an old-style buffer where +each buffer is at least one cluster. If ClustersPerBuffer is zero, then +we're doing a new-style buffer where the buffer size may be a fraction of +the cluster size. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + DebugPtrAssert( Drive ); + DebugAssert( ClusterSize != 0 ); + DebugAssert( BufferSize != 0 || ClustersPerBuffer != 0 ); + + Destroy(); + + _ClusterSize = ClusterSize; + _ClustersPerBuffer = ClustersPerBuffer; + _BufferSize = BufferSize; + _ThisBufferVcn = ThisBufferVcn; + _CollationRule = CollationRule; + _UpcaseTable = UpcaseTable; + + if( !_Mem.Initialize() || + (_Data = (PINDEX_ALLOCATION_BUFFER) + _Mem.Acquire( BufferSize, + Drive->QueryAlignmentMask() )) == NULL ) { + + Destroy(); + return FALSE; + } + + return TRUE; +} + + +VOID +NTFS_INDEX_BUFFER::Create( + IN BOOLEAN IsLeaf, + IN VCN EndEntryDownpointer + ) +/*++ + +Arguments: + + IsLeaf -- supplies a flag indicating whether this is a + leaf block (TRUE) or a node block (FALSE). + EndEntryDownpointer -- supplies the B-Tree downpointer for the end + entry. (This parameter is ignored if IsLeaf + is TRUE.) + +Return Value: + + None. + +--*/ +{ + PINDEX_ENTRY EndEntry; + + DebugPtrAssert( _Data ); + + // The layout of an index buffer is: + // Index Allocation Buffer Header + // Update Sequence Array + // First Entry. + + memset( _Data, 0, QuerySize() ); + + // Write the 'FILE' signature in the MultiSectorHeader. + + memcpy( _Data->MultiSectorHeader.Signature, + "INDX", + 4 ); + + // Compute the number of Update Sequence Numbers in the + // update array. This number is (see ntos\inc\cache.h): + // + // n/SEQUENCE_NUMBER_STRIDE + 1 + // + // where n is the number of bytes in the protected structure + // (in this case, an index allocation buffer ). + + _Data->MultiSectorHeader.UpdateSequenceArraySize = + (USHORT)(QuerySize()/SEQUENCE_NUMBER_STRIDE + 1); + + _Data->MultiSectorHeader.UpdateSequenceArrayOffset = + (USHORT)((PBYTE)&(_Data->UpdateSequenceArray) - (PBYTE)_Data); + + _Data->Lsn.LowPart = 0; + _Data->Lsn.HighPart = 0; + + _Data->ThisVcn = _ThisBufferVcn; + _Data->IndexHeader.Flags = IsLeaf ? 0 : INDEX_NODE; + + + _Data->IndexHeader.FirstIndexEntry = + QuadAlign( _Data->MultiSectorHeader.UpdateSequenceArrayOffset + + _Data->MultiSectorHeader.UpdateSequenceArraySize * + sizeof(UPDATE_SEQUENCE_NUMBER)); + + // the first entry is the end entry. The only fields in it that + // matter are the length, the flags, and the downpointer (if any). + + EndEntry = (PINDEX_ENTRY)((PBYTE)&(_Data->IndexHeader) + + _Data->IndexHeader.FirstIndexEntry); + + + EndEntry->Length = NtfsIndexLeafEndEntrySize; + EndEntry->AttributeLength = 0; + EndEntry->Flags = INDEX_ENTRY_END; + + if( !IsLeaf ) { + + EndEntry->Flags |= INDEX_ENTRY_NODE; + EndEntry->Length += sizeof(VCN); + GetDownpointer(EndEntry) = EndEntryDownpointer; + } + + + _Data->IndexHeader.FirstFreeByte = + _Data->IndexHeader.FirstIndexEntry + EndEntry->Length; + + _Data->IndexHeader.BytesAvailable = + QuerySize() - (ULONG)( (PBYTE)&(_Data->IndexHeader) - (PBYTE)_Data ); + +} + + + +BOOLEAN +NTFS_INDEX_BUFFER::Read( + IN OUT PNTFS_ATTRIBUTE AllocationAttribute + ) +/*++ + +Routine Description: + + This method reads the index allocation buffer. + +Arguments: + + AllocationAttribute -- supplies the Index Allocation Attribute + that describes the allocation for this + b-tree. + +Return Value: + + TRUE upon successful completion + +--*/ +{ + ULONG BytesRead; + BOOLEAN Result; + PINDEX_ENTRY CurrentEntry; + ULONG RemainingSpace, FirstEntryOffset; + BIG_INT AttributeOffset; + PMESSAGE msg; + PIO_DP_DRIVE drive; + + DebugPtrAssert( AllocationAttribute ); + + drive = AllocationAttribute->GetDrive(); + DebugAssert(drive); + msg = drive ? drive->GetMessage() : NULL; + + if (_BufferSize < _ClusterSize) { + AttributeOffset = NTFS_INDEX_BLOCK_SIZE * QueryVcn(); + } else { + AttributeOffset = _ClusterSize * QueryVcn(); + } + + Result = AllocationAttribute->Read( _Data, + AttributeOffset, + QuerySize(), + &BytesRead ) && + BytesRead == QuerySize() && + NTFS_SA::PostReadMultiSectorFixup( _Data, + BytesRead, + drive, + _Data->IndexHeader.FirstFreeByte ); + + // if the read succeeded, sanity-check the buffer. + // + if( Result ) { + + CurrentEntry = GetFirstEntry(); + FirstEntryOffset = (ULONG)((PBYTE)CurrentEntry - (PBYTE)_Data); + + if( FirstEntryOffset > QuerySize() ) { + + // The first entry pointer is completely wrong. + // + Result = FALSE; + + } else { + + RemainingSpace = QuerySize() - FirstEntryOffset; + + while( TRUE ) { + + if( NTFS_INDEX_TREE::IsIndexEntryCorrupt( CurrentEntry, + RemainingSpace, + msg ) ) { + + Result = FALSE; + break; + } + + if( CurrentEntry->Flags & INDEX_ENTRY_END ) { + + break; + } + + RemainingSpace -= CurrentEntry->Length; + CurrentEntry = GetNextEntry( CurrentEntry ); + } + } + } + + return Result; +} + + +BOOLEAN +NTFS_INDEX_BUFFER::Write( + IN OUT PNTFS_ATTRIBUTE AllocationAttribute + ) +/*++ + +Routine Description: + + This method writes the index allocation buffer. + +Arguments: + + AllocationAttribute -- supplies the Index Allocation Attribute + that describes the allocation for this + b-tree. + +Return Value: + + TRUE upon successful completion + +--*/ +{ + ULONG BytesWritten; + BIG_INT Offset; + BOOLEAN r; + + DebugPtrAssert( AllocationAttribute ); + + NTFS_SA::PreWriteMultiSectorFixup( _Data, QuerySize() ); + + if (_ClusterSize <= QuerySize()) { + Offset = _ClusterSize * QueryVcn(); + } else { + Offset = NTFS_INDEX_BLOCK_SIZE * QueryVcn(); + } + + r = AllocationAttribute->Write( _Data, + Offset, + QuerySize(), + &BytesWritten, NULL ) && + BytesWritten == QuerySize(); + + NTFS_SA::PostReadMultiSectorFixup( _Data, + QuerySize(), + NULL ); + + return r; +} + + + +BOOLEAN +NTFS_INDEX_BUFFER::FindEntry( + IN PCINDEX_ENTRY SearchEntry, + IN OUT PULONG Ordinal, + OUT PINDEX_ENTRY* EntryFound + ) +/*++ + +Routine Description: + + This method locates an entry in the index buffer. Note that it + does not recurse into the buffer's children (if any). If no matching + entry is found, it returns the first entry which is greater than + the desired entry; if the search key is greater than all the entries + in the buffer, it returns the END entry. + +Arguments: + + SearchEntry -- Supplies an entry with the search key and length. + (Note that this entry has a meaningless file reference). + Ordinal -- supplies an ordinal showing which matching entry + to return; see note below. + EntryFound -- receives a pointer to the located entry. Receives + NULL if an error has occurred. + +Return Value: + + TRUE if a matching entry is found. If an error occurs, *EntryFound + is set to NULL. If no error occurs, and no matching entry is found, + *EntryFound is set to the next entry (i.e. the point at which the + search key would be inserted into this buffer). + +Notes: + + This method assumes that the index buffer is consistent. + + The ordinal argument indicates how many matching entries should be + passed over before one is returned. When an entry is found which + matches the search key, if *Ordinal is zero, that entry is returned; + otherwise, *Ordinal is decremented, and the FindEntry goes on to + the next entry. + + If *Ordinal is INDEX_SKIP, then all matching entries are skipped. + +--*/ +{ + PINDEX_ENTRY CurrentEntry; + BOOLEAN Found; + int CompareResult; + + CurrentEntry = GetFirstEntry(); + Found = FALSE; + + while( !(CurrentEntry->Flags & INDEX_ENTRY_END) ) { + + CompareResult = CompareNtfsIndexEntries( SearchEntry, + CurrentEntry, + _CollationRule, + _UpcaseTable ); + + if( CompareResult < 0 ) { + + // The search value is less than the current entry's + // value, so we've overshot where our search key would + // be. Stop (and return the current entry). + + break; + + } else if( CompareResult == 0 ) { + + // The current entry matches the search entry. Check + // the ordinal argument to see if we should return this + // entry or skip it. + + if( *Ordinal == 0 ) { + + Found = TRUE; + break; + + } else if( *Ordinal != INDEX_SKIP ) { + + *Ordinal -= 1; + } + } + + // Haven't found our entry, so we'll just go on to the next. + + CurrentEntry = GetNextEntry( CurrentEntry ); + } + + *EntryFound = CurrentEntry; + return( Found ); +} + + +BOOLEAN +NTFS_INDEX_BUFFER::InsertEntry( + IN PCINDEX_ENTRY NewEntry, + IN PINDEX_ENTRY InsertPoint + ) +/*++ + +Routine Description: + + This method inserts an index entry into the buffer. + +Arguments: + + NewEntry -- Supplies the entry to insert. + InsertPoint -- supplies the point in the buffer at which this entry + should be inserted, if known. This parameter may be + NULL, in which case the buffer determines where to + insert the new entry. + +Return Value: + + TRUE upon successful completion. A return value of FALSE indicates + that the entry will not fit in the buffer. + +Notes: + + This method assumes that the buffer is consistent. + + InsertPoint should be a pointer previously returned from FindEntry; + otherwise, this method will go badly astray. + +--*/ +{ + ULONG Ordinal, BytesToCopy; + + // First, check to see if there's enough room: + + if( _Data->IndexHeader.FirstFreeByte + NewEntry->Length > + _Data->IndexHeader.BytesAvailable ) { + + return FALSE; + } + + // We know there's enough space, so we know we'll succeed. + + if( InsertPoint == NULL ) { + + // The client has not supplied the insert point, so we get to + // figure it out for ourselves. Fortunately, we can get FindEntry + // to do our work for us. + + // Note that we don't care what InsertEntry returns--we know it + // won't hit an error, and we don't care whether there are any + // matching entries in the buffer. (If we get a matching buffer, + // we insert the new one before it, which is just fine.) + + Ordinal = 0; + + FindEntry( NewEntry, + &Ordinal, + &InsertPoint ); + + DebugPtrAssert( InsertPoint ); + } + + // Now we just make room for the entry and jam it in. + + BytesToCopy = _Data->IndexHeader.FirstFreeByte - + (ULONG)((PBYTE)InsertPoint - (PBYTE)&(_Data->IndexHeader)); + + memmove( (PBYTE)InsertPoint + NewEntry->Length, + InsertPoint, + BytesToCopy ); + + _Data->IndexHeader.FirstFreeByte += NewEntry->Length; + + memcpy( InsertPoint, NewEntry, NewEntry->Length ); + + return TRUE; +} + + +VOID +NTFS_INDEX_BUFFER::RemoveEntry( + IN PINDEX_ENTRY EntryToRemove + ) +/*++ + +Routine Description: + + This method removes an entry from the index buffer, closing up + the buffer over it. + +Arguments: + + EntryToRemove -- supplies a pointer to the entry to remove. + +Return Value: + + None. This method always succeeds. + +Notes: + + This method assumes that the index buffer is consistent. + + EntryToRemove must be a pointer that was returned by a previous + call (with no intervening inserts or deletes) to FindEntry or + GetFirstEntry. + +--*/ +{ + PBYTE NextEntry; + ULONG BytesToCopy; + + NextEntry = (PBYTE)EntryToRemove + EntryToRemove->Length; + + BytesToCopy = _Data->IndexHeader.FirstFreeByte - + (ULONG)(NextEntry - (PBYTE)&(_Data->IndexHeader)); + + _Data->IndexHeader.FirstFreeByte -= EntryToRemove->Length; + + memmove( EntryToRemove, + NextEntry, + BytesToCopy ); + +} + + + +PINDEX_ENTRY +NTFS_INDEX_BUFFER::FindSplitPoint( + ) +/*++ + +Routine Description: + + This method finds a point at which the index allocation buffer + would like to be split. + +Arguments: + + None. + +Return Value: + + A pointer to the entry which will be promoted in the split. + + Note that we can return any non-end entry, but we cannot return + the end entry. + +--*/ +{ + PINDEX_ENTRY CurrentEntry, PreviousEntry=NULL, NextEntry; + ULONG CurrentOffset; + + + CurrentOffset = _Data->IndexHeader.FirstIndexEntry; + CurrentEntry = GetFirstEntry(); + + DebugAssert( !(CurrentEntry->Flags & INDEX_ENTRY_END) ); + + while( !(CurrentEntry->Flags & INDEX_ENTRY_END) && + CurrentOffset < _Data->IndexHeader.FirstFreeByte/2 ) { + + PreviousEntry = CurrentEntry; + CurrentOffset += CurrentEntry->Length; + CurrentEntry = GetNextEntry( CurrentEntry ); + } + + // We need to make sure we don't pick the last entry in the buffer + // to split before; the entry just after the split point gets promoted + // to the parent buffer, which would leave us with an empty buffer. + // + + if( CurrentEntry->Flags & INDEX_ENTRY_END ) { + + // Oops! Back up one. [XXX.mjb: really need to back up *two* + // in this case, because the last entry is just a marker to hold + // the flag, it's not a true entry.] + // + + CurrentEntry = PreviousEntry; + + } else { + + NextEntry = GetNextEntry( CurrentEntry ); + + if( NextEntry->Flags & INDEX_ENTRY_END ) { + + CurrentEntry = PreviousEntry; + } + } + + return CurrentEntry; +} + + +VOID +NTFS_INDEX_BUFFER::InsertClump( + IN ULONG LengthOfClump, + IN PCVOID Clump + ) +/*++ + +Routine Description: + + This method inserts a clump of entries at the beginning of + the buffer. It is used to insert entries into a newly-created + buffer. + +Arguments: + + LengthOfClump -- supplies the number of bytes to insert + Clump -- supplies the source from which the data + is to be copied. + +Return Value: + + None. + +Notes: + + This private method should be used with care; the client must + ensure that the entries being inserted are valid, and that they + will not cause the buffer to overflow. + +--*/ +{ + ULONG BytesToMove; + PBYTE InsertPoint; + + DebugAssert( _Data->IndexHeader.FirstFreeByte + LengthOfClump <= + _Data->IndexHeader.BytesAvailable ); + + // We'll insert the new entries in front of the first entry in + // the buffer, which means we have to shift all the existing + // entries up to make room. + + InsertPoint = (PBYTE)GetFirstEntry(); + + BytesToMove = _Data->IndexHeader.FirstFreeByte - + _Data->IndexHeader.FirstIndexEntry; + + memmove( InsertPoint + LengthOfClump, + InsertPoint, + BytesToMove ); + + // Copy the new entries into the space we just created: + + memcpy( InsertPoint, + Clump, + LengthOfClump ); + + // Adjust the offset of the First Free Byte to reflect + // what we just did: + + _Data->IndexHeader.FirstFreeByte += LengthOfClump; + +} + + +VOID +NTFS_INDEX_BUFFER::RemoveClump( + IN ULONG LengthOfClump + ) +/*++ + +Routine Description: + + This method removes a clump of entries from the beginning of + the index buffer. It's particularly useful when splitting + a buffer. + +Arguments: + + LengthOfClump -- supplies the number of bytes to remove from + the index buffer. + +Return Value: + + None. + +Notes: + + This private method should be used with care; the client must + ensure that the number of bytes to be removed covers a valid + clump of entries, and does not include (or extend past) the + END entry. + +--*/ +{ + ULONG BytesToMove; + PBYTE FirstEntry; + + DebugAssert( LengthOfClump + _Data->IndexHeader.FirstIndexEntry + < _Data->IndexHeader.FirstFreeByte ); + + // Compute the number of bytes in entries after the clump: + + BytesToMove = _Data->IndexHeader.FirstFreeByte - + (LengthOfClump + _Data->IndexHeader.FirstIndexEntry); + + // Shift those entries down to the beginning of the index + // entries in this index buffer. + + FirstEntry = (PBYTE)GetFirstEntry(); + + memmove( FirstEntry, + FirstEntry + LengthOfClump, + BytesToMove ); + + // Adjust the offset of the First Free Byte to reflect + // what we just did: + + _Data->IndexHeader.FirstFreeByte -= LengthOfClump; +} + + + +BOOLEAN +NTFS_INDEX_BUFFER::IsEmpty( + ) +/*++ + +Routine Description: + + This method determines whether the buffer is empty. + +Arguments: + + None. + +Return Value: + + TRUE if the first entry is an END entry. + +--*/ +{ + PINDEX_ENTRY FirstEntry; + + FirstEntry = GetFirstEntry(); + + return( FirstEntry->Flags & INDEX_ENTRY_END ); +} + + +BOOLEAN +NTFS_INDEX_BUFFER::Copy( + IN PNTFS_INDEX_BUFFER p, + IN PCLOG_IO_DP_DRIVE Drive + ) +/*++ + +Routine Description: + + This method makes a copy of the given index buffer. + +Arguments: + + p - Supplies the index buffer to be copied + Drive - Supplies the drive on which the index buffer resides. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + DebugPtrAssert(p); + + _ThisBufferVcn = p->_ThisBufferVcn; + _ClusterSize = p->_ClusterSize; + _ClustersPerBuffer = p->_ClustersPerBuffer; + _BufferSize = p->_BufferSize; + _CollationRule = p->_CollationRule; + _UpcaseTable = p->_UpcaseTable; + + if( !_Mem.Initialize() || + (_Data = (PINDEX_ALLOCATION_BUFFER) + _Mem.Acquire( _BufferSize, + Drive->QueryAlignmentMask() )) == NULL ) { + + Destroy(); + return FALSE; + } + + memcpy(_Data, p->_Data, _BufferSize); + return TRUE; +} diff --git a/untfs/src/indxroot.cxx b/untfs/src/indxroot.cxx new file mode 100644 index 0000000..5acc4cf --- /dev/null +++ b/untfs/src/indxroot.cxx @@ -0,0 +1,629 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + indxroot.hxx + +Abstract: + + this module contains the member funciton definitions for the + NTFS_INDEX_ROOT class, which models the root of an NTFS index + + +--*/ + + +#include "ulib.hxx" + +#include "untfs.hxx" + +#include "drive.hxx" + +#include "attrib.hxx" +#include "frs.hxx" +#include "indxroot.hxx" +#include "indxtree.hxx" + +DEFINE_CONSTRUCTOR( NTFS_INDEX_ROOT, OBJECT ); + +NTFS_INDEX_ROOT::~NTFS_INDEX_ROOT( + ) +{ + Destroy(); +} + + +VOID +NTFS_INDEX_ROOT::Construct( + ) +/*++ + +Routine Description: + + Worker function for object construction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _MaximumSize = 0; + _DataLength = 0; + _Data = NULL; + _IsModified = FALSE; + _UpcaseTable = NULL; + +} + +VOID +NTFS_INDEX_ROOT::Destroy( + ) +/*++ + +Routine Description: + + This method cleans up an NTFS_INDEX_ROOT object in preparation + for destruction or reinitialization. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _MaximumSize = 0; + _DataLength = 0; + FREE( _Data ); + _IsModified = FALSE; + _UpcaseTable = NULL; + +} + + +BOOLEAN +NTFS_INDEX_ROOT::Initialize( + IN PNTFS_ATTRIBUTE RootAttribute, + IN PNTFS_UPCASE_TABLE UpcaseTable, + IN ULONG MaximumSize + ) +/*++ + +Routine Description: + + This method initializes the index root based on an $INDEX_ROOT + attribute. It is used to initialize an index root object for + an extant index. + +Arguments: + + RootAttribute -- supplies the $INDEX_ROOT attribute for + this index. + UpcaseTable -- supplies the volume upcase table. + MaximumSize -- supplies the maximum size of this index root + +Return Value: + + TRUE upon successful completion. + +Notes: + + If the existing attribute's value length is greater than the + supplied maximum size, the maximum size will be adjusted. + +--*/ +{ + BIG_INT ValueLength; + ULONG BytesRead; + + DebugPtrAssert( RootAttribute ); + + Destroy(); + + RootAttribute->QueryValueLength( &ValueLength ); + + // Check that the attribute is the correct type and that + // it is a reasonable size. + + if( RootAttribute->QueryTypeCode() != $INDEX_ROOT || + ValueLength.GetHighPart() != 0 ) { + + return FALSE; + } + + // If the existing attribute is already bigger than the specified + // maximum size, then increase the maximum size, since it is only + // provided to give an upper bound on the size of the attribute. + + if( ValueLength.GetLowPart() > MaximumSize ) { + + MaximumSize = ValueLength.GetLowPart(); + } + + _DataLength = ValueLength.GetLowPart(); + _MaximumSize = MaximumSize; + _IsModified = FALSE; + + if( (_Data = (PINDEX_ROOT)MALLOC( _MaximumSize )) == NULL ) { + + Destroy(); + return FALSE; + } + + if( !RootAttribute->Read( _Data, + 0, + _DataLength, + &BytesRead ) || + BytesRead != _DataLength ) { + + Destroy(); + return FALSE; + } + + _UpcaseTable = UpcaseTable; + + return TRUE; +} + + +BOOLEAN +NTFS_INDEX_ROOT::Initialize( + IN ATTRIBUTE_TYPE_CODE IndexedAttributeType, + IN COLLATION_RULE CollationRule, + IN PNTFS_UPCASE_TABLE UpcaseTable, + IN ULONG ClustersPerBuffer, + IN ULONG BytesPerBuffer, + IN ULONG MaximumRootSize + ) +/*++ + +Routine Description: + + This method initializes the index root based on the fundamental + information of the index. It is used to initialize an index root + for a new index. + +Arguments: + + IndexedAttributeType -- supplies the type code of the attribute + which is indexed by this index. + CollationRule -- supplies the collation rule for this index. + UpcaseTable -- supplies the volume upcase table. + BytesPerBuffer -- supplies the number of bytes per Index + Allocation Buffer in this index. + MaximumRootSize -- supplies the maximum size of this index root. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method marks the index root as modified, since it is being + created ex nihilo instead of being read from an attribute. + + It creates an empty leaf index root (ie. with only an END entry). + +--*/ +{ + PINDEX_ENTRY EndEntry; + + DebugAssert( sizeof( INDEX_ROOT ) % 8 == 0 ); + DebugAssert( sizeof( INDEX_HEADER ) % 8 == 0 ); + + Destroy(); + + _UpcaseTable = UpcaseTable; + _MaximumSize = MaximumRootSize; + _IsModified = TRUE; + + _DataLength = sizeof( INDEX_ROOT ) + NtfsIndexLeafEndEntrySize; + + // check to make sure that an empty index root will fit in the + // maximum size given. Note that we also reserve space for a + // VCN downpointer, in case this index root gets converted into + // a node by Recreate. + + if( _DataLength + sizeof(VCN) > _MaximumSize ) { + + Destroy(); + return FALSE; + } + + if( (_Data = (PINDEX_ROOT)MALLOC( _MaximumSize )) == NULL ) { + + Destroy(); + return FALSE; + } + + memset( _Data, 0, _MaximumSize ); + + _Data->IndexedAttributeType = IndexedAttributeType; + _Data->CollationRule = CollationRule; + _Data->BytesPerIndexBuffer = BytesPerBuffer; + _Data->ClustersPerIndexBuffer = (UCHAR)ClustersPerBuffer; + _Data->IndexHeader.FirstIndexEntry = sizeof( INDEX_HEADER ); + _Data->IndexHeader.FirstFreeByte = sizeof( INDEX_HEADER ) + + NtfsIndexLeafEndEntrySize; + _Data->IndexHeader.BytesAvailable = _Data->IndexHeader.FirstFreeByte; + _Data->IndexHeader.Flags = 0; + + // Fill in the end entry. Its only meaningful fields are Length + // and Flags. + + EndEntry = GetFirstEntry(); + + EndEntry->Length = NtfsIndexLeafEndEntrySize; + EndEntry->AttributeLength = 0; + EndEntry->Flags = INDEX_ENTRY_END; + + return TRUE; +} + + +BOOLEAN +NTFS_INDEX_ROOT::FindEntry( + IN PCINDEX_ENTRY SearchEntry, + IN OUT PULONG Ordinal, + OUT PINDEX_ENTRY* EntryFound + ) +/*++ + +Routine Description: + + This method locates an entry in the index root. Note that it does + not search the index allocation b-tree (if any). + + +Arguments: + + SearchEntry -- Supplies a search entry, which gives the attribute + value to find. + Ordinal -- supplies an ordinal showing which matching entry + to return; see note below. A value of INDEX_SKIP + indicates that all matching entries should be skipped. + EntryFound -- receives a pointer to the located entry. Receives + NULL if an error has occurred. + +Return Value: + + TRUE if a matching entry is found. + + If an error occurs, *EntryFound is set to NULL. + + If no error occurs, and no matching entry is found, *EntryFound + is set to the next entry (i.e. the point at which the search key + would be inserted into this index root) and the method returns + FALSE. + +Notes: + + This method assumes that the index root is consistent. + + The ordinal argument indicates how many matching entries should be + passed over before one is returned. When an entry is found which + matches the search key, if *Ordinal is zero, that entry is returned; + otherwise, *Ordinal is decremented, and the FindEntry goes on to + the next entry. + + If *Ordinal is INDEX_SKIP, all matching entries are skipped. + +--*/ +{ + PINDEX_ENTRY CurrentEntry; + BOOLEAN Found; + int CompareResult; + + CurrentEntry = GetFirstEntry(); + Found = FALSE; + + while( !(CurrentEntry->Flags & INDEX_ENTRY_END) ) { + + CompareResult = CompareNtfsIndexEntries( SearchEntry, + CurrentEntry, + _Data->CollationRule, + _UpcaseTable); + + if( CompareResult < 0 ) { + + // The search value is less than the current entry's + // value, so we've overshot where our search key would + // be. Stop (and return the current entry). + + break; + + } else if( CompareResult == 0 ) { + + // The current entry matches the search entry. Check + // the ordinal argument to see if we should return this + // entry or skip it. + + if( *Ordinal == 0 ) { + + Found = TRUE; + break; + + } else if( *Ordinal != INDEX_SKIP ) { + + *Ordinal -= 1; + } + } + + // Haven't found our entry, so we'll just go on to the next. + + CurrentEntry = GetNextEntry( CurrentEntry ); + } + + *EntryFound = CurrentEntry; + return( Found ); +} + + +BOOLEAN +NTFS_INDEX_ROOT::InsertEntry( + IN PCINDEX_ENTRY NewEntry, + IN PINDEX_ENTRY InsertPoint + ) +/*++ + +Routine Description: + + This inserts an index entry into the index root. It will + expand the index root (if possible), but it will not split it. + +Arguments: + + NewEntry -- supplies the new entry to be inserted. + InsertPoint -- supplies the point at which the new entry should be + inserted--it may be NULL, in which case the index + root should decide for itself. + +Return Value: + + TRUE upon successful completion. + +Notes: + + A return value of FALSE indicates that there is not enough room + for the entry in this index root. + + If an insertion point is specified, it must be a pointer returned + by a previous call to FindEntry or GetFirstEntry (with no intervening + inserts or deletes). + + This method assumes that the index root and the new entry are + consistent, which includes that the entry has a downpointer if + and only if the index root is a node. + +--*/ +{ + ULONG Ordinal, BytesToMove; + + + // Check to see if there's room. + + if( _DataLength + NewEntry->Length > _MaximumSize ) { + + return FALSE; + } + + // There's enough room, so we're bound to succeed. + + if( InsertPoint == NULL ) { + + // The client has not supplied the insert point, so we get to + // figure it out for ourselves. Fortunately, we can get FindEntry + // to do our work for us. + + // Note that we don't care what InsertEntry returns--we know it + // won't hit an error, and we don't care whether there are any + // matching entries in the buffer. (If we get a matching buffer, + // we insert the new one before it, which is just fine.) + + Ordinal = 0; + + FindEntry( NewEntry, + &Ordinal, + &InsertPoint ); + + DebugPtrAssert( InsertPoint ); + } + + + // Make room for the new entry... + + BytesToMove = _Data->IndexHeader.FirstFreeByte - + (ULONG)((PBYTE)InsertPoint - (PBYTE)&(_Data->IndexHeader)); + + memmove( (PBYTE)InsertPoint + NewEntry->Length, + InsertPoint, + BytesToMove ); + + _Data->IndexHeader.FirstFreeByte += NewEntry->Length; + _Data->IndexHeader.BytesAvailable = _Data->IndexHeader.FirstFreeByte; + + memcpy( InsertPoint, NewEntry, NewEntry->Length ); + + _DataLength += NewEntry->Length; + + return TRUE; +} + + +VOID +NTFS_INDEX_ROOT::RemoveEntry( + PINDEX_ENTRY EntryToRemove + ) +/*++ + +Routine Description: + + This method removes an entry from the index root. Note that it will + not find a replacement entry for nodes, or perform any other b-tree + maintenance; it just expunges the entry from the root. + +Arguments: + + EntryToRemove -- supplies the index entry to be removed. This + must be an entry returned by a previous call + to FindEntry or GetFirstEntry. + +Return Value: + + None. + +Notes: + + This method assumes that the index root is consistent. +--*/ +{ + PBYTE NextEntry; + ULONG BytesToMove; + + + DebugAssert( (PBYTE)EntryToRemove < + (PBYTE)&(_Data->IndexHeader) + + _Data->IndexHeader.FirstFreeByte ); + + DebugAssert( (PBYTE)EntryToRemove >= + (PBYTE)&(_Data->IndexHeader) + + _Data->IndexHeader.FirstIndexEntry ); + + NextEntry = (PBYTE)GetNextEntry( EntryToRemove ); + + BytesToMove = _Data->IndexHeader.FirstFreeByte - + (ULONG)( NextEntry - (PBYTE)&(_Data->IndexHeader) ); + + _Data->IndexHeader.FirstFreeByte -= EntryToRemove->Length; + _Data->IndexHeader.BytesAvailable = _Data->IndexHeader.FirstFreeByte; + + _DataLength -= EntryToRemove->Length; + + memmove( EntryToRemove, + NextEntry, + BytesToMove ); + +} + + + +VOID +NTFS_INDEX_ROOT::Recreate( + IN BOOLEAN IsLeaf, + IN VCN EndEntryDownpointer + ) +/*++ + +Routine Description: + + This method recreates the Index Root with only an end entry. + +Arguments: + + IsLeaf -- supplies an indicator whether this index root + is a leaf (TRUE) or a node (FALSE). + EndEntryDownpointer -- supplies the VCN DownPointer for the End + Entry. (If IsLeaf is TRUE, this parameter + is ignored.) + +Return Value: + + None. + +Notes: + + The basic information of the index root is not changed; just the + index entries and flags. + +--*/ +{ + PINDEX_ENTRY EndEntry; + ULONG EndEntrySize; + + _IsModified = TRUE; + _DataLength = sizeof( INDEX_ROOT ) + NtfsIndexLeafEndEntrySize; + + EndEntrySize = NtfsIndexLeafEndEntrySize; + + if( !IsLeaf ) { + + _Data->IndexHeader.Flags = INDEX_NODE; + _DataLength += sizeof(VCN); + EndEntrySize += sizeof( VCN ); + + } else { + + _Data->IndexHeader.Flags = 0; + } + + DebugAssert( _MaximumSize >= _DataLength ); + + _Data->IndexHeader.FirstFreeByte = sizeof( INDEX_HEADER ) + + EndEntrySize; + + _Data->IndexHeader.BytesAvailable = _Data->IndexHeader.FirstFreeByte; + + // Fill in the end entry. Its only meaningful fields are Length, + // Flags, and downpointer (if any). + + EndEntry = GetFirstEntry(); + + memset(&(EndEntry->FileReference), 0, sizeof(FILE_REFERENCE)); + EndEntry->Length = (USHORT)EndEntrySize; + EndEntry->AttributeLength = 0; + EndEntry->Flags = INDEX_ENTRY_END; + + if( !IsLeaf ) { + + EndEntry->Flags |= INDEX_ENTRY_NODE; + GetDownpointer( EndEntry ) = EndEntryDownpointer; + } +} + + +BOOLEAN +NTFS_INDEX_ROOT::Write( + PNTFS_ATTRIBUTE RootAttribute + ) +/*++ + +Routine Description: + + This method writes the index root to the supplied attribute. + +Arguments: + + RootAttribute -- supplies the INDEX_ROOT attribute. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + ULONG BytesWritten; + + // Resize the attribute to the correct size, and then write + // the root's data to it. Since this attribute is always + // resident, pass in NULL for the bitmap. + // + + return( RootAttribute->IsResident() && + RootAttribute->Resize( _DataLength, NULL ) && + RootAttribute->Write( _Data, + 0, + _DataLength, + &BytesWritten, + NULL ) && + BytesWritten == _DataLength ); +} diff --git a/untfs/src/indxtree.cxx b/untfs/src/indxtree.cxx new file mode 100644 index 0000000..c14c03e --- /dev/null +++ b/untfs/src/indxtree.cxx @@ -0,0 +1,1893 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + indxtree.cxx + +Abstract: + + This module contains the member function definitions for the + NTFS_INDEX_TREE class, which models index trees on an NTFS + volume. + + An NTFS Index Tree consists of an index root and a set of + index buffers. The index root is stored as the value of + an INDEX_ROOT attribute; the index buffers are part of the + value of an INDEX_ALLOCATION attribute. + +--*/ + + +#include "ulib.hxx" + +#include "untfs.hxx" + +#include "drive.hxx" + +#include "attrib.hxx" +#include "frs.hxx" +#include "indxtree.hxx" +#include "indxbuff.hxx" +#include "indxroot.hxx" +#include "ntfsbit.hxx" +#include "upcase.hxx" +#include "message.hxx" + + +CONST USHORT IndexEntryAttributeLength[] = { 4, 8, 12, 16 }; + +LONG +CompareNtfsFileNames( + IN PCFILE_NAME Name1, + IN PCFILE_NAME Name2, + IN PNTFS_UPCASE_TABLE UpcaseTable + ) +/*++ + +Routine Description: + + This method compares two FILE_NAME structures according to the + COLLATION_FILE_NAME collation rule. + +Arguments: + + Name1 -- Supplies the first name to compare. + Name2 -- Supplies the second name to compare. + UpcaseTable -- Supplies the volume upcase table. + +Returns: + + <0 if Name1 is less than Name2 + =0 if Name1 is equal to Name2 + >0 if Name1 is greater than Name2. + +--*/ +{ + LONG Result; + + Result = NtfsUpcaseCompare( NtfsFileNameGetName( Name1 ), + Name1->FileNameLength, + NtfsFileNameGetName( Name2 ), + Name2->FileNameLength, + UpcaseTable, + TRUE ); + + return Result; +} + +LONG +NtfsCollate( + IN PCVOID Value1, + IN ULONG Length1, + IN PCVOID Value2, + IN ULONG Length2, + IN COLLATION_RULE CollationRule, + IN PNTFS_UPCASE_TABLE UpcaseTable + ) +/*++ + +Routine Description: + + This function compares two values according to an NTFS + collation rule. + +Arguments: + + Value1 -- Supplies the first value. + Length1 -- Supplies the length of the first value. + Value2 -- Supplies the second value. + Length2 -- Supplies the length of the second value. + CollationRule -- Supplies the rule used for collation. + UpcaseTable -- Supplies the volume upcase table. (May be NULL + if the collatio rule is not COLLATION_FILE_NAME). + +Return Value: + + <0 if Entry1 is less than Entry2 by CollationRule + 0 if Entry1 is equal to Entry2 by CollationRule + >0 if Entry1 is greater than Entry2 by CollationRule + +Notes: + + The upcase table is only required for comparing file names. + + If two values are compared according to an unsupported collation + rule, they are always treated as equal. + +--*/ +{ + LONG result; + + switch( CollationRule ) { + + case COLLATION_BINARY : + + // Binary collation of the values. + // + result = memcmp( Value1, + Value2, + MIN( Length1, Length2 ) ); + + if( result != 0 ) { + + return result; + + } else { + + return( Length1 - Length2 ); + } + + case COLLATION_FILE_NAME : + + return CompareNtfsFileNames( (PFILE_NAME)Value1, + (PFILE_NAME)Value2, + UpcaseTable ); + + + case COLLATION_UNICODE_STRING : + + // unsupported collation rule. + // + return 0; + + case COLLATION_ULONG: + + // Unsigned long collation + + DebugAssert(Length1 == sizeof(ULONG)); + DebugAssert(Length1 == sizeof(ULONG)); + + if (*(ULONG*)Value1 < *(ULONG *)Value2) + return -1; + else if (*(ULONG*)Value1 > *(ULONG *)Value2) + return 1; + else + return 0; + + case COLLATION_SID: + + // SecurityId collation + + result = memcmp(&Length1, &Length2, sizeof(Length1)); + if (result != 0) + return result; + + result = memcmp( Value1, Value2, Length1 ); + return result; + + case COLLATION_SECURITY_HASH: { + + // Security Hash (Hash key and SecurityId) Collation + + PSECURITY_HASH_KEY HashKey1 = (PSECURITY_HASH_KEY)Value1; + PSECURITY_HASH_KEY HashKey2 = (PSECURITY_HASH_KEY)Value2; + + DebugAssert(Length1 == sizeof(SECURITY_HASH_KEY)); + DebugAssert(Length2 == sizeof(SECURITY_HASH_KEY)); + + if (HashKey1->Hash < HashKey2->Hash) + return -1; + else if (HashKey1->Hash > HashKey2->Hash) + return 1; + else if (HashKey1->SecurityId < HashKey2->SecurityId) + return -1; + else if (HashKey1->SecurityId > HashKey2->SecurityId) + return 1; + else + return 0; + } + + case COLLATION_ULONGS: { + PULONG pu1, pu2; + ULONG count; + + result = 0; + + DebugAssert( (Length1 & 3) == 0 ); + DebugAssert( (Length2 & 3) == 0 ); + + count = Length1; + if (count != Length2) { + result = -1; + if (count > Length2) { + count = Length2; + result = 1; + } + } + + pu1 = (PULONG)Value1; + pu2 = (PULONG)Value2; + + while (count > 0) { + if (*pu1 > *pu2) { + return 1; + } else if (*(pu1++) < *(pu2++)) { + return -1; + } + count -= 4; + } + return result; + } + + default: + + DebugAbort( "Unsupported collation rule.\n" ); + return 0; + } +} + + + + +LONG +CompareNtfsIndexEntries( + IN PCINDEX_ENTRY Entry1, + IN PCINDEX_ENTRY Entry2, + IN COLLATION_RULE CollationRule, + IN PNTFS_UPCASE_TABLE UpcaseTable + ) +/*++ + +Routine Description: + + This global function is used to compare index entries. + +Arguments: + + Entry1 -- Supplies the first entry to compare. + Entry2 -- Supplies the second entry to compare. + CollationRule -- Supplies the rule used for collation. + UpcaseTable -- Supplies the volume upcase table. + +Return Value: + + <0 if Entry1 is less than Entry2 by CollationRule + 0 if Entry1 is equal to Entry2 by CollationRule + >0 if Entry1 is greater than Entry2 by CollationRule + +Notes: + + The upcase table is only required for comparing file names. + +--*/ +{ + return NtfsCollate( GetIndexEntryValue( Entry1 ), + Entry1->AttributeLength, + GetIndexEntryValue( Entry2 ), + Entry2->AttributeLength, + CollationRule, + UpcaseTable ); +} + + + +DEFINE_CONSTRUCTOR( NTFS_INDEX_TREE, OBJECT ); + + +NTFS_INDEX_TREE::~NTFS_INDEX_TREE( + ) +{ + Destroy(); +} + +VOID +NTFS_INDEX_TREE::Construct( + ) +/*++ + +Routine Description: + + Worker function for object construction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _Drive = NULL; + _ClusterFactor = 0; + _ClustersPerBuffer = 0; + _BufferSize = 0; + _VolumeBitmap = NULL; + _UpcaseTable = NULL; + _AllocationAttribute = NULL; + _IndexAllocationBitmap = NULL; + _IndexRoot = NULL; + _Name = NULL; + + _IteratorState = INDEX_ITERATOR_RESET; + _CurrentEntry = NULL; + _CurrentBuffer = NULL; + _CurrentKey = NULL; + _CurrentKeyLength = 0; +} + +VOID +NTFS_INDEX_TREE::Destroy( + ) +/*++ + +Routine Description: + + This method cleans up an NTFS_INDEX_TREE object in preparation + for destruction or reinitialization. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _Drive = NULL; + _ClustersPerBuffer = 0; + _BufferSize = 0; + _VolumeBitmap = NULL; + _UpcaseTable = NULL; + + DELETE( _AllocationAttribute ); + DELETE( _IndexAllocationBitmap ); + DELETE( _IndexRoot ); + DELETE( _Name ); + + _IteratorState = INDEX_ITERATOR_RESET; + + _CurrentEntry = NULL; + DELETE( _CurrentBuffer ); + FREE( _CurrentKey ); + + _CurrentKeyLength = 0; +} + + +BOOLEAN +NTFS_INDEX_TREE::Initialize( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN OUT PNTFS_BITMAP VolumeBitmap, + IN PNTFS_UPCASE_TABLE UpcaseTable, + IN ULONG MaximumRootSize, + IN PNTFS_FILE_RECORD_SEGMENT SourceFrs, + IN PCWSTRING IndexName + ) +/*++ + +Routine Description: + + This method initializes an NTFS_INDEX_TREE based on + attributes queried from a File Record Segment. + +Arguments: + + Drive -- supplies the drive on which the + index resides. + ClusterFactor -- supplies the cluster factor for the drive. + VolumeBitmap -- supplies the volume bitmap. + MaximumRootSize -- supplies the maximum length of the index root + SourceFrs -- supplies the File Record Segment that contains + this index. + UpcaseTable -- supplies the volume upcase table. + IndexName -- supplies the name for this index. (May be NULL, + in which case the index has no name.) + +Return Value: + + TRUE upon successful completion. + +Notes: + + SourceFrs must have an $INDEX_ROOT attribute, or this method will + fail. + + The index tree does not remember what File Record Segment it came + from; it only uses the FRS as a place to get the index root and + index allocation attributes. + + The volume upcase table is only required if the indexed attribute + type code is $FILE_NAME. + +--*/ +{ + NTFS_ATTRIBUTE RootAttribute; + NTFS_ATTRIBUTE BitmapAttribute; + + BIG_INT ValueLength; + ULONG NumberOfBuffers; + BOOLEAN Error; + + Destroy(); + + DebugAssert(0 != ClusterFactor); + + if( !SourceFrs->QueryAttribute( &RootAttribute, + &Error, + $INDEX_ROOT, + IndexName ) || + (_IndexRoot = NEW NTFS_INDEX_ROOT) == NULL || + !_IndexRoot->Initialize( &RootAttribute, + UpcaseTable, + MaximumRootSize ) ) { + + Destroy(); + return FALSE; + } + + _Drive = Drive; + _ClusterFactor = ClusterFactor; + _ClustersPerBuffer = _IndexRoot->QueryClustersPerBuffer(); + _BufferSize = _IndexRoot->QueryBufferSize(); + _VolumeBitmap = VolumeBitmap; + _UpcaseTable = UpcaseTable; + + DebugAssert(0 != _BufferSize); + + if( RootAttribute.GetName() != NULL && + ( (_Name = NEW DSTRING) == NULL || + !_Name->Initialize( RootAttribute.GetName() ) ) ) { + + Destroy(); + return FALSE; + } + + _IndexedAttributeType = _IndexRoot->QueryIndexedAttributeType(); + _CollationRule = _IndexRoot->QueryCollationRule(); + + if( SourceFrs->IsAttributePresent( $INDEX_ALLOCATION, IndexName ) ) { + + if( (_AllocationAttribute = NEW NTFS_ATTRIBUTE) == NULL || + !SourceFrs->QueryAttribute( _AllocationAttribute, + &Error, + $INDEX_ALLOCATION, + IndexName ) ) { + + Destroy(); + return FALSE; + } + + // Set (ie. initialize and read) the bitmap associated with + // the index allocation attribute. Note that the bitmap + // attribute's value may be larger than necessary to cover + // the allocation attribute because the bitmap attribute's + // value always grows in increments of eight bytes. However, + // at this point, we don't care, since we only worry about + // that when we grow the bitmap. + + _AllocationAttribute->QueryValueLength( &ValueLength ); + + DebugAssert( ValueLength % _BufferSize == 0 ); + + NumberOfBuffers = ValueLength.GetLowPart()/_BufferSize; + + + if( (_IndexAllocationBitmap = NEW NTFS_BITMAP) == NULL || + !_IndexAllocationBitmap->Initialize( NumberOfBuffers, TRUE ) || + !SourceFrs->QueryAttribute( &BitmapAttribute, + &Error, + $BITMAP, + IndexName ) || + !_IndexAllocationBitmap->Read( &BitmapAttribute ) ) { + + Destroy(); + return FALSE; + } + } + + // Set up the buffer to support iteration. This buffer must be + // big enough to hold the largest key value. The size of an + // index allocation buffer will suffice. + + _IteratorState = INDEX_ITERATOR_RESET; + _CurrentKeyMaxLength = _BufferSize; + + if( (_CurrentKey = MALLOC( _CurrentKeyMaxLength )) == NULL ) { + + Destroy(); + return FALSE; + } + + _CurrentKeyLength = 0; + + if( !_CurrentEntryTrail.Initialize() ) { + + Destroy(); + return FALSE; + } + + return TRUE; +} + + + +BOOLEAN +NTFS_INDEX_TREE::Initialize( + IN ATTRIBUTE_TYPE_CODE IndexedAttributeType, + IN OUT PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor, + IN OUT PNTFS_BITMAP VolumeBitmap, + IN PNTFS_UPCASE_TABLE UpcaseTable, + IN COLLATION_RULE CollationRule, + IN ULONG BufferSize, + IN ULONG MaximumRootSize, + IN PCWSTRING IndexName + ) +/*++ + +Routine Description: + + This method initializes an NTFS_INDEX_TREE based on its basic + information. It is used when creating an index. + +Arguments: + + Drive -- supplies the drive on which the + index resides. + VolumeBitmap -- supplies the volume bitmap + UpcaseTable -- supplies the volume upcase table. + IndexedAttributeType -- supplies the attribute type code of the + attribute which is used as the key for + this index. + CollationRule -- supplies the collation rule for this index. + BufferSize -- supplies the size of each Index Buffer in this index. + MaximumRootSize -- supplies the maximum length of the index root + IndexName -- supplies the name of this index. (May be + NULL, in which case the index has no name.) + +Return Value: + + TRUE upon successful completion. + + The volume upcase table is only required if the indexed attribute + type code is $FILE_NAME. + +--*/ +{ + ULONG ClusterSize; + + Destroy(); + + DebugAssert(0 != ClusterFactor); + DebugPtrAssert(Drive); + + _Drive = Drive; + _BufferSize = BufferSize; + _VolumeBitmap = VolumeBitmap; + _UpcaseTable = UpcaseTable; + _ClusterFactor = ClusterFactor; + + ClusterSize = Drive->QuerySectorSize()*ClusterFactor; + + DebugAssert(ClusterSize <= 64 * 1024); + + _ClustersPerBuffer = BufferSize / ((BufferSize < ClusterSize) ? + NTFS_INDEX_BLOCK_SIZE : ClusterSize); + + if( IndexName != NULL && + ( (_Name = NEW DSTRING) == NULL || + !_Name->Initialize( IndexName ) ) ) { + + Destroy(); + return FALSE; + } + + _IndexedAttributeType = IndexedAttributeType; + _CollationRule = CollationRule; + + _AllocationAttribute = NULL; + _IndexAllocationBitmap = NULL; + + if( (_IndexRoot = NEW NTFS_INDEX_ROOT) == NULL || + !_IndexRoot->Initialize( IndexedAttributeType, + CollationRule, + UpcaseTable, + _ClustersPerBuffer, + BufferSize, + MaximumRootSize ) ) { + + Destroy(); + return FALSE; + } + + + // Set up the buffer to support iteration. This buffer must be + // big enough to hold the largest key value. The size of an + // index allocation buffer will suffice. + + _IteratorState = INDEX_ITERATOR_RESET; + _CurrentKeyMaxLength = BufferSize; + + if( (_CurrentKey = MALLOC( _CurrentKeyMaxLength )) == NULL ) { + + Destroy(); + return FALSE; + } + + _CurrentKeyLength = 0; + + if( !_CurrentEntryTrail.Initialize() ) { + + Destroy(); + return FALSE; + } + + return TRUE; +} + +BOOLEAN +NTFS_INDEX_TREE::InsertEntry( + IN PCINDEX_ENTRY NewEntry, + IN BOOLEAN NoDuplicates, + IN PBOOLEAN Duplicate + ) +/*++ + +Routine Description: + + This method adds an entry to the index. + +Arguments: + + NewEntry -- supplies the new entry to add to the index. + NoDuplicates -- Supplies a flag which, if TRUE, indicates + that InsertEntry should fail if a matching + entry is already present in the index. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + INTSTACK ParentTrail; + + PNTFS_INDEX_BUFFER ContainingBuffer; + PINDEX_ENTRY FoundEntry; + ULONG Ordinal; + BOOLEAN Found; + BOOLEAN Result; + BOOLEAN dup; + + if (Duplicate == NULL) + Duplicate = &dup; + + // First, find the spot in the tree where we want to insert the + // new entry. + // + // If the client does not allow duplicates, search for the first + // matching entry--if we find a match, refuse the insert; if we + // don't, FindEntry will find the insertion point for us. + // + // If the client does allow duplicates, call FindEntry with + // a value INDEX_SKIP, which indicates all matching entries + // should be skipped. Thus, the new entry will be inserted + // after all matching entries. + // + Ordinal = NoDuplicates ? 0 : (INDEX_SKIP); + + Found = FindEntry( NewEntry->AttributeLength, + GetIndexEntryValue( NewEntry ), + Ordinal, + &FoundEntry, + &ContainingBuffer, + &ParentTrail ); + + *Duplicate = Found; + + if( Found && NoDuplicates ) { + + // A matching entry already exists, and the client wants + // to fail in that case. So fail. + // + + if ( ContainingBuffer ) + DELETE( ContainingBuffer ); + + return FALSE; + } + + DebugAssert( !Found ); + + // Since no matching entry was found, FindEntry will + // return a leaf entry as its insertion point. This + // makes this code a lot easier, since we only need + // to handle inserting a new leaf. + // + if( FoundEntry == NULL ) { + + // An error occurred trying to insert the entry. + + return FALSE; + } + + if( ContainingBuffer == NULL ) { + + // The root is also a leaf (see comment above), so we'll + // insert the new entry into it. + + return( InsertIntoRoot( NewEntry, FoundEntry ) ); + + } else { + + // We've found a leaf buffer, so we'll insert the new + // entry into it. + + Result = InsertIntoBuffer( ContainingBuffer, + &ParentTrail, + NewEntry, + FoundEntry ); + + DELETE( ContainingBuffer ); + return Result; + } +} + + + +BOOLEAN +NTFS_INDEX_TREE::Save( + IN OUT PNTFS_FILE_RECORD_SEGMENT TargetFrs + ) +/*++ + +Routine Description: + + This method saves the index. The root is saved as an INDEX_ROOT + attribute in the target File Record Segment; the index allocation + (if any) is saved as an INDEX_ALLOCATION attribute. + +Arguments: + + TargetFrs -- supplies the File Record Segment in which to save + the index. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + NTFS_ATTRIBUTE RootAttribute; + NTFS_ATTRIBUTE BitmapAttribute; + + BOOLEAN Error; + + DebugAssert( ( _IndexAllocationBitmap == NULL && + _AllocationAttribute == NULL ) || + ( _IndexAllocationBitmap != NULL && + _AllocationAttribute != NULL ) ); + + + + // Fetch or create attributes for the Index Root and (if necessary) + // the allocation bitmap. If either is to be newly created, make + // it resident with zero length (since writing it it resize it + // appropriately). + + if( !TargetFrs->QueryAttribute( &RootAttribute, + &Error, + $INDEX_ROOT, + _Name ) && + ( Error || + !RootAttribute.Initialize( _Drive, + _ClusterFactor, + NULL, + 0, + $INDEX_ROOT, + _Name ) ) ) { + + return FALSE; + } + + if( _IndexAllocationBitmap != NULL && + !TargetFrs->QueryAttribute( &BitmapAttribute, + &Error, + $BITMAP, + _Name ) && + ( Error || + !BitmapAttribute.Initialize( _Drive, + _ClusterFactor, + NULL, + 0, + $BITMAP, + _Name ))) { + + return FALSE; + } + + // If this tree does not have an allocation attribute, purge + // any existing stale allocation & bitmap attributes. + // + if( _AllocationAttribute == NULL && + (!TargetFrs->PurgeAttribute( $INDEX_ALLOCATION, _Name ) || + !TargetFrs->PurgeAttribute( $BITMAP, _Name )) ) { + + return FALSE; + } + + + // Now save the attributes that describe this tree. + // + if( !_IndexRoot->Write( &RootAttribute ) || + !RootAttribute.InsertIntoFile( TargetFrs, _VolumeBitmap ) ) { + + return FALSE; + } + + + if( _AllocationAttribute == NULL ) { + return TRUE; + } + + if( !_IndexAllocationBitmap->Write( &BitmapAttribute, _VolumeBitmap )) { + DebugPrint("UNTFS: Could not write index allocation bitmap\n"); + return FALSE; + } + + if( !BitmapAttribute.InsertIntoFile( TargetFrs, _VolumeBitmap )) { + + DebugPrint("UNTFS: Could not insert bitmap attribute\n"); + + // Try a second time after making sure the attribute is non-resident. + // + + if( !BitmapAttribute.MakeNonresident( _VolumeBitmap ) || + !BitmapAttribute.InsertIntoFile( TargetFrs, _VolumeBitmap )) { + + DebugPrint("UNTFS: Still could not insert bitmap attr.\n"); + return FALSE; + } + } + + if( !_AllocationAttribute->InsertIntoFile( TargetFrs, _VolumeBitmap )) { + + DebugPrintTrace(("UNTFS: Could not insert allocation attribute\n")); + return FALSE; + } + + return TRUE; +} + + + +VOID +NTFS_INDEX_TREE::FreeAllocation( + ) +/*++ + +Routine Description: + + This method frees the disk space associated with this index's + Allocation Attribute. + +Arguments: + + None. + +Return Value: + + None. + +Notes: + + This method may leave the tree in a corrupt state, since it + truncates the allocation attribute to zero without cleaning + up downpointers in the root. Use with care. + +--*/ +{ + if( _AllocationAttribute != NULL ) { + + _AllocationAttribute->Resize( 0, _VolumeBitmap ); + } +} + + +BOOLEAN +NTFS_INDEX_TREE::UpdateFileName( + IN PCFILE_NAME Name, + IN FILE_REFERENCE FileReference + ) +/*++ + +Routine Description: + + This method updates the duplicated information in a file name + index entry. + +Arguments: + + Name -- Supplies the file name structure with the new + duplicated information. + FileReference -- Supplies the file reference for the file to + which this name belongs. (Note that this is + the base FRS for that file, not necessarily the + exact FRS that contains the name.) + +Return Value: + + TRUE upon successful completion. + +Notes: + + This operation is meaningless on an index that is not constructed + over the $FILE_NAME attribute. + +--*/ +{ + INTSTACK ParentTrail; + PINDEX_ENTRY FoundEntry; + PNTFS_INDEX_BUFFER ContainingBuffer = NULL; + PFILE_NAME TargetName; + BOOLEAN Result; + + DebugPtrAssert( Name ); + + if( QueryIndexedAttributeType() != $FILE_NAME || + QueryCollationRule() != COLLATION_FILE_NAME ) { + + DebugAbort( "Updating file name in an index that isn't over $FILE_NAME.\n" ); + return FALSE; + } + + // OK, find the entry that corresponds to the input. Note that the + // collation rule for File Names ignores everything but the actual + // file name portion of the key value. + + if( !FindEntry( NtfsFileNameGetLength( Name ), + (PVOID)Name, + 0, + &FoundEntry, + &ContainingBuffer, + &ParentTrail ) ) { + + // If FoundEntry is NULL, FindEntry failed because of an error; + // otherwise, there is no matching entry in the index, which + // means there's nothing to update. + // + + DebugPrint( "UpdateFileName--index entry not found.\n" ); + Result = ( FoundEntry != NULL ); + + } else { + + // We've found an entry. As an extra sanity check, make sure + // that the file reference for the found entry is the same as + // the input file reference. + + if( memcmp( &(FoundEntry->FileReference), + &(FileReference), + sizeof( FILE_REFERENCE ) ) != 0 ) { + + DebugPrint( "File references don't match in UpdateFileName.\n" ); + Result = TRUE; + + } else { + + // Copy the duplicated information and update the file-name bits. + // + TargetName = (PFILE_NAME)(GetIndexEntryValue(FoundEntry)); + TargetName->Info = Name->Info; + TargetName->Flags = Name->Flags; + + if( ContainingBuffer != NULL ) { + + // This entry is in a buffer, so we have to write the + // buffer while we've still got it. + // + Result = ContainingBuffer->Write( _AllocationAttribute ); + + } else { + + // This entry is in the root, so we're done. + // + Result = TRUE; + } + } + } + + DELETE( ContainingBuffer ); + return Result; +} + + +BOOLEAN +NTFS_INDEX_TREE::IsIndexEntryCorrupt( + IN PCINDEX_ENTRY IndexEntry, + IN ULONG MaximumLength, + IN OUT PMESSAGE Message, + IN INDEX_ENTRY_TYPE IndexEntryType + ) +{ + ULONG len; + + if (sizeof(INDEX_ENTRY) > MaximumLength) + { + return TRUE; + } + + if (IndexEntry->Length != QuadAlign(IndexEntry->Length) || + IndexEntry->Length > MaximumLength) + { + return TRUE; + } + + len = ((IndexEntry->Flags & INDEX_ENTRY_NODE) ? sizeof(VCN) : 0) + + ((IndexEntry->Flags & INDEX_ENTRY_END) ? 0 : IndexEntry->AttributeLength) + + sizeof(INDEX_ENTRY); + + DebugAssert(INDEX_ENTRY_WITH_DATA_TYPE_4 == 0 && + INDEX_ENTRY_WITH_DATA_TYPE_8 == 1 && + INDEX_ENTRY_WITH_DATA_TYPE_12 == 2 && + INDEX_ENTRY_WITH_DATA_TYPE_16 == 3); + + switch (IndexEntryType) { + case INDEX_ENTRY_WITH_DATA_TYPE_4: + case INDEX_ENTRY_WITH_DATA_TYPE_8: + case INDEX_ENTRY_WITH_DATA_TYPE_12: + case INDEX_ENTRY_WITH_DATA_TYPE_16: + if (!(IndexEntry->Flags & INDEX_ENTRY_END) && + IndexEntry->AttributeLength != IndexEntryAttributeLength[IndexEntryType]) + { + return TRUE; + } + + // fall through + + case INDEX_ENTRY_WITH_DATA_TYPE: + if (QuadAlign(IndexEntry->DataOffset + IndexEntry->DataLength) > + IndexEntry->Length) + { + return TRUE; + } + + len += IndexEntry->DataLength; + + // fall through + + case INDEX_ENTRY_WITH_FILE_NAME_TYPE: + if (IndexEntry->Length != QuadAlign(len)) + { + return TRUE; + } else + return FALSE; + } + + if (QuadAlign(len) > IndexEntry->Length) + { + return TRUE; + } + else + return FALSE; +} + + +BOOLEAN +NTFS_INDEX_TREE::FindEntry( + IN ULONG KeyLength, + IN PVOID KeyValue, + IN ULONG Ordinal, + OUT PINDEX_ENTRY* FoundEntry, + OUT PNTFS_INDEX_BUFFER* ContainingBuffer, + OUT PINTSTACK ParentTrail + ) +/*++ + +Routine Description: + + This method locates an entry (based on its key value) in + the index tree. If no matching entry is found, it locates + the first leaf entry which is greater than the search value + (i.e. the point at which the search value would be inserted + into the tree). + +Arguments: + + KeyLength -- supplies the length, in bytes, of the + search value + KeyValue -- supplies the search value + Ordinal -- supplies the (zero-based) ordinal of the + matching entry to return. (zero returns + the first matching value). + + Note that a value of INDEX_SKIP skips + all matching entries. + + FoundEntry -- Receives a pointer to the located entry + (NULL indicates error). + ContainingBuffer -- Receives a pointer to the index buffer + containing the returned entry (NULL if the + entry is in the root). + ParentTrail -- Receives the parent trail of ContainingBuffer + (ie. the VCNs of that buffer's ancestors). + If the entry is in the root, this object + may be left uninitialized. + +Return Value: + + TRUE If a matching entry is found. + + FALSE if no matching entry was found. If no error occurred, then + *FoundEntry will point at the place in the tree where the search + value would be inserted. + + If the method fails due to error, it returns FALSE and sets + *FoundEntry to NULL. + + Note that if FindEntry does not find a matching entry, it will + always return a leaf entry. + +--*/ +{ + PINDEX_ENTRY SearchEntry; + VCN CurrentBufferVcn; + PNTFS_INDEX_BUFFER CurrentBuffer=NULL; + BOOLEAN Finished = FALSE; + BOOLEAN Result = FALSE; + USHORT SearchEntryLength; + + // Rig up an index-entry to pass to the index root and buffers: + + SearchEntryLength = (USHORT)QuadAlign( sizeof( INDEX_ENTRY ) + KeyLength ); + + if( (SearchEntry = (PINDEX_ENTRY)MALLOC( SearchEntryLength )) == NULL ) { + + // Return the error. + + *FoundEntry = NULL; + return FALSE; + + } + + SearchEntry->Length = SearchEntryLength; + SearchEntry->AttributeLength = (USHORT)KeyLength; + + memcpy( GetIndexEntryValue( SearchEntry ), + KeyValue, + KeyLength ); + + + // See if the entry we want is in the index root: + + if( _IndexRoot->FindEntry( SearchEntry, + &Ordinal, + FoundEntry ) ) { + + // The desired entry is in the root. *FoundEntry has been set + // by the Index Root; fill in the other return parameters + + *ContainingBuffer = NULL; + Result = TRUE; + + } else if ( *FoundEntry == NULL ) { + + // An error occurred trying to find the entry. + + *ContainingBuffer = NULL; + Result = FALSE; + + } else if( !((*FoundEntry)->Flags & INDEX_ENTRY_NODE) || + GetDownpointer( *FoundEntry ) == INVALID_VCN ) { + + // The entry we want isn't in the root, and the root is a leaf, + // so it's not in the tree. Return the entry we did find, and + // return 'not found' to the client. + + *ContainingBuffer = NULL; + Result = FALSE; + + } else { + + // We didn't find the entry we want in the index root, and + // the root is not a leaf, so we'll start looking through the + // index allocation buffers. + + // First, we have to allocate an index allocation buffer + // for our search. If all goes well, we'll return this + // buffer to the client. Initialize the parent trail, but + // leave it empty (indicating that we're at the root). + + if( !ParentTrail->Initialize() || + (CurrentBuffer = NEW NTFS_INDEX_BUFFER) == NULL ) { + + *FoundEntry = NULL; + } + + if (_AllocationAttribute == NULL) { + + + *FoundEntry = NULL; + } + + while( *FoundEntry != NULL && !Finished ) { + + DebugAssert( ((*FoundEntry)->Flags & INDEX_ENTRY_NODE) && + GetDownpointer( *FoundEntry ) != INVALID_VCN ); + + CurrentBufferVcn = GetDownpointer( *FoundEntry ); + + if( !CurrentBuffer->Initialize( _Drive, + CurrentBufferVcn, + _ClusterFactor * _Drive->QuerySectorSize(), + _ClustersPerBuffer, + _BufferSize, + _CollationRule, + _UpcaseTable ) || + !CurrentBuffer->Read( _AllocationAttribute ) ) { + + *FoundEntry = NULL; + + } else if( CurrentBuffer->FindEntry( SearchEntry, + &Ordinal, + FoundEntry ) ) { + + // We found the entry we want. + + Finished = TRUE; + Result = TRUE; + + } else if ( *FoundEntry != NULL && + (!((*FoundEntry)->Flags & INDEX_ENTRY_NODE) || + GetDownpointer( *FoundEntry ) == INVALID_VCN) ) { + + // This buffer is a leaf, so the entry we want isn't + // to be found. Instead, we'll return this entry, along + // with a result of FALSE to indicate 'not found'. + + Finished = TRUE; + Result = FALSE; + + } else { + + // We have to recurse down another level in the tree. + // Add the current buffer's VCN to the parent trail. + + if( !ParentTrail->Push( CurrentBufferVcn ) ) { + + // Error. Drop out of the loop and into the error + // handling. + + *FoundEntry = NULL; + } + } + } + + if( *FoundEntry == NULL ) { + + // We're returning an error, so we have to clean up. + + DELETE( CurrentBuffer ); + CurrentBuffer = NULL; + *ContainingBuffer = NULL; + Result = FALSE; + + } else { + + // We're returning an entry--either the one the client + // wants or the next leaf. Either way, it's contained + // in the current buffer, so we need to return that, too. + + *ContainingBuffer = CurrentBuffer; + } + } + + FREE( SearchEntry ); + + return Result; +} + + +BOOLEAN +NTFS_INDEX_TREE::InsertIntoRoot( + PCINDEX_ENTRY NewEntry, + PINDEX_ENTRY InsertionPoint + ) +/*++ + +Routine Description: + + This method attempts to insert an entry into the Index Root + attribute. If necessary, it will twiddle the index b-tree. + +Arguments: + + NewEntry -- supplies the new index entry + InsertionPoint -- supplies a pointer to the point in the root + where the entry should be inserted, if known. + This must be a pointer that was returned by a + call to _IndexRoot->FindEntry (with no intervening + inserts or deletes). This parameter may be NULL. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + NTFS_INDEX_BUFFER NewBuffer; + INTSTACK ParentTrail; + VCN NewBufferVcn; + ULONG BytesToMove; + PINDEX_ENTRY CurrentEntry; + + // Try the easy case--NTFS_INDEX_ROOT::InsertEntry will succeed + // if there's room in the root for the new entry. + + if( _IndexRoot->InsertEntry( NewEntry, InsertionPoint ) ) { + + return TRUE; + } + + // We didn't get away with the easy case. Instead, we have to + // push the entries that are currently in the index root down + // into an index allocation buffer. Here's the plan: + // + // If we don't have an allocation attribute, create one. + // Allocate a new index buffer. + // Create it as an empty buffer. If the root is currently + // a leaf, this new buffer becomes a leaf; if not, not. + // Move all the index entries that are in the root to the + // new buffer + // Recreate the root as an empty node, and set the downpointer + // of its END entry to point at the new buffer. + + if( _AllocationAttribute == NULL && + !CreateAllocationAttribute() ) { + + // Can't create an allocation attribute. + return FALSE; + } + + + // Allocate and initialize the new buffer. Postpone creating it + // until we know what to give it as an end-entry downpointer + + if( !AllocateIndexBuffer( &NewBufferVcn ) ) { + + return FALSE; + } + + if( !NewBuffer.Initialize( _Drive, + NewBufferVcn, + _ClusterFactor * _Drive->QuerySectorSize(), + _ClustersPerBuffer, + _BufferSize, + _CollationRule, + _UpcaseTable ) ) { + + FreeIndexBuffer( NewBufferVcn ); + } + + + // Now copy all the non-end entries from the index root to + // the new buffer. + + BytesToMove = 0; + + CurrentEntry = _IndexRoot->GetFirstEntry(); + + while( !(CurrentEntry->Flags & INDEX_ENTRY_END) ) { + + BytesToMove += CurrentEntry->Length; + CurrentEntry = GetNextEntry( CurrentEntry ); + } + + // OK, now we can create the new buffer and copy the entries into + // it. + + if( CurrentEntry->Flags & INDEX_ENTRY_NODE && + GetDownpointer( CurrentEntry ) != INVALID_VCN ) { + + // Give the new buffer's end entry the downpointer from the + // root's end entry. + + NewBuffer.Create( FALSE, GetDownpointer( CurrentEntry ) ); + + } else { + + // The new buffer is a leaf. + + NewBuffer.Create( TRUE, 0 ); + } + + NewBuffer.InsertClump( BytesToMove, + _IndexRoot->GetFirstEntry() ); + + NewBuffer.Write( _AllocationAttribute ); + + + // Recreate the index root as an empty node. This will wipe out the + // old end entry, which is OK. (If it had a downpointer, we passed + // that value to the new buffer's end entry; if not, then it didn't + // have any interesting information.) + + _IndexRoot->Recreate( FALSE, NewBufferVcn ); + + // Set up an empty stack for the parent trail (since the new + // buffer's parent is the root) and insert the new entry into + // the new leaf buffer. + + return( ParentTrail.Initialize() && + InsertIntoBuffer( &NewBuffer, &ParentTrail, NewEntry ) ); +} + + +BOOLEAN +NTFS_INDEX_TREE::InsertIntoBuffer( + IN OUT PNTFS_INDEX_BUFFER TargetBuffer, + IN OUT PINTSTACK ParentTrail, + IN PCINDEX_ENTRY NewEntry, + IN PINDEX_ENTRY InsertionPoint + ) +/*++ + +Routine Description: + + This method attempts to insert an entry into an Index + Allocation Buffer. If necessary, it will split the buffer. + +Arguments: + + TargetBuffer -- supplies the buffer that will receive the + new entry. + ParentTrail -- supplies the parent trail (ie. stack of VCNs + of all buffers between here and root) of the + target buffer. If this stack is empty, then + the parent of the buffer is the root. + NewEntry -- supplies the new index entry + InsertionPoint -- supplies a pointer to the point in the root + where the entry should be inserted, if known. + This must be a pointer that was returned by a + call to TargetBuffer->FindEntry (with no + intervening inserts or deletes). This parameter + may be NULL. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method may consume ParentTrail. The client should not rely + on the state of ParentTrail after this method returns. + +--*/ +{ + PINDEX_ENTRY PromotionBuffer; + PINDEX_ENTRY SplitPoint; + NTFS_INDEX_BUFFER NewBuffer, ParentBuffer; + VCN NewBufferVcn, ParentVcn; + ULONG BytesToCopy, BytesToRemove; + BOOLEAN Result; + int CompareResult; + + // Try the easy way first--NTFS_INDEX_BUFFER will succeed if + // there's enough room in the buffer to accept this entry. + + if( TargetBuffer->InsertEntry( NewEntry, InsertionPoint ) ) { + + return( TargetBuffer->Write( _AllocationAttribute ) ); + } + + // We didn't get away with the easy case; instead, we have to + // split this index buffer. + + // Allocate a new index allocation buffer. + + if( !AllocateIndexBuffer( &NewBufferVcn ) ) { + + return FALSE; + } + + if( !NewBuffer.Initialize( _Drive, + NewBufferVcn, + _ClusterFactor * _Drive->QuerySectorSize(), + _ClustersPerBuffer, + _BufferSize, + _CollationRule, + _UpcaseTable ) ) { + + FreeIndexBuffer( NewBufferVcn ); + return FALSE; + } + + // Find the split point in the buffer we want to split. This + // entry will be promoted into the parent; the entries after it + // stay in this buffer, while the entries before it go into the + // new buffer. The new buffer will become the child of the promoted + // entry. + + SplitPoint = TargetBuffer->FindSplitPoint(); + + PromotionBuffer = (PINDEX_ENTRY)MALLOC( TargetBuffer->QuerySize() ); + + if( PromotionBuffer == NULL ) { + + FreeIndexBuffer( NewBufferVcn ); + return FALSE; + } + + memcpy( PromotionBuffer, + SplitPoint, + SplitPoint->Length ); + + if( TargetBuffer->IsLeaf() ) { + + PromotionBuffer->Flags |= INDEX_ENTRY_NODE; + PromotionBuffer->Length += sizeof(VCN); + NewBuffer.Create( TRUE, 0 ); + + } else { + + NewBuffer.Create( FALSE, GetDownpointer(PromotionBuffer) ); + } + + GetDownpointer( PromotionBuffer ) = NewBufferVcn; + + + // OK, copy all the entries before the split point into the + // new buffer. + + BytesToCopy = (ULONG)((PBYTE)SplitPoint - (PBYTE)(TargetBuffer->GetFirstEntry())); + + NewBuffer.InsertClump( BytesToCopy, TargetBuffer->GetFirstEntry() ); + + + // Now shift the remaining entries down, and adjust the target + // buffer's FirstFreeByte field by the number of bytes we moved + // to the new buffer. + + BytesToRemove = BytesToCopy + SplitPoint->Length; + + TargetBuffer->RemoveClump( BytesToRemove ); + + + // Now we decide which buffer gets the new entry, and insert it. + // If it's less than the promoted entry, it goes in the new buffer; + // otherwise, it goes in the original buffer. + + CompareResult = CompareNtfsIndexEntries( NewEntry, + PromotionBuffer, + _CollationRule, + _UpcaseTable ); + + // + // Either of the buffer should now be large enough for the new entry + // + + if( CompareResult < 0 ) { + + if (!NewBuffer.InsertEntry( NewEntry )) { + FREE(PromotionBuffer); + DebugAbort("Unable to insert the new entry into the new buffer.\n"); + return FALSE; + } + + } else { + + if (!TargetBuffer->InsertEntry( NewEntry )) { + FREE(PromotionBuffer); + DebugAbort("Unable to insert the new entry into the target buffer.\n"); + return FALSE; + } + } + + if (!TargetBuffer->Write( _AllocationAttribute ) || + !NewBuffer.Write( _AllocationAttribute )) { + FREE(PromotionBuffer); + DebugAbort("Unable to write out the contents of the buffers\n"); + return FALSE; + } + + // OK, we've finished splitting everybody, so we are ready to + // insert the promoted entry into the parent. + + if( ParentTrail->QuerySize() == 0 ) { + + // The parent of the target buffer is the root. + + Result = InsertIntoRoot( PromotionBuffer ); + + } else { + + // The target buffer's parent is another buffer, and its + // VCN is on top of the ParentTrail stack. Get that VCN, + // and then pop the stack so we can pass it to the parent + // buffer. (Popping it makes it the parent trail of the + // parent buffer.) + + ParentVcn = ParentTrail->Look(); + ParentTrail->Pop(); + + Result = ( ParentBuffer.Initialize( _Drive, + ParentVcn, + _ClusterFactor * _Drive->QuerySectorSize(), + _ClustersPerBuffer, + _BufferSize, + _CollationRule, + _UpcaseTable ) && + ParentBuffer.Read( _AllocationAttribute ) && + InsertIntoBuffer( &ParentBuffer, + ParentTrail, + PromotionBuffer ) ); + } + + FREE( PromotionBuffer ); + return Result; +} + + + + +BOOLEAN +NTFS_INDEX_TREE::AllocateIndexBuffer( + OUT PVCN NewBufferVcn + ) +/*++ + +Routine Description: + + This method allocates an index allocation buffer from the index + allocation attribute. It first checks the bitmap, to see if any + are free; if there are none free in the bitmap, it adds a new + index buffer to the end of the allocation attribute. + +Arguments: + + NewBuffer -- receives the VCN of the new buffer. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + BIG_INT ValueLength; + VCN NewBufferNumber; + ULONG NumberOfBuffers; + + + DebugPtrAssert( _AllocationAttribute != NULL && + _IndexAllocationBitmap != NULL ); + + _AllocationAttribute->QueryValueLength( &ValueLength ); + + DebugAssert( ValueLength % _BufferSize == 0 ); + + NumberOfBuffers = ValueLength.GetLowPart()/_BufferSize; + + // First, check the bitmap. Allocate as close to the beginning + // as possible (hence use 0 for the NearHere parameter). + + if( _IndexAllocationBitmap->AllocateClusters( 0, + 1, + &NewBufferNumber ) ) { + + // Found a free one in the bitmap--return it. + + DebugPrint( "Buffer allocated from index allocation bitmap.\n" ); + + if (0 == _ClustersPerBuffer) { + *NewBufferVcn = NewBufferNumber * (_BufferSize / 512) ; + } else { + *NewBufferVcn = NewBufferNumber * _ClustersPerBuffer; + } + return TRUE; + } + + + // There are no free buffers in the index allocation attribute, + // so I have to add one. + + NewBufferNumber = NumberOfBuffers; + NumberOfBuffers += 1; + + // Grow the allocation attribute by one buffer: + + if( !_AllocationAttribute->Resize( ValueLength + _BufferSize, _VolumeBitmap ) ) { + + return FALSE; + } + + + // Grow the index allocation bitmap (if necessary) to cover the + // current size of the index allocation attributes. + + if( !_IndexAllocationBitmap->Resize( NumberOfBuffers ) ) { + + // Couldn't resize the bitmap--truncate the allocation attribute + // back to its original size and return failure. + + _AllocationAttribute->Resize( ValueLength, _VolumeBitmap ); + return FALSE; + } + + // Mark the new buffer as allocated and return it. + + _IndexAllocationBitmap->SetAllocated( NewBufferNumber, 1 ); + + if (0 == _ClustersPerBuffer) { + + // The buffers are indexed by their block offset, where each block + // in the allocation is 512 bytes. + // + + *NewBufferVcn = NewBufferNumber * (_BufferSize / NTFS_INDEX_BLOCK_SIZE); + + } else { + *NewBufferVcn = NewBufferNumber * _ClustersPerBuffer; + } + + return TRUE; +} + + + +VOID +NTFS_INDEX_TREE::FreeIndexBuffer( + IN VCN BufferVcn + ) +/*++ + +Routine Description: + + This method adds a buffer, identified by VCN, to the free + buffer list. + +Arguments: + + BufferVcn -- supplies the VCN of the buffer to free. + +Return Value: + + None. + +--*/ +{ + if (0 == _ClustersPerBuffer) { + _IndexAllocationBitmap->SetFree( BufferVcn, _BufferSize/512 ); + } else { + _IndexAllocationBitmap->SetFree( BufferVcn/_ClustersPerBuffer, 1 ); + } +} + + + +ULONG +NTFS_INDEX_TREE::QueryMaximumEntrySize( + ) CONST +/*++ + +Routine Description: + + This method returns the maximum size buffer needed to hold an + index entry from this index. + +Arguments + + None. + +Return Value: + + None. + +Notes: + + The maximum entry size must be less than the buffer size for + the allocation buffers in the tree (since an entry must fit + into a buffer), so we'll return the index allocation buffer size. + +--*/ +{ + return( _BufferSize ); +} + + + +BOOLEAN +NTFS_INDEX_TREE::CreateAllocationAttribute( + ) +/*++ + +Routine Description: + + This method creates an allocation attribute. This attribute is + an empty, nonresident attribute. This method also creates an + index allocation bitmap associated with this index allocation + attribute. + +Arguments: + + None. + +Return value: + + TRUE upon successful completion. Note that if this method succeeds, + the private member data _AllocationAttribute is set to point at the + newly-created attribute and _IndexAllocationBitmap is set to point + at the newly-created bitmap. + +--*/ +{ + PNTFS_ATTRIBUTE NewAttribute; + PNTFS_BITMAP NewBitmap; + NTFS_EXTENT_LIST Extents; + + DebugAssert(0 != _ClusterFactor); + + + // Create an empty extent list. + + if( !Extents.Initialize( (ULONG)0, (ULONG)0 ) ) { + + return FALSE; + } + + + // Construct an index allocation attribute and initialize + // it with this extent list. + + if( (NewAttribute = NEW NTFS_ATTRIBUTE) == NULL || + !NewAttribute->Initialize( _Drive, + _ClusterFactor, + &Extents, + 0, + 0, + $INDEX_ALLOCATION, + _Name ) ) { + + DebugPrint( "CreateAllocationAttribute--Cannot create index allocation attribute.\n" ); + + DELETE( NewAttribute ); + return FALSE; + } + + // Create a new bitmap. Initialize it to cover zero allocation units, + // and indicate that it is growable. + + if( (NewBitmap = NEW NTFS_BITMAP) == NULL || + !NewBitmap->Initialize( 0, TRUE ) ) { + + DebugPrint( "CreateAllocationAttribute--Cannot create index allocation bitmap.\n" ); + + DELETE( NewAttribute ); + DELETE( NewBitmap ); + return FALSE; + } + + _AllocationAttribute = NewAttribute; + _IndexAllocationBitmap = NewBitmap; + + return TRUE; +} + + + diff --git a/untfs/src/largemcb.cxx b/untfs/src/largemcb.cxx new file mode 100644 index 0000000..fed5993 --- /dev/null +++ b/untfs/src/largemcb.cxx @@ -0,0 +1,2781 @@ +#include "stdafx.h" + +#include "../../common.h" + +extern "C" { +#include "fsrtlp.h" +} + +/*++ + +Module Name: + + LargeMcb.c + +Abstract: + + The MCB routines provide support for maintaining an in-memory copy of + the retrieval mapping information for a file. The general idea is to + have the file system lookup the retrieval mapping for a VBN once from + the disk, add the mapping to the MCB structure, and then utilize the + MCB to retrieve the mapping for subsequent accesses to the file. A + variable of type MCB is used to store the mapping information. + + The routines provided here allow the user to incrementally store some + or all of the retrieval mapping for a file and to do so in any order. + That is, the mapping can be inserted to the MCB structure all at once + starting from the beginning and working to the end of the file, or it + can be randomly scattered throughout the file. + + The package identifies each contiguous run of sectors mapping VBNs + and LBNs indenpendent of the order they are added to the MCB + structure. For example a user can define a mapping between VBN + sector 0 and LBN sector 107, and between VBN sector 2 and LBN sector + 109. The mapping now contains two runs each one sector in length. + Now if the user adds an additional mapping between VBN sector 1 and + LBN sector 106 the MCB structure will contain only one run 3 sectors + in length. + + Concurrent access to the MCB structure is control by this package. + + The following routines are provided by this package: + + o FsRtlInitializeMcb - Initialize a new MCB structure. There + should be one MCB for every opened file. Each MCB structure + must be initialized before it can be used by the system. + + o FsRtlUninitializeMcb - Uninitialize an MCB structure. This call + is used to cleanup any anciallary structures allocated and + maintained by the MCB. After being uninitialized the MCB must + again be initialized before it can be used by the system. + + o FsRtlAddMcbEntry - This routine adds a new range of mappings + between LBNs and VBNs to the MCB structure. + + o FsRtlRemoveMcbEntry - This routines removes an existing range of + mappings between LBNs and VBNs from the MCB structure. + + o FsRtlLookupMcbEntry - This routine returns the LBN mapped to by + a VBN, and indicates, in sectors, the length of the run. + + o FsRtlLookupLastMcbEntry - This routine returns the mapping for + the largest VBN stored in the structure. + + o FsRtlNumberOfRunsInMcb - This routine tells the caller total + number of discontiguous sectors runs stored in the MCB + structure. + + o FsRtlGetNextMcbEntry - This routine returns the the caller the + starting VBN and LBN of a given run stored in the MCB structure. + +--*/ + + +// +// Trace level for the module +// + +#define Dbg (0x80000000) + + +// +// Retrieval mapping data structures. The following two structure together +// are used to map a Vbn to an Lbn. It is layed out as follows: +// +// +// MCB: +// +----------------+----------------+ +// | PairCount |MaximumPairCount| +// +----------------+----------------+ +// | Mapping | PoolType | +// +----------------+----------------+ +// +// +// MAPPING: +// +----------------+----------------+ +// | Lbn | NextVbn | : 0 +// +----------------+----------------+ +// | | +// / / +// / / +// | | +// +----------------+----------------+ +// | Lbn | NextVbn | : PairCount +// +----------------+----------------+ +// | | +// / / +// / / +// | | +// +----------------+----------------+ +// | Lbn | NextVbn | +// +----------------+----------------+ +// +// : MaximumPairCount +// +// The pairs from 0 to PairCount - 1 are valid. Given an index between +// 0 and PairCount - 1 (inclusive) it represents the following Vbn +// to Lbn mapping information +// +// +// { if Index == 0 then 0 +// StartingVbn { +// { if Index <> 0 then NextVbn[i-1] +// +// +// EndingVbn = NextVbn[i] - 1 +// +// +// StartingLbn = Lbn[i] +// +// +// To compute the mapping of a Vbn to an Lbn the following algorithm +// is used +// +// 1. search through the pairs until we find the slot "i" that contains +// the Vbn we after. Report an error if none if found. +// +// 2. Lbn = StartingLbn + (Vbn - StartingVbn); +// +// A hole in the allocation (i.e., a sparse allocation) is represented by +// an Lbn value of -1 (note that is is different than Mcb.c). +// + + + +#define UNUSED_LBN ((LBN64)-1) + +typedef struct _MAPPING { + VBN NextVbn; + LBN64 Lbn; +} MAPPING; +typedef MAPPING *PMAPPING; + +typedef struct _NONOPAQUE_MCB { + PFAST_MUTEX FastMutex; + ULONG MaximumPairCount; + ULONG PairCount; + POOL_TYPE PoolType; + PMAPPING Mapping; +} NONOPAQUE_MCB; +typedef NONOPAQUE_MCB *PNONOPAQUE_MCB; + +C_ASSERT(sizeof(LARGE_MCB) >= sizeof(NONOPAQUE_MCB)); + +// +// A macro to return the size, in bytes, of a retrieval mapping structure +// + +#define SizeOfMapping(MCB) ((sizeof(MAPPING) * (MCB)->MaximumPairCount)) + +// +// The parts of a run can be computed as follows: +// +// +// StartingVbn(MCB,I) Mapping[I].NextVbn +// | | +// V V +// +// Run-(I-1)---+ +---------Run-(I)-----------+ +---Run-(I+1) +// +// A A +// | | +// Mapping[I].Lbn EndingLbn(MCB,I) +// + +#define PreviousEndingVbn(MCB,I) ( \ + (VBN)((I) == 0 ? 0xffffffff : EndingVbn(MCB,(I)-1)) \ +) + +#define StartingVbn(MCB,I) ( \ + (VBN)((I) == 0 ? 0 : (((MCB)->Mapping))[(I)-1].NextVbn) \ +) + +#define EndingVbn(MCB,I) ( \ + (VBN)((((MCB)->Mapping)[(I)].NextVbn) - 1) \ +) + +#define NextStartingVbn(MCB,I) ( \ + (VBN)((I) >= (MCB)->PairCount ? 0 : StartingVbn(MCB,(I)+1)) \ +) + + + + +#define PreviousEndingLbn(MCB,I) ( \ + ((I) == 0 ? UNUSED_LBN : EndingLbn(MCB,(I)-1)) \ +) + +#define StartingLbn(MCB,I) ( \ + (((MCB)->Mapping)[(I)].Lbn) \ +) + +#define EndingLbn(MCB,I) ( \ + (StartingLbn(MCB,I) == UNUSED_LBN ? \ + UNUSED_LBN : \ + ((MCB)->Mapping[(I)].Lbn + \ + (MCB)->Mapping[(I)].NextVbn - StartingVbn(MCB,I) - 1) \ + ) \ +) + +#define NextStartingLbn(MCB,I) ( \ + ((I) >= (MCB)->PairCount - 1 ? UNUSED_LBN : StartingLbn(MCB,(I)+1)) \ +) + +#if 0 +LBN +NextStartingLbn( + PNONOPAQUE_MCB Mcb, + ULONG I + ) +{ + if ( I >= Mcb->PairCount - 1 ) { + return (LBN)UNUSED_LBN; + } + else { + return StartingLbn(Mcb,I+1); + } +} +#endif + +#define SectorsWithinRun(MCB,I) ( \ + (ULONG)(EndingVbn(MCB,I) - StartingVbn(MCB,I) + 1) \ +) + + + +VOID +FsRtlRemoveMcbEntryPrivate ( + IN PNONOPAQUE_MCB OpaqueMcb, + IN ULONG Vbn, + IN ULONG SectorCount + ); + +// +// A private routine to search a mapping structure for a Vbn +// + +BOOLEAN +FsRtlFindLargeIndex ( + IN PNONOPAQUE_MCB Mcb, + IN VBN Vbn, + OUT PULONG Index + ); + +VOID +FsRtlAddLargeEntry ( + IN PNONOPAQUE_MCB Mcb, + IN ULONG WhereToAddIndex, + IN ULONG AmountToAdd + ); + +VOID +FsRtlRemoveLargeEntry ( + IN PNONOPAQUE_MCB Mcb, + IN ULONG WhereToRemoveIndex, + IN ULONG AmountToRemove + ); + +// +// Some private routines to handle common allocations. +// + +PVOID +FsRtlAllocateFirstMapping ( + ); + +VOID +FsRtlFreeFirstMapping ( + IN PVOID Mapping + ); + +PFAST_MUTEX +FsRtlAllocateFastMutex ( + ); + +VOID +FsRtlFreeFastMutex ( + IN PFAST_MUTEX FastMutex + ); + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, FsRtlInitializeMcb) +#pragma alloc_text(PAGE, FsRtlUninitializeMcb) +#endif + + +// +// Define a small cache of free mapping pairs structures and also the +// initial size of the mapping pair +// + +#define INITIAL_MAXIMUM_PAIR_COUNT (15) + +// +// Some globals used with the first mapping allocation +// + +#define FREE_FIRST_MAPPING_ARRAY_SIZE (16) + +PVOID FsRtlFreeFirstMappingArray[FREE_FIRST_MAPPING_ARRAY_SIZE]; + +UCHAR FsRtlFreeFirstMappingSize = 0; + +ULONG FsRtlNetFirstMapping = 0; + +// +// Some globals used with the FastMutex allocation +// + +#define FREE_FAST_MUTEX_ARRAY_SIZE (16) + +PFAST_MUTEX FsRtlFreeFastMutexArray[FREE_FAST_MUTEX_ARRAY_SIZE]; + +UCHAR FsRtlFreeFastMutexSize = 0; + +ULONG FsRtlNetFastMutex = 0; + +VOID +FsRtlInitializeLargeMcb ( + IN PLARGE_MCB OpaqueMcb, + IN POOL_TYPE PoolType + ) + +/*++ + +Routine Description: + + This routine initializes a new Mcb structure. The caller must + supply the memory for the Mcb structure. This call must precede all + other calls that set/query the Mcb structure. + + If pool is not available this routine will raise a status value + indicating insufficient resources. + +Arguments: + + OpaqueMcb - Supplies a pointer to the Mcb structure to initialize. + + PoolType - Supplies the pool type to use when allocating additional + internal Mcb memory. + +Return Value: + + None. + +--*/ + +{ + PNONOPAQUE_MCB Mcb = (PNONOPAQUE_MCB)OpaqueMcb; + + DebugTrace(+1, Dbg, "FsRtlInitializeLargeMcb, Mcb = %08lx\n", Mcb ); + + // + // Preset the following fields to null so we know to deallocate them + // during an abnormal termination + // + + Mcb->FastMutex = NULL; + Mcb->Mapping = NULL; + + __try { + + // + // Initialize the fields in the Mcb + // + + Mcb->FastMutex = FsRtlAllocateFastMutex(); + + ExInitializeFastMutex( Mcb->FastMutex ); + + Mcb->PairCount = 0; + Mcb->PoolType = PoolType; + + // + // Allocate a new buffer an initial size is one that will hold + // 16 runs + // + + if (PoolType == PagedPool) { + + Mcb->Mapping = (PMAPPING)FsRtlAllocateFirstMapping(); + + } else { + + Mcb->Mapping = (PMAPPING)FsRtlAllocatePool( Mcb->PoolType, sizeof(MAPPING) * INITIAL_MAXIMUM_PAIR_COUNT ); + } + + //**** RtlZeroMemory( Mcb->Mapping, sizeof(MAPPING) * INITIAL_MAXIMUM_PAIR_COUNT ); + + Mcb->MaximumPairCount = INITIAL_MAXIMUM_PAIR_COUNT; + + } __finally { + + // + // If this is an abnormal termination then we need to deallocate + // the FastMutex and/or mapping. + // + + if (AbnormalTermination()) { + + if (Mcb->FastMutex != NULL) { FsRtlFreeFastMutex( Mcb->FastMutex ); } + } + + DebugTrace(-1, Dbg, "FsRtlInitializeLargeMcb -> VOID\n", 0 ); + } + + // + // And return to our caller + // + + return; +} + + +VOID +FsRtlUninitializeLargeMcb ( + IN PLARGE_MCB OpaqueMcb + ) + +/*++ + +Routine Description: + + This routine uninitializes an Mcb structure. After calling this routine + the input Mcb structure must be re-initialized before being used again. + +Arguments: + + OpaqueMcb - Supplies a pointer to the Mcb structure to uninitialize. + +Return Value: + + None. + +--*/ + +{ + PNONOPAQUE_MCB Mcb = (PNONOPAQUE_MCB)OpaqueMcb; + + DebugTrace(+1, Dbg, "FsRtlUninitializeLargeMcb, Mcb = %08lx\n", Mcb ); + + // + // Protect against some user calling us to uninitialize an mcb twice + // + + if (Mcb->FastMutex == NULL) { + + ASSERTMSG("Being called to uninitialize an Mcb that is already Uninitialized ", FALSE); + + return; + } + + // + // Deallocate the FastMutex and mapping buffer + // + + FsRtlFreeFastMutex( Mcb->FastMutex ); + + Mcb->FastMutex = NULL; + + if ((Mcb->PoolType == PagedPool) && (Mcb->MaximumPairCount == INITIAL_MAXIMUM_PAIR_COUNT)) { + + FsRtlFreeFirstMapping( Mcb->Mapping ); + + } else { + + ExFreePool( Mcb->Mapping ); + } + + // + // Now zero our all of the fields in the Mcb + // + + //**** Mcb->MaximumPairCount = 0; + //**** Mcb->PairCount = 0; + //**** Mcb->Mapping = NULL; + + // + // And return to our caller + // + + DebugTrace(-1, Dbg, "FsRtlUninitializeLargeMcb -> VOID\n", 0 ); + + return; +} + + +VOID +FsRtlTruncateLargeMcb ( + IN PLARGE_MCB OpaqueMcb, + IN LONGLONG LargeVbn + ) + +/*++ + +Routine Description: + + This routine truncates an Mcb structure to the specified Vbn. + After calling this routine the Mcb will only contain mappings + up to and not including the input vbn. + +Arguments: + + OpaqueMcb - Supplies a pointer to the Mcb structure to truncate. + + LargeVbn - Specifies the last Vbn at which is no longer to be + mapped. + +Return Value: + + None. + +--*/ + +{ + PNONOPAQUE_MCB Mcb = (PNONOPAQUE_MCB)OpaqueMcb; + + VBN Vbn = ((ULONG)LargeVbn); + + ASSERTMSG("LargeInteger not supported yet ", ((((PLARGE_INTEGER)&LargeVbn)->HighPart == 0) || + ((((PLARGE_INTEGER)&LargeVbn)->HighPart == 0x7FFFFFFF) && + (((ULONG)LargeVbn) == 0xFFFFFFFF)))); + + PAGED_CODE(); + + DebugTrace(+1, Dbg, "FsRtlTruncateLargeMcb, Mcb = %08lx\n", Mcb ); + + ExAcquireFastMutex( Mcb->FastMutex ); + + __try { + + // + // Do a quick test to see if we are truncating the entire Mcb. + // + + if (Vbn == 0) { + + Mcb->PairCount = 0; + + } else if (Mcb->PairCount > 0) { + + // + // Now if the pair count is greater than zero then we will + // call the remove mcb entry routine to actually do the truncation + // for us. + // + + FsRtlRemoveMcbEntryPrivate( Mcb, Vbn, 0xffffffff - Vbn ); + } + + // + // Now see if we can shrink the allocation for the mapping pairs. + // We'll shrink the mapping pair buffer if the new pair count will + // fit within a quarter of the current maximum pair count and the + // current maximum is greater than the initial pair count. + // + + if ((Mcb->PairCount < (Mcb->MaximumPairCount / 4)) && + (Mcb->MaximumPairCount > INITIAL_MAXIMUM_PAIR_COUNT)) { + + ULONG NewMax; + PMAPPING Mapping; + + // + // We need to allocate a new mapping so compute a new maximum pair + // count. We'll allocate double the current pair count, but never + // less than the initial pair count. + // + + NewMax = Mcb->PairCount * 2; + if (NewMax < INITIAL_MAXIMUM_PAIR_COUNT) { NewMax = INITIAL_MAXIMUM_PAIR_COUNT; } + + Mapping =(PMAPPING) ExAllocatePool( Mcb->PoolType, sizeof(MAPPING) * NewMax ); + + // + // Now check if we really got a new buffer + // + + if (Mapping != NULL) { + + // + // Now copy over the old mapping to the new buffer + // + + RtlCopyMemory( Mapping, Mcb->Mapping, sizeof(MAPPING) * Mcb->PairCount ); + + // + // Deallocate the old buffer + // + + ExFreePool( Mcb->Mapping ); + + // + // And set up the new buffer in the Mcb + // + + Mcb->Mapping = Mapping; + Mcb->MaximumPairCount = NewMax; + } + } + + } __finally { + + ExReleaseFastMutex( Mcb->FastMutex ); + } + + // + // And return to our caller + // + + DebugTrace(-1, Dbg, "FsRtlTruncateLargeMcb -> VOID\n", 0 ); + + return; +} + + +BOOLEAN +FsRtlAddLargeMcbEntry ( + IN PLARGE_MCB OpaqueMcb, + IN LONGLONG LargeVbn, + IN LONGLONG LargeLbn, + IN LONGLONG LargeSectorCount + ) + +/*++ + +Routine Description: + + This routine is used to add a new mapping of VBNs to LBNs to an existing + Mcb. The information added will map + + Vbn to Lbn, + + Vbn+1 to Lbn+1,... + + Vbn+(SectorCount-1) to Lbn+(SectorCount-1). + + The mapping for the VBNs must not already exist in the Mcb. If the + mapping continues a previous run, then this routine will actually coalesce + them into 1 run. + + If pool is not available to store the information this routine will raise a + status value indicating insufficient resources. + + An input Lbn value of zero is illegal (i.e., the Mcb structure will never + map a Vbn to a zero Lbn value). + +Arguments: + + OpaqueMcb - Supplies the Mcb in which to add the new mapping. + + Vbn - Supplies the starting Vbn of the new mapping run to add to the Mcb. + + Lbn - Supplies the starting Lbn of the new mapping run to add to the Mcb. + + SectorCount - Supplies the size of the new mapping run (in sectors). + +Return Value: + + BOOLEAN - TRUE if the mapping was added successfully (i.e., the new + Vbns did not collide with existing Vbns), and FALSE otherwise. If + FALSE is returned then the Mcb is not changed. + +--*/ + +{ + PNONOPAQUE_MCB Mcb = (PNONOPAQUE_MCB)OpaqueMcb; + + VBN Vbn = ((ULONG)LargeVbn); + LBN64 Lbn = (LargeLbn); + ULONG SectorCount = ((ULONG)LargeSectorCount); + + ULONG Index; + + VBN LastVbn; + + BOOLEAN Result; + + ASSERTMSG("LargeInteger not supported yet ", ((PLARGE_INTEGER)&LargeVbn)->HighPart == 0); + ASSERTMSG("LargeInteger not supported yet ", ((PLARGE_INTEGER)&LargeSectorCount)->HighPart == 0); + + PAGED_CODE(); + + DebugTrace(+1, Dbg, "FsRtlAddLargeMcbEntry, Mcb = %08lx\n", Mcb ); + DebugTrace( 0, Dbg, " Vbn = %08lx\n", Vbn ); + DebugTrace( 0, Dbg, " Lbn = %I64x\n", Lbn ); + DebugTrace( 0, Dbg, " SectorCount = %08lx\n", SectorCount ); + + ExAcquireFastMutex( Mcb->FastMutex ); + + __try { + + if (FsRtlFindLargeIndex(Mcb, Vbn, &Index)) { + + ULONG EndVbn = Vbn + SectorCount - 1; + ULONG EndIndex; + + // + // First check the case where we are adding to an existing mcb run + // and if so then we will modify the insertion to complete the run + // + // --ExistingRun--| ==becomes==> --ExistingRun--| + // |--NewRun--| |---| + // + // --ExistingRun----| ==becomes==> a noop + // |--NewRun--| + // + + if (StartingLbn(Mcb, Index) != UNUSED_LBN) { + + // + // Assert that the Lbn's line up between the new and existing run + // + + ASSERT(Lbn == (StartingLbn(Mcb, Index) + (Vbn - StartingVbn(Mcb, Index)))); + + // + // Check if the new run is contained in the existing run + // + + if (EndVbn <= EndingVbn(Mcb, Index)) { + + // + // Do nothing because the run is contained within the existing run + // + + try_return(Result = TRUE); + } + + // + // Otherwise we will simply trim off the request for the new run + // to not overlap with the existing run + // + + Vbn = NextStartingVbn(Mcb, Index); + Lbn = EndingLbn(Mcb, Index) + 1; + + ASSERT(EndVbn >= Vbn); + + SectorCount = EndVbn - Vbn + 1; + + // + // At this point the new run start in a hole, now check that if + // crosses into a non hole and if so then adjust new run to fit + // in the hole + // + // + // |--ExistingRun-- ==becomes==> |--ExistingRun-- + // |--NewRun--| |--New| + // + + } else if (FsRtlFindLargeIndex(Mcb, EndVbn, &EndIndex) && (Index == (EndIndex-1))) { + + // + // Assert that the Lbn's line up in the overlap + // + + ASSERT( StartingLbn(Mcb, EndIndex) == Lbn + (StartingVbn(Mcb, EndIndex) - Vbn) ); + + // + // Truncate the sector count to go up to but not include + // the existing run + // + + SectorCount = StartingVbn(Mcb, EndIndex) - Vbn; + } + } + + // + // Find the index for the starting Vbn of our new run, if there isn't + // a hole found then index will be set to paircount. + // + + if (((Index = Mcb->PairCount) == 0) || + (PreviousEndingVbn(Mcb,Index)+1 <= Vbn) || + !FsRtlFindLargeIndex(Mcb, Vbn, &Index)) { + + // + // We didn't find a mapping, therefore this new mapping must + // go on at the end of the current mapping. + // + // See if we can just grow the last mapping in the current mcb. + // We can grow the last entry if (1) the Vbns follow on, and (2) + // the Lbns follow on. We can only grow the last mapping if the + // index is not 0. + // + + if ((Index != 0) && + (PreviousEndingVbn(Mcb,Index) + 1 == Vbn) && + (PreviousEndingLbn(Mcb,Index) + 1 == Lbn)) { + + // + // --LastRun--|---NewRun--| + // + + // + // Extend the last run in the mcb + // + + DebugTrace( 0, Dbg, "Continuing last run\n", 0); + + (Mcb->Mapping)[Mcb->PairCount-1].NextVbn += SectorCount; + + try_return (Result = TRUE); + } + + // + // We couldn't grow the last mapping, now check to see if + // this is a continuation of the last Vbn (i.e., there isn't + // going to be a hole in the mapping). Or if this is the first + // run in the mapping + // + + if ((Vbn == 0) || + (PreviousEndingVbn(Mcb,Index) + 1 == Vbn)) { + + // + // --LastRun--||---NewRun--| + // + // 0:|--NewRun--| + // + + // + // We only need to add one more run to the mcb, so make sure + // there is enough room for one. + // + + DebugTrace( 0, Dbg, "Adding new contiguous last run\n", 0); + + FsRtlAddLargeEntry( Mcb, Index, 1 ); + + // + // Add the new mapping + // + + (Mcb->Mapping)[Index].Lbn = Lbn; + (Mcb->Mapping)[Index].NextVbn = Vbn + SectorCount; + + try_return (Result = TRUE); + } + + // + // If we reach this point then there is going to be a hole in the + // mapping. and the mapping gets appended to the end of the current + // allocation. So need to make room for two more runs in the mcb. + // + + // + // --LastRun--| hole |---NewRun--| + // + // 0: hole |--NewRun--| + // + + DebugTrace( 0, Dbg, "Adding new noncontiguous last run\n", 0); + + FsRtlAddLargeEntry( Mcb, Index, 2 ); + + // + // Add the hole + // + + (Mcb->Mapping)[Index].Lbn = UNUSED_LBN; + (Mcb->Mapping)[Index].NextVbn = Vbn; + + // + // Add the new mapping + // + + (Mcb->Mapping)[Index+1].Lbn = Lbn; + (Mcb->Mapping)[Index+1].NextVbn = Vbn + SectorCount; + + try_return (Result = TRUE); + } + + // + // We found an index for the Vbn therefore we must be trying + // to fill up a hole in the mcb. So first we need to check to make + // sure there really is a hole to be filled + // + + LastVbn = Vbn + SectorCount - 1; + + if ((StartingLbn(Mcb,Index) == UNUSED_LBN) && + (StartingVbn(Mcb,Index) <= Vbn) && (LastVbn <= EndingVbn(Mcb,Index))) { + + // + // The mapping fits in this hole, but now here are the following + // cases we must consider for the new mapping + // + + if ((StartingVbn(Mcb,Index) < Vbn) && (LastVbn < EndingVbn(Mcb,Index))) { + + // Leaves a hole are both ends + // + // --PreviousRun--| hole |--NewRun--| hole |--FollowingRun-- + // + // 0: hole |--NewRun--| hole |--FollowingRun-- + // + + DebugTrace( 0, Dbg, "Hole at both ends\n", 0); + + // + // Make room for two more entries. The NextVbn field of the + // one we're shifting remains valid. + // + + FsRtlAddLargeEntry( Mcb, Index, 2 ); + + // + // Add the first hole + // + + (Mcb->Mapping)[Index].Lbn = UNUSED_LBN; + (Mcb->Mapping)[Index].NextVbn = Vbn; + + // + // Add the new mapping + // + + (Mcb->Mapping)[Index+1].Lbn = Lbn; + (Mcb->Mapping)[Index+1].NextVbn = Vbn + SectorCount; + + // + // The second hole is already set up by the add entry call, because + // that call just shift over the original hole to that slot + // + + try_return (Result = TRUE); + } + + if ((StartingVbn(Mcb,Index) == Vbn) && (LastVbn < EndingVbn(Mcb,Index))) { + + if (PreviousEndingLbn(Mcb,Index) + 1 == Lbn) { + + // + // Leaves a hole at the rear, and continues the earlier run + // + // --PreviousRun--|--NewRun--| hole |--FollowingRun-- + // + + DebugTrace( 0, Dbg, "Hole at rear and continue\n", 0); + + // + // We just need to extend the previous run + // + + (Mcb->Mapping)[Index-1].NextVbn += SectorCount; + + try_return (Result = TRUE); + + } else { + + // + // Leaves a hole at the rear, and does not continue the + // earlier run. As occurs if index is zero. + // + // --PreviousRun--||--NewRun--| hole |--FollowingRun-- + // + // 0:|--NewRun--| hole |--FollowingRun-- + // + + DebugTrace( 0, Dbg, "Hole at rear and not continue\n", 0); + + // + // Make room for one more entry. The NextVbn field of the + // one we're shifting remains valid. + // + + FsRtlAddLargeEntry( Mcb, Index, 1 ); + + // + // Add the new mapping + // + + (Mcb->Mapping)[Index].Lbn = Lbn; + (Mcb->Mapping)[Index].NextVbn = Vbn + SectorCount; + + // + // The hole is already set up by the add entry call, because + // that call just shift over the original hole to that slot + // + + try_return (Result = TRUE); + } + } + + if ((StartingVbn(Mcb,Index) < Vbn) && (LastVbn == EndingVbn(Mcb,Index))) { + + if (NextStartingLbn(Mcb,Index) == Lbn + SectorCount) { + + // + // Leaves a hole at the front, and continues the following run + // + // --PreviousRun--| hole |--NewRun--|--FollowingRun-- + // + // 0: hole |--NewRun--|--FollowingRun-- + // + + DebugTrace( 0, Dbg, "Hole at front and continue\n", 0); + + // + // We just need to extend the following run + // + + (Mcb->Mapping)[Index].NextVbn = Vbn; + (Mcb->Mapping)[Index+1].Lbn = Lbn; + + try_return (Result = TRUE); + + } else { + + // + // Leaves a hole at the front, and does not continue the following + // run + // + // --PreviousRun--| hole |--NewRun--||--FollowingRun-- + // + // 0: hole |--NewRun--||--FollowingRun-- + // + + DebugTrace( 0, Dbg, "Hole at front and not continue\n", 0); + + // + // Make room for one more entry. The NextVbn field of the + // one we're shifting remains valid. + // + + FsRtlAddLargeEntry( Mcb, Index, 1 ); + + // + // Add the hole + // + + (Mcb->Mapping)[Index].Lbn = UNUSED_LBN; + (Mcb->Mapping)[Index].NextVbn = Vbn; + + // + // Add the new mapping + // + + (Mcb->Mapping)[Index+1].Lbn = Lbn; + + try_return (Result = TRUE); + } + + } + + if ((PreviousEndingLbn(Mcb,Index) + 1 == Lbn) && + (NextStartingLbn(Mcb,Index) == Lbn + SectorCount)) { + + // + // Leaves no holes, and continues both runs + // + // --PreviousRun--|--NewRun--|--FollowingRun-- + // + + DebugTrace( 0, Dbg, "No holes, and continues both runs\n", 0); + + // + // We need to collapse the current index and the following index + // but first we copy the NextVbn of the follwing run into + // the NextVbn field of the previous run to so it all becomes + // one run + // + + (Mcb->Mapping)[Index-1].NextVbn = (Mcb->Mapping)[Index+1].NextVbn; + + FsRtlRemoveLargeEntry( Mcb, Index, 2 ); + + try_return (Result = TRUE); + } + + if (NextStartingLbn(Mcb,Index) == Lbn + SectorCount) { + + // + // Leaves no holes, and continues only following run + // + // --PreviousRun--||--NewRun--|--FollowingRun-- + // + // 0:|--NewRun--|--FollowingRun-- + // + + DebugTrace( 0, Dbg, "No holes, and continues following\n", 0); + + // + // This index is going away so we need to stretch the + // following run to meet up with the previous run + // + + (Mcb->Mapping)[Index+1].Lbn = Lbn; + + FsRtlRemoveLargeEntry( Mcb, Index, 1 ); + + try_return (Result = TRUE); + } + + if (PreviousEndingLbn(Mcb,Index) + 1 == Lbn) { + + // + // Leaves no holes, and continues only earlier run + // + // --PreviousRun--|--NewRun--||--FollowingRun-- + // + + DebugTrace( 0, Dbg, "No holes, and continues earlier\n", 0); + + // + // This index is going away so we need to stretch the + // previous run to meet up with the following run + // + + (Mcb->Mapping)[Index-1].NextVbn = (Mcb->Mapping)[Index].NextVbn; + + FsRtlRemoveLargeEntry( Mcb, Index, 1 ); + + try_return (Result = TRUE); + } + + // + // Leaves no holes, and continues neither run + // + // --PreviousRun--||--NewRun--||--FollowingRun-- + // + // 0:|--NewRun--||--FollowingRun-- + // + + DebugTrace( 0, Dbg, "No holes, and continues none\n", 0); + + (Mcb->Mapping)[Index].Lbn = Lbn; + + try_return (Result = TRUE); + } + + // + // We tried to overwrite an existing mapping so we'll have to + // tell our caller that it's not possible + // + + Result = FALSE; + + try_exit: NOTHING; + } __finally { + + ExReleaseFastMutex( Mcb->FastMutex ); + + DebugTrace(-1, Dbg, "FsRtlAddLargeMcbEntry -> %08lx\n", Result ); + } + + return Result; +} + + +VOID +FsRtlRemoveLargeMcbEntry ( + IN PLARGE_MCB OpaqueMcb, + IN LONGLONG LargeVbn, + IN LONGLONG LargeSectorCount + ) + +/*++ + +Routine Description: + + This routine removes a mapping of VBNs to LBNs from an Mcb. The mappings + removed are for + + Vbn, + + Vbn+1, to + + Vbn+(SectorCount-1). + + The operation works even if the mapping for a Vbn in the specified range + does not already exist in the Mcb. If the specified range of Vbn includes + the last mapped Vbn in the Mcb then the Mcb mapping shrinks accordingly. + + If pool is not available to store the information this routine will raise + a status value indicating insufficient resources. + +Arguments: + + OpaqueMcb - Supplies the Mcb from which to remove the mapping. + + Vbn - Supplies the starting Vbn of the mappings to remove. + + SectorCount - Supplies the size of the mappings to remove (in sectors). + +Return Value: + + None. + +--*/ + +{ + PNONOPAQUE_MCB Mcb = (PNONOPAQUE_MCB)OpaqueMcb; + + VBN Vbn = ((ULONG)LargeVbn); + ULONG SectorCount = ((ULONG)LargeSectorCount); + + PAGED_CODE(); + + ASSERTMSG("LargeInteger not supported yet ", ((PLARGE_INTEGER)&LargeVbn)->HighPart == 0); + + DebugTrace(+1, Dbg, "FsRtlRemoveLargeMcbEntry, Mcb = %08lx\n", Mcb ); + DebugTrace( 0, Dbg, " Vbn = %08lx\n", Vbn ); + DebugTrace( 0, Dbg, " SectorCount = %08lx\n", SectorCount ); + + ExAcquireFastMutex( Mcb->FastMutex ); + + __try { + + FsRtlRemoveMcbEntryPrivate( Mcb, Vbn, SectorCount ); + + } __finally { + + ExReleaseFastMutex( Mcb->FastMutex ); + + DebugTrace(-1, Dbg, "FsRtlRemoveLargeMcbEntry -> VOID\n", 0 ); + } + + return; +} + + +BOOLEAN +FsRtlLookupLargeMcbEntry ( + IN PLARGE_MCB OpaqueMcb, + IN LONGLONG LargeVbn, + OUT PLONGLONG LargeLbn OPTIONAL, + OUT PLONGLONG LargeSectorCount OPTIONAL, + OUT PLONGLONG LargeStartingLbn OPTIONAL, + OUT PLONGLONG LargeCountFromStartingLbn OPTIONAL, + OUT PULONG Index OPTIONAL + ) + +/*++ + +Routine Description: + + This routine retrieves the mapping of a Vbn to an Lbn from an Mcb. + It indicates if the mapping exists and the size of the run. + +Arguments: + + OpaqueMcb - Supplies the Mcb being examined. + + Vbn - Supplies the Vbn to lookup. + + Lbn - Receives the Lbn corresponding to the Vbn. A value of -1 is + returned if the Vbn does not have a corresponding Lbn. + + SectorCount - Receives the number of sectors that map from the Vbn to + contiguous Lbn values beginning with the input Vbn. + + Index - Receives the index of the run found. + +Return Value: + + BOOLEAN - TRUE if the Vbn is within the range of VBNs mapped by the + MCB (even if it corresponds to a hole in the mapping), and FALSE + if the Vbn is beyond the range of the MCB's mapping. + + For example, if an MCB has a mapping for VBNs 5 and 7 but not for + 6, then a lookup on Vbn 5 or 7 will yield a non zero Lbn and a sector + count of 1. A lookup for Vbn 6 will return TRUE with an Lbn value of + 0, and lookup for Vbn 8 or above will return FALSE. + +--*/ + +{ + PNONOPAQUE_MCB Mcb = (PNONOPAQUE_MCB)OpaqueMcb; + + BOOLEAN Result; + + ULONG LocalIndex; + + ASSERTMSG("LargeInteger not supported yet ", ((((PLARGE_INTEGER)&LargeVbn)->HighPart == 0) || + ((((PLARGE_INTEGER)&LargeVbn)->HighPart == 0x7FFFFFFF) && + (((ULONG)LargeVbn) == 0xFFFFFFFF)))); + + DebugTrace(+1, Dbg, "FsRtlLookupLargeMcbEntry, Mcb = %08lx\n", Mcb ); + DebugTrace( 0, Dbg, " LargeVbn.LowPart = %08lx\n", LargeVbn.LowPart ); + + ExAcquireFastMutex( Mcb->FastMutex ); + + __try { + + if (!FsRtlFindLargeIndex(Mcb, ((ULONG)LargeVbn), &LocalIndex)) { + + try_return (Result = FALSE); + } + + // + // Compute the lbn for corresponding to the vbn, the value is the + // starting lbn of the run plus the number of sectors offset into the + // run. But if it's a hole then the sector Lbn is zero. + // + + if (ARGUMENT_PRESENT(LargeLbn)) { + + if (StartingLbn(Mcb,LocalIndex) == UNUSED_LBN) { + + *(LargeLbn) = UNUSED_LBN; + + } else { + + *(LargeLbn) = StartingLbn(Mcb,LocalIndex) + (((ULONG)LargeVbn) - StartingVbn(Mcb,LocalIndex)); + } + } + + // + // If there sector count argument is present then we'll return the number + // of sectors remaing in the run. + // + + if (ARGUMENT_PRESENT(LargeSectorCount)) { + + *((PULONG)LargeSectorCount) = EndingVbn(Mcb,LocalIndex) - ((ULONG)LargeVbn) + 1; + } + + // + // Compute the starting lbn for corresponding to the start of the run, the value is the + // starting lbn of the run. But if it's a hole then the sector Lbn is zero. + // + + if (ARGUMENT_PRESENT(LargeStartingLbn)) { + + if (StartingLbn(Mcb,LocalIndex) == UNUSED_LBN) { + + *(LargeStartingLbn) = UNUSED_LBN; + + } else { + + *(LargeStartingLbn) = StartingLbn(Mcb,LocalIndex); + } + } + + // + // If there sector count argument is present then we'll return the number + // of sectors in the run. + // + + if (ARGUMENT_PRESENT(LargeCountFromStartingLbn)) { + + *((PULONG)LargeCountFromStartingLbn) = EndingVbn(Mcb,LocalIndex) - StartingVbn(Mcb,LocalIndex) + 1; + } + + // + // If the caller want to know the Index number, fill it in. + // + + if (ARGUMENT_PRESENT(Index)) { + + *Index = LocalIndex; + } + + Result = TRUE; + + try_exit: NOTHING; + } __finally { + + ExReleaseFastMutex( Mcb->FastMutex ); + + DebugTrace(-1, Dbg, "FsRtlLookupLargeMcbEntry -> %08lx\n", Result ); + } + + if (ARGUMENT_PRESENT(LargeSectorCount)) { + ((PLARGE_INTEGER)LargeSectorCount)->HighPart = 0; + } + + if (ARGUMENT_PRESENT(LargeCountFromStartingLbn)) { + ((PLARGE_INTEGER)LargeCountFromStartingLbn)->HighPart = 0; + } + + return Result; +} + + +BOOLEAN +FsRtlLookupLastLargeMcbEntry ( + IN PLARGE_MCB OpaqueMcb, + OUT PLONGLONG LargeVbn, + OUT PLONGLONG LargeLbn + ) + +/*++ + +Routine Description: + + This routine retrieves the last Vbn to Lbn mapping stored in the Mcb. + It returns the mapping for the last sector or the last run in the + Mcb. The results of this function is useful when extending an existing + file and needing to a hint on where to try and allocate sectors on the + disk. + +Arguments: + + OpaqueMcb - Supplies the Mcb being examined. + + Vbn - Receives the last Vbn value mapped. + + Lbn - Receives the Lbn corresponding to the Vbn. + +Return Value: + + BOOLEAN - TRUE if there is a mapping within the Mcb and FALSE otherwise + (i.e., the Mcb does not contain any mapping). + +--*/ + +{ + PNONOPAQUE_MCB Mcb = (PNONOPAQUE_MCB)OpaqueMcb; + + BOOLEAN Result; + + PAGED_CODE(); + + DebugTrace(+1, Dbg, "FsRtlLookupLastLargeMcbEntry, Mcb = %08lx\n", Mcb ); + + ExAcquireFastMutex( Mcb->FastMutex ); + + __try { + + // + // Check to make sure there is at least one run in the mcb + // + + if (Mcb->PairCount <= 0) { + + try_return (Result = FALSE); + } + + // + // Return the last mapping of the last run + // + + *(LargeLbn) = EndingLbn(Mcb,Mcb->PairCount-1); + *((PULONG)LargeVbn) = EndingVbn(Mcb,Mcb->PairCount-1); + + Result = TRUE; + + try_exit: NOTHING; + } __finally { + + ExReleaseFastMutex( Mcb->FastMutex ); + + DebugTrace(-1, Dbg, "FsRtlLookupLastLargeMcbEntry -> %08lx\n", Result ); + } + + ((PLARGE_INTEGER)LargeVbn)->HighPart = (*((PULONG)LargeVbn) == 0xffffffff ? 0xffffffff : 0); + + return Result; +} + + +ULONG +FsRtlNumberOfRunsInLargeMcb ( + IN PLARGE_MCB OpaqueMcb + ) + +/*++ + +Routine Description: + + This routine returns to the its caller the number of distinct runs + mapped by an Mcb. Holes (i.e., Vbns that map to Lbn=UNUSED_LBN) are counted + as runs. For example, an Mcb containing a mapping for only Vbns 0 and 3 + will have 3 runs, one for the first mapped sector, a second for the + hole covering Vbns 1 and 2, and a third for Vbn 3. + +Arguments: + + OpaqueMcb - Supplies the Mcb being examined. + +Return Value: + + ULONG - Returns the number of distinct runs mapped by the input Mcb. + +--*/ + +{ + PNONOPAQUE_MCB Mcb = (PNONOPAQUE_MCB)OpaqueMcb; + + ULONG Count; + + PAGED_CODE(); + + DebugTrace(+1, Dbg, "FsRtlNumberOfRunsInLargeMcb, Mcb = %08lx\n", Mcb ); + + ExAcquireFastMutex( Mcb->FastMutex ); + + Count = Mcb->PairCount; + + ExReleaseFastMutex( Mcb->FastMutex ); + + DebugTrace(-1, Dbg, "FsRtlNumberOfRunsInLargeMcb -> %08lx\n", Count ); + + return Count; +} + + +BOOLEAN +FsRtlGetNextLargeMcbEntry ( + IN PLARGE_MCB OpaqueMcb, + IN ULONG RunIndex, + OUT PLONGLONG LargeVbn, + OUT PLONGLONG LargeLbn, + OUT PLONGLONG LargeSectorCount + ) + +/*++ + +Routine Description: + + This routine returns to its caller the Vbn, Lbn, and SectorCount for + distinct runs mapped by an Mcb. Holes are counted as runs. For example, + to construct to print out all of the runs in a a file is: + +//. . for (i = 0; FsRtlGetNextLargeMcbEntry(Mcb,i,&Vbn,&Lbn,&Count); i++) { +// +//. . // print out vbn, lbn, and count +// +//. . } + +Arguments: + + OpaqueMcb - Supplies the Mcb being examined. + + RunIndex - Supplies the index of the run (zero based) to return to the + caller. + + Vbn - Receives the starting Vbn of the returned run, or zero if the + run does not exist. + + Lbn - Recieves the starting Lbn of the returned run, or zero if the + run does not exist. + + SectorCount - Receives the number of sectors within the returned run, + or zero if the run does not exist. + +Return Value: + + BOOLEAN - TRUE if the specified run (i.e., RunIndex) exists in the Mcb, + and FALSE otherwise. If FALSE is returned then the Vbn, Lbn, and + SectorCount parameters receive zero. + +--*/ + +{ + PNONOPAQUE_MCB Mcb = (PNONOPAQUE_MCB)OpaqueMcb; + + BOOLEAN Result; + + PAGED_CODE(); + + DebugTrace(+1, Dbg, "FsRtlGetNextLargeMcbEntry, Mcb = %08lx\n", Mcb ); + DebugTrace( 0, Dbg, " RunIndex = %08lx\n", RunIndex ); + + ExAcquireFastMutex( Mcb->FastMutex ); + + __try { + + // + // Make sure the run index is within range + // + + if (RunIndex >= Mcb->PairCount) { + + try_return (Result = FALSE); + } + + // + // Set the return variables + // + + *((PULONG)LargeVbn) = StartingVbn(Mcb,RunIndex); + *(LargeLbn) = StartingLbn(Mcb,RunIndex); + *((PULONG)LargeSectorCount) = SectorsWithinRun(Mcb,RunIndex); + + Result = TRUE; + + try_exit: NOTHING; + } __finally { + + ExReleaseFastMutex( Mcb->FastMutex ); + + DebugTrace(-1, Dbg, "FsRtlGetNextLargeMcbEntry -> %08lx\n", Result ); + } + + ((PLARGE_INTEGER)LargeVbn)->HighPart = (*((PULONG)LargeVbn) == 0xffffffff ? 0xffffffff : 0); + ((PLARGE_INTEGER)LargeSectorCount)->HighPart = 0; + + return Result; +} + + +BOOLEAN +FsRtlSplitLargeMcb ( + IN PLARGE_MCB OpaqueMcb, + IN LONGLONG LargeVbn, + IN LONGLONG LargeAmount + ) + +/*++ + +Routine Description: + + This routine is used to create a hole within an MCB, by shifting the + mapping of Vbns. All mappings above the input vbn are shifted by the + amount specified and while keeping their current lbn value. Pictorially + we have as input the following MCB + + VBN : LargeVbn-1 LargeVbn N + +-----------------+------------------+ + LBN : X Y + + And after the split we have + + VBN : LargeVbn-1 LargeVbn+Amount N+Amount + +-----------------+.............+---------------------------+ + LBN : X UnusedLbn Y + + When doing the split we have a few cases to consider. They are: + + 1. The input Vbn is beyond the last run. In this case this operation + is a noop. + + 2. The input Vbn is within or adjacent to a existing run of unused Lbns. + In this case we simply need to extend the size of the existing hole + and shift succeeding runs. + + 3. The input Vbn is between two existing runs, including the an input vbn + value of zero. In this case we need to add a new entry for the hole + and shift succeeding runs. + + 4. The input Vbn is within an existing run. In this case we need to add + two new entries to contain the split run and the hole. + + If pool is not available to store the information this routine will raise a + status value indicating insufficient resources. + +Arguments: + + OpaqueMcb - Supplies the Mcb in which to add the new mapping. + + Vbn - Supplies the starting Vbn that is to be shifted. + + Amount - Supplies the amount to shift by. + +Return Value: + + BOOLEAN - TRUE if the mapping was successfully shifted, and FALSE otherwise. + If FALSE is returned then the Mcb is not changed. + +--*/ + +{ + PNONOPAQUE_MCB Mcb = (PNONOPAQUE_MCB)OpaqueMcb; + + VBN Vbn = ((ULONG)LargeVbn); + ULONG Amount = ((ULONG)LargeAmount); + + ULONG Index; + + BOOLEAN Result; + + ULONG i; + + ASSERTMSG("LargeInteger not supported yet ", ((PLARGE_INTEGER)&LargeVbn)->HighPart == 0); + ASSERTMSG("LargeInteger not supported yet ", ((PLARGE_INTEGER)&LargeAmount)->HighPart == 0); + + PAGED_CODE(); + + DebugTrace(+1, Dbg, "FsRtlSplitLargeMcb, Mcb = %08lx\n", Mcb ); + DebugTrace( 0, Dbg, " Vbn = %08lx\n", Vbn ); + DebugTrace( 0, Dbg, " Amount = %08lx\n", Amount ); + + ExAcquireFastMutex( Mcb->FastMutex ); + + __try { + + // + // First lookup the index for the entry that we are going to split. + // If we can't find the entry then there is nothing to split. This + // takes care of the case where the input vbn is beyond the last run + // in the mcb + // + + if (!FsRtlFindLargeIndex( Mcb, Vbn, &Index)) { + + try_return(Result = FALSE); + } + + // + // Now check if the input Vbn is within a hole + // + + if (StartingLbn(Mcb,Index) == UNUSED_LBN) { + + // + // Before: --PreviousRun--||--IndexHole--||--FollowingRun-- + // After: --PreviousRun--||----IndexHole----||--FollowingRun-- + // + // In this case the vbn is somewhere within the hole and we + // simply need to added the amount of each existing run + // beyond the hole. + // + + // + // In this case there is really nothing to do here because the + // ending code will already shift the runs by proper amount + // starting at index + // + + NOTHING; + + // + // Now check if the input vbn is between a hole and an existing run. + // + + } else if ((StartingVbn(Mcb,Index) == Vbn) && (Index != 0) && (PreviousEndingLbn(Mcb,Index) == UNUSED_LBN)) { + + // + // Before: --Hole--||--IndexRun-- + // After: --Hole------||--IndexRun-- + // + // In this case the vbn points to the start of the existing + // run and we need to do the split between the hole and the + // existing run by simply adding the amount to each existing + // run beyond the hole. + // + + // + // In this case we need to decement the index by 1 and then + // fall to the bottom code which will do the shifting for us + // + + Index -= 1; + + // + // Now check if the input vbn is between two existing runs + // + + } else if (StartingVbn(Mcb,Index) == Vbn) { + + // + // Before: --PreviousRun--||--IndexRun-- + // After: --PreviousRun--||--NewHole--||--IndexRun-- + // + // Before: 0:|--IndexRun-- + // After: 0:|--NewHole--||--IndexRun-- + // + // In this case the vbn points to the start of an existing + // run and the preceeding is either a real run or the start + // of mapping pairs We simply add a new entry for the hole + // and shift succeeding runs. + // + + FsRtlAddLargeEntry( Mcb, Index, 1 ); + + (Mcb->Mapping)[Index].Lbn = UNUSED_LBN; + (Mcb->Mapping)[Index].NextVbn = Vbn + Amount; + + Index += 1; + + // + // Otherwise the input vbn is inside an existing run + // + + } else { + + // + // Before: --IndexRun-- + // After: --SplitRun--||--NewHole--||--SplitRun-- + // + // In this case the vbn points within an existing run + // we need to add two new extries for hole and split + // run and shift succeeding runs + // + + FsRtlAddLargeEntry( Mcb, Index, 2 ); + + (Mcb->Mapping)[Index].Lbn = (Mcb->Mapping)[Index+2].Lbn; + (Mcb->Mapping)[Index].NextVbn = Vbn; + + (Mcb->Mapping)[Index+1].Lbn = UNUSED_LBN; + (Mcb->Mapping)[Index+1].NextVbn = Vbn + Amount; + + (Mcb->Mapping)[Index+2].Lbn = (Mcb->Mapping)[Index+2].Lbn + + StartingVbn(Mcb, Index+1) - + StartingVbn(Mcb, Index); + + Index += 2; + + } + + // + // At this point we have completed most of the work we now need to + // shift existing runs from the index to the end of the mappings + // by the specified amount + // + + for (i = Index; i < Mcb->PairCount; i += 1) { + + (Mcb->Mapping)[i].NextVbn += Amount; + } + + Result = TRUE; + + try_exit: NOTHING; + } __finally { + + ExReleaseFastMutex( Mcb->FastMutex ); + + DebugTrace(-1, Dbg, "FsRtlSplitLargeMcb -> %08lx\n", Result ); + } + + return Result; +} + + +// +// Private support routine +// + +VOID +FsRtlRemoveMcbEntryPrivate ( + IN PNONOPAQUE_MCB Mcb, + IN ULONG Vbn, + IN ULONG SectorCount + ) + +/*++ + +Routine Description: + + This is the work routine for remove large mcb entry. It does the work + without taking out the mcb FastMutex. + +Arguments: + + Mcb - Supplies the Mcb from which to remove the mapping. + + Vbn - Supplies the starting Vbn of the mappings to remove. + + SectorCount - Supplies the size of the mappings to remove (in sectors). + +Return Value: + + None. + +--*/ + +{ + ULONG Index; + + PAGED_CODE(); + + // + // Do a quick test to see if we are wiping out the entire MCB. + // + + if ((Vbn == 0) && (Mcb->PairCount > 0) && (SectorCount >= Mcb->Mapping[Mcb->PairCount-1].NextVbn)) { + + Mcb->PairCount = 0; + + return; + } + + // + // While there is some more mapping to remove we'll continue + // with our main loop + // + + while (SectorCount > 0) { + + // + // Locate the mapping for the vbn + // + + if (!FsRtlFindLargeIndex(Mcb, Vbn, &Index)) { + + DebugTrace( 0, Dbg, "FsRtlRemoveLargeMcbEntry, Cannot remove an unmapped Vbn = %08lx\n", Vbn ); + + return; + } + + // + // Now that we some something to remove the following cases must + // be considered + // + + if ((StartingVbn(Mcb,Index) == Vbn) && + (EndingVbn(Mcb,Index) < Vbn + SectorCount)) { + + ULONG i; + + // + // Removes the entire run + // + + // + // Update the amount to remove + // + + i = SectorsWithinRun(Mcb,Index); + Vbn += i; + SectorCount -= i; + + // + // If already a hole then leave it alone + // + + if (StartingLbn(Mcb,Index) == UNUSED_LBN) { + + NOTHING; + + // + // Test for last run + // + + } else if (Index == Mcb->PairCount - 1) { + + if ((PreviousEndingLbn(Mcb,Index) != UNUSED_LBN) || + (Index == 0)) { + + // + // Previous is not hole, index is last run + // + // --Previous--| Hole + // + // 0: Hole + // + + DebugTrace( 0, Dbg, "Entire run, Previous not hole, index is last run\n", 0); + + // + // Just remove this entry + // + + FsRtlRemoveLargeEntry( Mcb, Index, 1); + + } else { + + // + // Previous is hole, index is last run + // + // --Hole--| Hole + // + + DebugTrace( 0, Dbg, "Entire run, Previous hole, index is last run\n", 0); + + // + // Just remove this entry, and preceding entry + // + + FsRtlRemoveLargeEntry( Mcb, Index-1, 2); + } + + } else if (((PreviousEndingLbn(Mcb,Index) != UNUSED_LBN) || (Index == 0)) && + (NextStartingLbn(Mcb,Index) != UNUSED_LBN)) { + + // + // Previous and following are not holes + // + // --Previous--| Hole |--Following-- + // + // 0: Hole |--Following-- + // + + DebugTrace( 0, Dbg, "Entire run, Previous & Following not holes\n", 0); + + // + // Make this index a hole + // + + (Mcb->Mapping)[Index].Lbn = UNUSED_LBN; + + } else if (((PreviousEndingLbn(Mcb,Index) != UNUSED_LBN) || (Index == 0)) && + (NextStartingLbn(Mcb,Index) == UNUSED_LBN)) { + + // + // Following is hole + // + // --Previous--| Hole |--Hole-- + // + // 0: Hole |--Hole-- + // + + DebugTrace( 0, Dbg, "Entire run, Following is hole\n", 0); + + // + // Simply remove this entry + // + + FsRtlRemoveLargeEntry( Mcb, Index, 1 ); + + } else if ((PreviousEndingLbn(Mcb,Index) == UNUSED_LBN) && + (NextStartingLbn(Mcb,Index) != UNUSED_LBN)) { + + // + // Previous is hole + // + // --Hole--| Hole |--Following-- + // + + DebugTrace( 0, Dbg, "Entire run, Previous is hole\n", 0); + + // + // Mark current entry a hole + // + + (Mcb->Mapping)[Index].Lbn = UNUSED_LBN; + + // + // Remove previous entry + // + + FsRtlRemoveLargeEntry( Mcb, Index - 1, 1 ); + + } else { + + // + // Previous and following are holes + // + // --Hole--| Hole |--Hole-- + // + + DebugTrace( 0, Dbg, "Entire run, Previous & following are holes\n", 0); + + // + // Remove previous and this entry + // + + FsRtlRemoveLargeEntry( Mcb, Index - 1, 2 ); + } + + } else if (StartingVbn(Mcb,Index) == Vbn) { + + // + // Removes first part of run + // + + // + // If already a hole then leave it alone + // + + if (StartingLbn(Mcb,Index) == UNUSED_LBN) { + + NOTHING; + + } else if ((PreviousEndingLbn(Mcb,Index) != UNUSED_LBN) || (Index == 0)) { + + // + // Previous is not hole + // + // --Previous--| Hole |--Index--||--Following-- + // + // 0: Hole |--Index--||--Following-- + // + + DebugTrace( 0, Dbg, "1st part, Previous is not hole\n", 0); + + // + // Make room for one more entry. The NextVbn field of the + // one we're shifting remains valid. + // + + FsRtlAddLargeEntry( Mcb, Index, 1 ); + + // + // Set the hole + // + + (Mcb->Mapping)[Index].Lbn = UNUSED_LBN; + (Mcb->Mapping)[Index].NextVbn = Vbn + SectorCount; + + // + // Set the new Lbn for the remaining run + // + + (Mcb->Mapping)[Index+1].Lbn += SectorCount; + + } else { + + // + // Previous is hole + // + // --Hole--| Hole |--Index--||--Following-- + // + + DebugTrace( 0, Dbg, "1st part, Previous is hole\n", 0); + + // + // Expand the preceding hole + // + + (Mcb->Mapping)[Index-1].NextVbn += SectorCount; + + // + // Set the new Lbn for the remaining run + // + + (Mcb->Mapping)[Index].Lbn += SectorCount; + } + + // + // Update the amount to remove + // + + Vbn += SectorCount; + SectorCount = 0; + + } else if (EndingVbn(Mcb,Index) < Vbn + SectorCount) { + + ULONG AmountToRemove; + + AmountToRemove = EndingVbn(Mcb,Index) - Vbn + 1; + + // + // Removes last part of run + // + + // + // If already a hole then leave it alone + // + + if (StartingLbn(Mcb,Index) == UNUSED_LBN) { + + NOTHING; + + } else if (Index == Mcb->PairCount - 1) { + + // + // Index is last run + // + // --Previous--||--Index--| Hole + // + // 0:|--Index--| Hole + // + + DebugTrace( 0, Dbg, "last part, Index is last run\n", 0); + + // + // Shrink back the size of the current index + // + + (Mcb->Mapping)[Index].NextVbn -= AmountToRemove; + + } else if (NextStartingLbn(Mcb,Index) == UNUSED_LBN) { + + // + // Following is hole + // + // --Previous--||--Index--| Hole |--Hole-- + // + // 0:|--Index--| Hole |--Hole-- + // + + DebugTrace( 0, Dbg, "last part, Following is hole\n", 0); + + // + // Shrink back the size of the current index + // + + (Mcb->Mapping)[Index].NextVbn -= AmountToRemove; + + } else { + + // + // Following is not hole + // + // --Previous--||--Index--| Hole |--Following-- + // + // + // 0:|--Index--| Hole |--Following-- + // + + DebugTrace( 0, Dbg, "last part, Following is not hole\n", 0); + + // + // Make room for one more entry. The NextVbn field of the + // one we're shifting remains valid. + // + + FsRtlAddLargeEntry( Mcb, Index+1, 1 ); + + // + // Set the new hole + // + + (Mcb->Mapping)[Index+1].Lbn = UNUSED_LBN; + (Mcb->Mapping)[Index+1].NextVbn = (Mcb->Mapping)[Index].NextVbn; + + // + // Shrink back the size of the current index + // + + (Mcb->Mapping)[Index].NextVbn -= AmountToRemove; + } + + // + // Update amount to remove + // + + Vbn += AmountToRemove; + SectorCount -= AmountToRemove; + + } else { + + // + // If already a hole then leave it alone + // + + if (StartingLbn(Mcb,Index) == UNUSED_LBN) { + + NOTHING; + + } else { + + // + // Remove middle of run + // + // --Previous--||--Index--| Hole |--Index--||--Following-- + // + // 0:|--Index--| Hole |--Index--||--Following-- + // + + DebugTrace( 0, Dbg, "Middle of run\n", 0); + + // + // Make room for two more entries. The NextVbn field of the + // one we're shifting remains valid. + // + + FsRtlAddLargeEntry( Mcb, Index, 2 ); + + // + // Set up the first remaining run + // + + (Mcb->Mapping)[Index].Lbn = (Mcb->Mapping)[Index+2].Lbn; + (Mcb->Mapping)[Index].NextVbn = Vbn; + + // + // Set up the hole + // + + (Mcb->Mapping)[Index+1].Lbn = UNUSED_LBN; + (Mcb->Mapping)[Index+1].NextVbn = Vbn + SectorCount; + + // + // Set up the second remaining run + // + + (Mcb->Mapping)[Index+2].Lbn += SectorsWithinRun(Mcb,Index) + + SectorsWithinRun(Mcb,Index+1); + } + + // + // Update amount to remove + // + + Vbn += SectorCount; + SectorCount = 0; + } + } + + return; +} + + +// +// Private routine +// + +BOOLEAN +FsRtlFindLargeIndex ( + IN PNONOPAQUE_MCB Mcb, + IN VBN Vbn, + OUT PULONG Index + ) + +/*++ + +Routine Description: + + This is a private routine that locates a mapping for a Vbn + in a given mapping array + +Arguments: + + Mcb - Supplies the mapping array to examine + + Vbn - Supplies the Vbn to look up + + Index - Receives the index within the mapping array of the mapping + containing the Vbn. If none if found then the index is set to + PairCount. + +Return Value: + + BOOLEAN - TRUE if Vbn is found and FALSE otherwise + +--*/ + +{ + LONG MinIndex; + LONG MaxIndex; + LONG MidIndex; + + // + // We'll just do a binary search for the mapping entry. Min and max + // are our search boundaries + // + + MinIndex = 0; + MaxIndex = Mcb->PairCount - 1; + + while (MinIndex <= MaxIndex) { + + // + // Compute the middle index to look at + // + + MidIndex = ((MaxIndex + MinIndex) / 2); + + // + // check if the Vbn is less than the mapping at the mid index + // + + if (Vbn < StartingVbn(Mcb, MidIndex)) { + + // + // Vbn is less than the middle index so we need to drop + // the max down + // + + MaxIndex = MidIndex - 1; + + // + // check if the Vbn is greater than the mapping at the mid index + // + + } else if (Vbn > EndingVbn(Mcb, MidIndex)) { + + // + // Vbn is greater than the middle index so we need to bring + // up the min + // + + MinIndex = MidIndex + 1; + + // + // Otherwise we've found the index containing the Vbn so set the + // index and return TRUE. + // + + } else { + + *Index = MidIndex; + + return TRUE; + } + } + + // + // A match wasn't found so set index to PairCount and return FALSE + // + + *Index = Mcb->PairCount; + + return FALSE; +} + + +// +// Private Routine +// + +VOID +FsRtlAddLargeEntry ( + IN PNONOPAQUE_MCB Mcb, + IN ULONG WhereToAddIndex, + IN ULONG AmountToAdd + ) + +/*++ + +Routine Description: + + This routine takes a current Mcb and detemines if there is enough + room to add the new mapping entries. If there is not enough room + it reallocates a new mcb buffer and copies over the current mapping. + If also will spread out the current mappings to leave the specified + index slots in the mapping unfilled. For example, if WhereToAddIndex + is equal to the current pair count then we don't need to make a hole + in the mapping, but if the index is less than the current pair count + then we'll need to slide some of the mappings down to make room + at the specified index. + +Arguments: + + Mcb - Supplies the mcb being checked and modified + + WhereToAddIndex - Supplies the index of where the additional entries + need to be made + + AmountToAdd - Supplies the number of additional entries needed in the + mcb + +Return Value: + + None. + +--*/ + +{ + PAGED_CODE(); + + // + // Check to see if the current buffer is large enough to hold + // the additional entries + // + + if (Mcb->PairCount + AmountToAdd > Mcb->MaximumPairCount) { + + ULONG NewMax; + PMAPPING Mapping; + + // + // We need to allocate a new mapping so compute a new maximum pair + // count. We'll only be asked to grow by at most 2 at a time, so + // doubling will definitely make us large enough for the new amount. + // But we won't double without bounds we'll stop doubling if the + // pair count gets too high. + // + + if (Mcb->MaximumPairCount < 2048) { + + NewMax = Mcb->MaximumPairCount * 2; + + } else { + + NewMax = Mcb->MaximumPairCount + 2048; + } + + Mapping = (PMAPPING)FsRtlAllocatePool( Mcb->PoolType, sizeof(MAPPING)*NewMax ); + + //**** RtlZeroMemory( Mapping, sizeof(MAPPING) * NewMax ); + + // + // Now copy over the old mapping to the new buffer + // + + RtlCopyMemory( Mapping, Mcb->Mapping, sizeof(MAPPING) * Mcb->PairCount ); + + // + // Deallocate the old buffer + // + + if ((Mcb->PoolType == PagedPool) && (Mcb->MaximumPairCount == INITIAL_MAXIMUM_PAIR_COUNT)) { + + { PVOID t = Mcb->Mapping; FsRtlFreeFirstMapping( t ); } + + } else { + + ExFreePool( Mcb->Mapping ); + } + + // + // And set up the new buffer in the Mcb + // + + Mcb->Mapping = Mapping; + Mcb->MaximumPairCount = NewMax; + } + + // + // Now see if we need to shift some entries over according to the + // WhereToAddIndex value + // + + if (WhereToAddIndex < Mcb->PairCount) { + + RtlMoveMemory( &((Mcb->Mapping)[WhereToAddIndex + AmountToAdd]), + &((Mcb->Mapping)[WhereToAddIndex]), + (Mcb->PairCount - WhereToAddIndex) * sizeof(MAPPING) ); + } + + // + // Now zero out the new additions + // + + //**** RtlZeroMemory( &((Mcb->Mapping)[WhereToAddIndex]), sizeof(MAPPING) * AmountToAdd ); + + // + // Now increment the PairCount + // + + Mcb->PairCount += AmountToAdd; + + // + // And return to our caller + // + + return; +} + + +// +// Private Routine +// + +VOID +FsRtlRemoveLargeEntry ( + IN PNONOPAQUE_MCB Mcb, + IN ULONG WhereToRemoveIndex, + IN ULONG AmountToRemove + ) + +/*++ + +Routine Description: + + This routine takes a current Mcb and removes one or more entries. + +Arguments: + + Mcb - Supplies the mcb being checked and modified + + WhereToRemoveIndex - Supplies the index of the entries to remove + + AmountToRemove - Supplies the number of entries to remove + +Return Value: + + None. + +--*/ + +{ + PAGED_CODE(); + + // + // Check to see if we need to shift everything down because the + // entries to remove do not include the last entry in the mcb + // + + if (WhereToRemoveIndex + AmountToRemove < Mcb->PairCount) { + + RtlMoveMemory( &((Mcb->Mapping)[WhereToRemoveIndex]), + &((Mcb->Mapping)[WhereToRemoveIndex + AmountToRemove]), + (Mcb->PairCount - (WhereToRemoveIndex + AmountToRemove)) + * sizeof(MAPPING) ); + } + + // + // Now zero out the entries beyond the part we just shifted down + // + + //**** RtlZeroMemory( &((Mcb->Mapping)[Mcb->PairCount - AmountToRemove]), AmountToRemove * sizeof(MAPPING) ); + + // + // Now decrement the PairCount + // + + Mcb->PairCount -= AmountToRemove; + + // + // And return to our caller + // + + return; +} + + +// +// Private Routine +// + +PVOID +FsRtlAllocateFirstMapping( + ) + +/*++ + +Routine Description: + + This routine will if possible allocate the first mapping from either + a zone, a recent deallocated mapping, or pool. + +Arguments: + +Return Value: + + The mapping. + +--*/ + +{ + KIRQL _SavedIrql; + PVOID Mapping; + + ExAcquireSpinLock( &FsRtlStrucSupSpinLock, &_SavedIrql ); + + FsRtlNetFirstMapping += 1; + + if (FsRtlFreeFirstMappingSize > 0) { + Mapping = FsRtlFreeFirstMappingArray[--FsRtlFreeFirstMappingSize]; + ExReleaseSpinLock( &FsRtlStrucSupSpinLock, _SavedIrql ); + } else { + ExReleaseSpinLock( &FsRtlStrucSupSpinLock, _SavedIrql ); + Mapping = FsRtlAllocatePool( PagedPool, sizeof(MAPPING) * INITIAL_MAXIMUM_PAIR_COUNT ); + } + + return Mapping; +} + + +// +// Private Routine +// + +VOID +FsRtlFreeFirstMapping( + IN PVOID Mapping + ) + +/*++ + +Routine Description: + + This routine will if possible allocate the first mapping from either + a zone, a recent deallocated mapping, or pool. + +Arguments: + + Mapping - The mapping to either free to zone, put on the recent + deallocated list or free to pool. + +Return Value: + + The mapping. + +--*/ + +{ + KIRQL _SavedIrql; + + ExAcquireSpinLock( &FsRtlStrucSupSpinLock, &_SavedIrql ); + + FsRtlNetFirstMapping -= 1; + + if (FsRtlFreeFirstMappingSize < FREE_FIRST_MAPPING_ARRAY_SIZE) { + FsRtlFreeFirstMappingArray[FsRtlFreeFirstMappingSize++] = Mapping; + ExReleaseSpinLock( &FsRtlStrucSupSpinLock, _SavedIrql ); + } else { + ExReleaseSpinLock( &FsRtlStrucSupSpinLock, _SavedIrql ); + ExFreePool( Mapping ); + } +} + + +// +// Private Routine +// + +PFAST_MUTEX +FsRtlAllocateFastMutex( + ) + +/*++ + +Routine Description: + + This routine will if possible allocate the FastMutex from either + a zone, a recent deallocated FastMutex, or pool. + +Arguments: + +Return Value: + + The FastMutex. + +--*/ + +{ + KIRQL _SavedIrql; + PFAST_MUTEX FastMutex; + + ExAcquireSpinLock( &FsRtlStrucSupSpinLock, &_SavedIrql ); + + FsRtlNetFastMutex += 1; + + if (!ExIsFullZone(&FsRtlFastMutexZone)) { + FastMutex = (PFAST_MUTEX)ExAllocateFromZone(&FsRtlFastMutexZone); + ExReleaseSpinLock( &FsRtlStrucSupSpinLock, _SavedIrql ); + + } else if (FsRtlFreeFastMutexSize > 0) { + FastMutex = FsRtlFreeFastMutexArray[--FsRtlFreeFastMutexSize]; + ExReleaseSpinLock( &FsRtlStrucSupSpinLock, _SavedIrql ); + + } else { + ExReleaseSpinLock( &FsRtlStrucSupSpinLock, _SavedIrql ); + FastMutex = (PFAST_MUTEX)FsRtlAllocatePool( NonPagedPool, sizeof(FAST_MUTEX) ); + } + + return FastMutex; +} + + +// +// Private Routine +// + +VOID +FsRtlFreeFastMutex ( + IN PFAST_MUTEX FastMutex + ) + +/*++ + +Routine Description: + + This routine will if possible allocate the FastMutex from either + a zone, a recent deallocated FastMutexs, or pool. + +Arguments: + + Mapping - The FastMutex to either free to zone, put on the recent + deallocated list or free to pool. + +Return Value: + + The mapping. + +--*/ + +{ + KIRQL _SavedIrql; + + ExAcquireSpinLock( &FsRtlStrucSupSpinLock, &_SavedIrql ); + + FsRtlNetFastMutex -= 1; + + if (ExIsObjectInFirstZoneSegment(&FsRtlFastMutexZone, FastMutex)) { + ExFreeToZone(&FsRtlFastMutexZone, FastMutex); + ExReleaseSpinLock( &FsRtlStrucSupSpinLock, _SavedIrql ); + + } else if (FsRtlFreeFastMutexSize < FREE_FAST_MUTEX_ARRAY_SIZE) { + FsRtlFreeFastMutexArray[FsRtlFreeFastMutexSize++] = FastMutex; + ExReleaseSpinLock( &FsRtlStrucSupSpinLock, _SavedIrql ); + + } else { + ExReleaseSpinLock( &FsRtlStrucSupSpinLock, _SavedIrql ); + ExFreePool( FastMutex ); + } +} + + + diff --git a/untfs/src/mft.cxx b/untfs/src/mft.cxx new file mode 100644 index 0000000..ef216e0 --- /dev/null +++ b/untfs/src/mft.cxx @@ -0,0 +1,380 @@ +#include "stdafx.h" + + + + + + +#include "ulib.hxx" + +#include "untfs.hxx" + + +#include "mft.hxx" + +#include "attrib.hxx" +#include "drive.hxx" +#include "frsstruc.hxx" +#include "hmem.hxx" +#include "numset.hxx" + + + +DEFINE_CONSTRUCTOR( NTFS_MASTER_FILE_TABLE, OBJECT ); + +NTFS_MASTER_FILE_TABLE::~NTFS_MASTER_FILE_TABLE( + ) +{ + Destroy(); +} + + +VOID +NTFS_MASTER_FILE_TABLE::Construct( + ) +/*++ + +Routine Description: + + Worker function for the construtor. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _DataAttribute = NULL; + _MftBitmap = NULL; + _VolumeBitmap = NULL; + _BytesPerFrs = 0; + _ClusterFactor = 0; + _VolumeSectors = 0; + _MethodsEnabled = FALSE; + _ReadOnly = FALSE; +} + + +VOID +NTFS_MASTER_FILE_TABLE::Destroy( + ) +/*++ + +Routine Description: + + Clean up an NTFS_MASTER_FILE_TABLE object in preparation for + destruction or reinitialization. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _DataAttribute = NULL; + _MftBitmap = NULL; + _VolumeBitmap = NULL; + _BytesPerFrs = 0; + _ClusterFactor = 0; + _VolumeSectors = 0; + _MethodsEnabled = FALSE; + _ReadOnly = FALSE; +} + + +BOOLEAN +NTFS_MASTER_FILE_TABLE::Initialize( + IN OUT PNTFS_ATTRIBUTE DataAttribute, + IN OUT PNTFS_BITMAP MftBitmap, + IN OUT PNTFS_BITMAP VolumeBitmap, + IN OUT PNTFS_UPCASE_TABLE UpcaseTable, + IN ULONG ClusterFactor, + IN ULONG FrsSize, + IN ULONG SectorSize, + IN BIG_INT VolumeSectors, + IN BOOLEAN ReadOnly + ) +/*++ + +Routine Description: + + Initialize an NTFS_MASTER_FILE_TABLE object. + +Arguments: + + DataAttribute - Supplies the DATA attribute for the MFT. + MftBitmap - Supplies the MFT Bitmap for the MFT. + VolumeBitmap - Suppleis the volume bitmap. + UpcaseTable - Supplies the volume bitmap. + ClusterFactor - Supplies the number of sectors per cluster. + FrsSize - Supplies the number of bytes per FRS. + SectorSize - Supplies the number of bytes per sector. + VolumeSectors - Supplies the number of volume sectors. + ReadOnly - Supplies whether or not this class is read only. + +Return Value: + + TRUE upon successful completion. + +Notes: + + Unless the Upcase table is supplied, FRS's initialized with this + MFT will not be able to manipulate named attributes until the + upcase table is set. + +--*/ +{ + Destroy(); + + DebugAssert(DataAttribute); + DebugAssert(MftBitmap); + DebugAssert(ClusterFactor); + DebugAssert(FrsSize); + DebugAssert(SectorSize); + + _DataAttribute = DataAttribute; + _MftBitmap = MftBitmap; + _VolumeBitmap = VolumeBitmap; + _UpcaseTable = UpcaseTable; + _ClusterFactor = ClusterFactor; + _BytesPerFrs = FrsSize; + _VolumeSectors = VolumeSectors; + _MethodsEnabled = TRUE; + _ReadOnly = ReadOnly; + _SectorSize = SectorSize; + + return TRUE; +} + + + +BOOLEAN +NTFS_MASTER_FILE_TABLE::AllocateFileRecordSegment( + OUT PVCN FileNumber, + IN BOOLEAN IsMft + ) +/*++ + +Routine Description: + + Allocate a File Record Segment from the Master File Table. If the + allocation is being done for a user file, we make sure that the frs + doesn't come from the first cluster of the mft's allocation. + +Arguments: + + FileNumber -- Returns the file number of the allocated segment. + IsMft -- supplies a flag which indicates, if TRUE, that + the allocation is being made on behalf of the + MFT itself. + +Return Value: + + TRUE upon successful completion. + +Notes: + + Any bad clusters discovered by this routine are added to the volume + bitmap but not added to the bad clusters file. + +--*/ +{ + VCN vcn, reserved_vcn; + BIG_INT run_length; + HMEM hmem; + NTFS_FRS_STRUCTURE frs; + NUMBER_SET bad_cluster_list; + BOOLEAN reserve_allocated; + ULONG cluster_size; + + // + // This should really be a VCN instead of a LARGE_INTEGER, but the + // VCN causes the compiler to insert a reference to atexit(), which + // we want to avoid. -mjb. + // + + STATIC LARGE_INTEGER LastAllocatedVcn; + + DebugAssert(_MftBitmap); + + if (!_MethodsEnabled) { + return FALSE; + } + + cluster_size = QueryClusterFactor() * _SectorSize; + + if (LastAllocatedVcn * QueryFrsSize() < cluster_size) { + + LastAllocatedVcn.QuadPart = cluster_size / QueryFrsSize(); + } + + if( IsMft ) { + + // If the MFT has asked for a sector to be allocated, + // we can't grow the MFT (since we're in the process + // of saving it). However, the reservation scheme + // means that if we have allocated any FRS's to + // clients other than the MFT itself, there will be + // a free one in the bitmap, so we can just return + // it. + // + return _MftBitmap->AllocateClusters(1, 1, FileNumber, 1); + } + + + // Grab a reserved VCN for the MFT. + // + reserve_allocated = _MftBitmap->AllocateClusters(1, 1, &reserved_vcn, 1); + + + if (reserve_allocated && + _MftBitmap->AllocateClusters(LastAllocatedVcn, 1, FileNumber, 1)) { + + LastAllocatedVcn = FileNumber->GetLargeInteger(); + _MftBitmap->SetFree( reserved_vcn, 1 ); + return TRUE; + } + + // Grow the data attribute (and the MFT Bitmap) to + // include another File Record Segment. + // + if( !Extend(8) ) { + + return FALSE; + } + + // If we didn't get a reserved vcn before, get it now. + // + if( !reserve_allocated && + !_MftBitmap->AllocateClusters(1, 1, &reserved_vcn, 1) ) { + + return FALSE; + } + + // And now allocate the FRS we will return to the client. + // + if (!_MftBitmap->AllocateClusters(LastAllocatedVcn, 1, FileNumber, 1)) { + + return FALSE; + } + + + // Now read in the new FRS to make sure that it is good. + // Since we won't be manipulating any named attributes, we can + // pass in NULL for the upcase table. + + if (hmem.Initialize() && + bad_cluster_list.Initialize() && + frs.Initialize(&hmem, _DataAttribute, *FileNumber, + QueryClusterFactor(), + QueryVolumeSectors(), + QueryFrsSize(), + NULL)) { + + if (!frs.Read()) { + + vcn = (*FileNumber*QueryFrsSize() + (cluster_size - 1))/cluster_size; + + run_length = (QueryFrsSize() + (cluster_size - 1))/cluster_size; + + if (!_VolumeBitmap || + !_DataAttribute->Hotfix(vcn, run_length, _VolumeBitmap, + &bad_cluster_list)) { + + return FALSE; + } + } + } + + // Free the reserved FRS and return success. + // + _MftBitmap->SetFree( reserved_vcn, 1 ); + LastAllocatedVcn = FileNumber->GetLargeInteger(); + return TRUE; +} + + + +BOOLEAN +NTFS_MASTER_FILE_TABLE::Extend( + IN ULONG NumberOfSegmentsToAdd + ) +/*++ + +Routine Description: + + This method grows the Master File Table. It increases the + size of the Data attribute (to hold more File Record Segments) + and increases the size of the MFT Bitmap to match. + +Arguments: + + NumberOfSegmentsToAdd -- supplies the number of new File Record + Segments to add to the Master File Table. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + BIG_INT OldAllocatedLength, NumberOfSegments; + ULONG BytesToAdd; + + DebugAssert(_MftBitmap); + DebugAssert(_DataAttribute); + + if (!_MethodsEnabled || !_VolumeBitmap) { + return FALSE; + } + + // Find out how big it already is, and how much bigger + // it needs to be. + + OldAllocatedLength = _DataAttribute->QueryAllocatedLength(); + + BytesToAdd = NumberOfSegmentsToAdd * _BytesPerFrs; + + // Resize the attribute. Note that if Resize fails, it + // leaves the attribute unaltered. + // + if (!_DataAttribute->Resize( OldAllocatedLength + BytesToAdd, + _VolumeBitmap )) { + + return FALSE; + } + + // If the MFT is not operating in read-only mode, fill the + // new space with zeroes. + // + if (!_ReadOnly && !_DataAttribute->Fill( OldAllocatedLength, 0 ) ) { + + _DataAttribute->Resize( OldAllocatedLength, _VolumeBitmap ); + return FALSE; + } + + // Grow the MFT Bitmap to cover the new size of the Data Attribute. + // Note that NTFS_BITMAP::Resize will set the new bits free, which + // is what I want. + + NumberOfSegments = _DataAttribute->QueryAllocatedLength() / _BytesPerFrs; + + if( !_MftBitmap->Resize( NumberOfSegments ) ) { + + // I couldn't expand the MFT Bitmap to cover the new space, + // so I'll have to truncate the data attribute back down. + + _DataAttribute->Resize( OldAllocatedLength, _VolumeBitmap ); + return FALSE; + } + + return TRUE; +} diff --git a/untfs/src/mftfile.cxx b/untfs/src/mftfile.cxx new file mode 100644 index 0000000..010955e --- /dev/null +++ b/untfs/src/mftfile.cxx @@ -0,0 +1,602 @@ +#include "stdafx.h" + + +/*++ + +Module Name: + + mftfile.hxx + +Abstract: + + This module contains the member function definitions for the + NTFS_MFT_FILE class. + + +Notes: + + The MFT and the Volume Bitmap: + + The Master File Table needs the bitmap to extend itself. The + volume bitmap can be passed in upon initialization, or it can + be supplied (using SetVolumeBitmap) at any time. However, + until it is supplied, the Master File Table is unable to grow + itself. + +--*/ + + + + +#include "ulib.hxx" + +#include "untfs.hxx" + +#include "drive.hxx" +#include "attrib.hxx" +#include "ntfsbit.hxx" +#include "mftfile.hxx" +#include "clusrun.hxx" +#include "indxtree.hxx" + +#define LOGFILE_PLACEMENT_V1 1 + +DEFINE_CONSTRUCTOR( NTFS_MFT_FILE, NTFS_FILE_RECORD_SEGMENT ); + + +NTFS_MFT_FILE::~NTFS_MFT_FILE( + ) +{ + Destroy(); +} + + +VOID +NTFS_MFT_FILE::Construct( + ) +/*++ + +Routine Description: + + Worker function for the construtor. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _FirstLcn = 0; + _VolumeBitmap = NULL; +} + + +VOID +NTFS_MFT_FILE::Destroy( + ) +/*++ + +Routine Description: + + Clean up an NTFS_MFT_FILE object in preparation for + destruction or reinitialization. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _FirstLcn = 0; + _VolumeBitmap = NULL; +} + + + +BOOLEAN +NTFS_MFT_FILE::Initialize( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN LCN Lcn, + IN ULONG ClusterFactor, + IN ULONG FrsSize, + IN BIG_INT VolumeSectors, + IN OUT PNTFS_BITMAP VolumeBitmap, + IN PNTFS_UPCASE_TABLE UpcaseTable + ) +/*++ + +Routine Description: + + Initialize an NTFS_MFT_FILE object. + +Arguments: + + Drive -- supplies the Drive on which the file table resides + Lcn -- supplies the logical cluster number of the master + file table entry which describes the master file + table itself. + ClusterFactor -- supplies the number of sectors per cluster. + FrsSize -- supplies the number of bytes per File Record + Segment in this MFT. + VolumeSectors -- supplies the number of volume sectors. + VolumeBitmap -- supplies the bitmap for the volume. This parameter + may be NULL. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + ULONG MirroredClusters; + ULONG ClusterSize; + + Destroy(); + + DebugPtrAssert( Drive ); + + _FirstLcn = Lcn; + _VolumeBitmap = VolumeBitmap; + + ClusterSize = Drive->QuerySectorSize() * ClusterFactor; + + MirroredClusters = (REFLECTED_MFT_SEGMENTS * FrsSize + (ClusterSize - 1)) + / ClusterSize; + + if( !_MirrorMem.Initialize() || + !_MirrorClusterRun.Initialize( &_MirrorMem, + Drive, + 0, + ClusterFactor, + MirroredClusters ) ) { + + DebugPrint( "Can't initialize MFT helper cluster run.\n" ); + Destroy(); + return FALSE; + } + + if (!_Mft.Initialize(&_DataAttribute, &_MftBitmap, VolumeBitmap, + UpcaseTable, ClusterFactor, FrsSize, + Drive->QuerySectorSize(), VolumeSectors)) { + + return FALSE; + } + + _Mft.DisableMethods(); + + + if (!NTFS_FILE_RECORD_SEGMENT::Initialize(Drive, + Lcn, + &_Mft) ) { + + Destroy(); + return FALSE; + } + + + + return TRUE; +} + + +BOOLEAN +NTFS_MFT_FILE::Read( + ) +/*++ + +Routine Description: + + This routine reads this FRS for the MFT and then proceeds + to read the MFT bitmap. If all goes well, the internal + data attribute and MFT bitmap will be initialized. + + This method will return TRUE if and only if the base FRS for + this MFT is correctly read in. The MFT allocation methods will + be enabled by this method if and only if MFT bitmap and the + MFT data attribute are properly read in and initialized. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + NTFS_ATTRIBUTE MftBitmapAttribute; + BIG_INT DataAttributeWrittenLength; + BIG_INT DataAttributeAllocatedLength; + BIG_INT NumberOfAllocatedFileRecordSegments, + NumberOfWrittenFileRecordSegments; + ULONG RequiredBitsInBitmap, PreferredBitsInBitmap; + BOOLEAN Error; + + _Mft.DisableMethods(); + + if (!NTFS_FILE_RECORD_SEGMENT::Read()) { + return FALSE; + } + + _Mft.EnableMethods(); + + // Make sure that we have the Data Attribute to play with. + + if (!QueryAttribute(&_DataAttribute, &Error, $DATA)) { + + _Mft.DisableMethods(); + return TRUE; + } + + + // The length of the bitmap depends on the allocated length of + // the data attribute. + + _DataAttribute.QueryValueLength( &DataAttributeWrittenLength, + &DataAttributeAllocatedLength ); + + // A quick sanity check: + // + if( CompareGT(DataAttributeWrittenLength, DataAttributeAllocatedLength) ) { + + DebugAbort( "UNTFS: MFT Data attribute is corrupt.\n" ); + _Mft.DisableMethods(); + return TRUE; + } + + NumberOfWrittenFileRecordSegments = DataAttributeWrittenLength / QuerySize(); + NumberOfAllocatedFileRecordSegments = DataAttributeAllocatedLength / QuerySize(); + + DebugAssert( NumberOfWrittenFileRecordSegments.GetHighPart() == 0 ); + DebugAssert( NumberOfAllocatedFileRecordSegments.GetHighPart() == 0 ); + + RequiredBitsInBitmap = NumberOfWrittenFileRecordSegments.GetLowPart(); + PreferredBitsInBitmap = NumberOfAllocatedFileRecordSegments.GetLowPart(); + + // Create a bitmap, and get the MFT bitmap attribute through + // which we read it, and read the bitmap. + + if( !_MftBitmap.Initialize( RequiredBitsInBitmap, TRUE ) || + !QueryAttribute( &MftBitmapAttribute, &Error, $BITMAP ) || + !_MftBitmap.Read( &MftBitmapAttribute ) || + !_MftBitmap.Resize( PreferredBitsInBitmap ) ) { + + DebugAbort( "Cannot read MFT Bitmap.\n" ); + _Mft.DisableMethods(); + return TRUE; + } + + return TRUE; +} + + + +BOOLEAN +NTFS_MFT_FILE::Flush( + ) +/*++ + +Routine Description: + + This method flushes the MFT--re-inserts the DATA attribute (if + necessary); writes the MFT bitmap, and writes the MFT's own + File Record Segment. + +Arguments: + + None. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method will also write the volume bitmap and the mft mirror. + It will resize the $DATA attributes on the bitmap and mirror files + and write those FRS's, if necessary. + +--*/ +{ + NTFS_BITMAP_FILE BitmapFile; + NTFS_REFLECTED_MASTER_FILE_TABLE MirrorFile; + NTFS_INDEX_TREE RootIndex; + NTFS_FILE_RECORD_SEGMENT RootIndexFrs; + DSTRING FileNameIndexName; + + NTFS_ATTRIBUTE MftBitmapAttribute, + MirrorDataAttribute, + VolumeBitmapAttribute; + + LCN FirstMirrorLcn; + BIG_INT OldValidLength; + BOOLEAN Error; + + if( !_Mft.AreMethodsEnabled() ) { + + DebugAbort( "Tried to flush the MFT before enabling it.\n" ); + return FALSE; + } + + + // Ensure that the bitmap file and mirror file's $DATA attributes + // are the correct sizes. This will later allow us to write these + // two constructs without affecting their respective FRS's. + + if( !BitmapFile.Initialize( &_Mft ) || + !BitmapFile.Read() || + !BitmapFile.QueryAttribute( &VolumeBitmapAttribute, + &Error, + $DATA ) || + !_VolumeBitmap->CheckAttributeSize( &VolumeBitmapAttribute, + _VolumeBitmap ) || + !MirrorFile.Initialize( &_Mft ) || + !MirrorFile.Read() || + !MirrorFile.QueryAttribute( &MirrorDataAttribute, + &Error, + $DATA ) || + !CheckMirrorSize( &MirrorDataAttribute, + TRUE, + _VolumeBitmap, + &FirstMirrorLcn ) ) { + + DebugPrint( "Cannot check size of bitmap & mirror attributes.\n" ); + return FALSE; + } + + + if( VolumeBitmapAttribute.IsStorageModified() && + ( !VolumeBitmapAttribute.InsertIntoFile( &BitmapFile, + _VolumeBitmap ) || + !BitmapFile.Flush( _VolumeBitmap ) ) ) { + + DebugPrint( "Cannot save volume bitmap attribute.\n" ); + return FALSE; + } + + if( MirrorDataAttribute.IsStorageModified() && + ( !MirrorDataAttribute.InsertIntoFile( &MirrorFile, _VolumeBitmap ) || + !MirrorFile.Flush( _VolumeBitmap ) ) ) { + + DebugPrint( "Cannot save MFT mirror data attribute.\n" ); + return FALSE; + } + + // Fetch the root index from its FRS. + // + if( !RootIndexFrs.Initialize( ROOT_FILE_NAME_INDEX_NUMBER, this ) || + !RootIndexFrs.Read() || + !FileNameIndexName.Initialize( FileNameIndexNameData ) || + !RootIndex.Initialize( GetDrive(), + QueryClusterFactor(), + _VolumeBitmap, + GetUpcaseTable(), + QuerySize()/2, + &RootIndexFrs, + &FileNameIndexName ) ) { + + return FALSE; + } + + // Fetch the MFT Bitmap attribute. + // + if( !QueryAttribute( &MftBitmapAttribute, &Error, $BITMAP ) ) { + + DebugPrintTrace(( "UNTFS: Cannot fetch MFT bitmap attribute.\n" )); + return FALSE; + } + + // Write the bitmap once to make it the right size. If + // it grows while we're saving the MFT, we'll have to + // write it again. + // + if( !_MftBitmap.Write( &MftBitmapAttribute, _VolumeBitmap ) ) { + + DebugPrintTrace(( "UNTFS: Cannot write the MFT bitmap.\n" )); + return FALSE; + } + + do { + + // Note that inserting an attribute into a file resets the + // attribute's StorageModified flag. + // + if( MftBitmapAttribute.IsStorageModified() && + !MftBitmapAttribute.InsertIntoFile( this, NULL ) ) { + + DebugPrint( "UNTFS: Cannot save MFT bitmap attribute.\n" ); + return FALSE; + } + + // Remember the bitmap size (which is equal to the number + // of FRS's in the MFT). + // + OldValidLength = _DataAttribute.QueryValidDataLength(); + + // Save the data attribute. + // + if( _DataAttribute.IsStorageModified() && + !_DataAttribute.InsertIntoFile(this, NULL) ) { + + DebugAbort( "UNTFS: Cannot save MFT's data attribute.\n" ); + return FALSE; + } + + // Flush this FRS. + // + if( !NTFS_FILE_RECORD_SEGMENT::Flush( _VolumeBitmap, &RootIndex) ) { + + return FALSE; + } + + // Write the bitmap again, in case it changed. + // + if( !_MftBitmap.Write( &MftBitmapAttribute, _VolumeBitmap ) ) { + + DebugPrintTrace(( "UNTFS: Cannot write the MFT bitmap.\n" )); + return FALSE; + } + + // If the MFT's Valid Data Length changed while we were + // saving the data attribute and bitmap, we have to go + // through this loop again. + + } while( OldValidLength != _DataAttribute.QueryValidDataLength() ); + + // Save the root index: + // + if( !RootIndex.Save( &RootIndexFrs ) || + !RootIndexFrs.Flush( _VolumeBitmap ) ) { + + return FALSE; + } + + if( !_VolumeBitmap->Write( &VolumeBitmapAttribute, NULL ) || + !WriteMirror( &MirrorDataAttribute ) ){ + + DebugPrint( "Failed write of MFT Mirror or volume bitmap.\n" ); + return FALSE; + } + + return TRUE; +} + + +BOOLEAN +NTFS_MFT_FILE::CheckMirrorSize( + IN OUT PNTFS_ATTRIBUTE MirrorDataAttribute, + IN BOOLEAN Fix, + IN OUT PNTFS_BITMAP VolumeBitmap, + OUT PLCN FirstLcn + ) +/*++ + +Routine Description: + + This method checks that the MFT Mirror $DATA attribute is the + correct size and contiguous. It can also be used to check these + restrictions. + +Arguments: + + MirrorDataAttribut -- Supplies the MFT Mirror's $DATA attribute. + Fix -- Supplies a flag which indicates that the + attribute should be reallocated if it is + the wrong size or not contiguous. + VolumeBitmap -- Supplies the volume bitmap (only required + if Fix is TRUE). + FirstLcn -- Receives the starting LCN of the mirror. + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + BIG_INT RunLength; + LCN NewStartingLcn; + ULONG MirroredClusters; + ULONG ClusterSize; + + ClusterSize = QueryClusterFactor() * _Mft.QuerySectorSize(); + + MirroredClusters = (REFLECTED_MFT_SEGMENTS * QuerySize() + (ClusterSize - 1)) + / ClusterSize; + + if( MirrorDataAttribute->QueryLcnFromVcn( 0, FirstLcn, &RunLength ) && + *FirstLcn != 0 && + *FirstLcn != LCN_NOT_PRESENT && + RunLength >= MirroredClusters ) { + + // Everything is perfect. + + return TRUE; + } + + // Something is not perfect. + + if( Fix && + VolumeBitmap->AllocateClusters( QueryVolumeSectors()/ + QueryClusterFactor()/ + 2, + MirroredClusters, + &NewStartingLcn ) && + MirrorDataAttribute->Resize( 0, VolumeBitmap ) && + MirrorDataAttribute->AddExtent( 0, + NewStartingLcn, + MirroredClusters ) ) { + + // It was broken, but now it's perfect. + + *FirstLcn = NewStartingLcn; + return TRUE; + } + + return FALSE; +} + + +BOOLEAN +NTFS_MFT_FILE::WriteMirror( + IN OUT PNTFS_ATTRIBUTE MirrorDataAttribute + ) +/*++ + +Routine Description: + + This method writes the MFT Mirror. Note that it will fail if + the mirror's $DATA attribute is not the correct size or is + not contiguous. + +Arguments: + + MirrorDataAttribute -- Supplies the MFT Mirror's $DATA attribute. + +Return Value: + + TRUE upon successful completion. + +Notes: + + This method copies whatever is _on disk_ in the MFT's data attribute + to the mirror's data attribute. Therefore, it should only be called + after the MFT itself has been written. + +--*/ +{ + LCN FirstMirrorLcn; + + if( !CheckMirrorSize( MirrorDataAttribute, + FALSE, + NULL, + &FirstMirrorLcn ) ) { + + return FALSE; + } + + _MirrorClusterRun.Relocate( _FirstLcn ); + + if( !_MirrorClusterRun.Read() ) { + + return FALSE; + } + + _MirrorClusterRun.Relocate( FirstMirrorLcn ); + + if( !_MirrorClusterRun.Write() ) { + + return FALSE; + } + + return TRUE; +} diff --git a/untfs/src/mftref.cxx b/untfs/src/mftref.cxx new file mode 100644 index 0000000..a1be89c --- /dev/null +++ b/untfs/src/mftref.cxx @@ -0,0 +1,118 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + mftref.cxx + +Abstract: + + This module contains the member function definitions for + the NTFS_REFLECTED_MASTER_FILE_TABLE class. This class + models the backup copy of the Master File Table. + +--*/ + + +#include "ulib.hxx" + +#include "untfs.hxx" + +#include "drive.hxx" +#include "attrib.hxx" +#include "ntfsbit.hxx" +#include "mftref.hxx" +#include "ifssys.hxx" +#include "numset.hxx" +#include "message.hxx" + + +DEFINE_CONSTRUCTOR( NTFS_REFLECTED_MASTER_FILE_TABLE, + NTFS_FILE_RECORD_SEGMENT ); + + +NTFS_REFLECTED_MASTER_FILE_TABLE::~NTFS_REFLECTED_MASTER_FILE_TABLE( + ) +{ + Destroy(); +} + + +VOID +NTFS_REFLECTED_MASTER_FILE_TABLE::Construct( + ) +/*++ + +Routine Description: + + Worker function for the construtor. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ +} + + +VOID +NTFS_REFLECTED_MASTER_FILE_TABLE::Destroy( + ) +/*++ + +Routine Description: + + Clean up an NTFS_MASTER_FILE_TABLE object in preparation for + destruction or reinitialization. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ +} + + +BOOLEAN +NTFS_REFLECTED_MASTER_FILE_TABLE::Initialize( + IN OUT PNTFS_MASTER_FILE_TABLE Mft + ) +/*++ + +Routine Description: + + This method initializes a Master File Table Reflection object. + The only special knowledge that it adds to the File Record Segment + initialization is the location within the Master File Table of the + Master File Table Reflection. + +Arguments: + + Mft -- Supplies the volume MasterFile Table. + +Return Value: + + TRUE upon successful completion + +Notes: + + This class is reinitializable. + + +--*/ +{ + return( NTFS_FILE_RECORD_SEGMENT::Initialize( MASTER_FILE_TABLE2_NUMBER, + Mft ) ); +} + diff --git a/untfs/src/ntfsbit.cxx b/untfs/src/ntfsbit.cxx new file mode 100644 index 0000000..f231dc9 --- /dev/null +++ b/untfs/src/ntfsbit.cxx @@ -0,0 +1,705 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + ntfsbit.cxx + +Abstract: + + This module contains the declarations for NTFS_BITMAP, + which models the bitmap of an NTFS volume, and MFT_BITMAP, + which models the bitmap for the Master File Table. + +Notes: + + This implementation only supports bitmaps which have a number + of sectors which will fit in a ULONG. The interface supports + the 64-bit number of clusters, but Initialize will refuse to + accept a number-of-clusters value which has a non-zero high part. + + If we rewrite BITVECTOR to accept 64-bit cluster numbers (or + write a new one) this class could easily be fixed to support + larger volumes. + +--*/ + + +#include "ulib.hxx" + +#include "untfs.hxx" + +#include "ntfsbit.hxx" +#include "attrib.hxx" +#include "badfile.hxx" + +DEFINE_CONSTRUCTOR(NTFS_BITMAP, OBJECT); + + +NTFS_BITMAP::~NTFS_BITMAP( + ) +{ + Destroy(); +} + +VOID +NTFS_BITMAP::Construct( + ) +/*++ + +Routine Description: + + Worker method for object construction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _NumberOfClusters = 0; + _BitmapSize = 0; + _BitmapData = NULL; + _NextAlloc = 0; + _Mft = NULL; + _ClusterFactor = 0; + _Drive = NULL; +} + +VOID +NTFS_BITMAP::Destroy( + ) +/*++ + +Routine Description: + + Worker method to prepare an object for destruction + or reinitialization. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _NumberOfClusters = 0; + _BitmapSize = 0; + FREE( _BitmapData ); + _NextAlloc = 0; + _Mft = NULL; +} + + + + +BOOLEAN +NTFS_BITMAP::Initialize( + IN BIG_INT NumberOfClusters, + IN BOOLEAN IsGrowable, + IN PLOG_IO_DP_DRIVE Drive, + IN ULONG ClusterFactor + ) +/*++ + +Routine Description: + + This method initializes an NTFS_BITMAP object. + +Arguments: + + NumberOfClusters -- Supplies the number of allocation units + which the bitmap covers. + IsGrowable -- Supplies a flag indicating whether the + bitmap may grow (TRUE) or is of fixed size + (FALSE). + +Return Value: + + TRUE upon successful completion. + +Notes: + + The bitmap is initialized with all clusters within the range of + NumberOfClusters marked as FREE. + +--*/ +{ + ULONG LowNumberOfClusters; + + Destroy(); + + if( NumberOfClusters.GetHighPart() != 0 ) { + + DebugPrint( "bitmap.cxx: cannot manage a volume of this size.\n" ); + return FALSE; + } + + LowNumberOfClusters = NumberOfClusters.GetLowPart(); + + _NumberOfClusters = NumberOfClusters; + _IsGrowable = IsGrowable; + + _Drive = Drive; + _ClusterFactor = ClusterFactor; + + + // Determine the size in bytes of the bitmap. Note that this size + // is quad-aligned. + + _BitmapSize = ( LowNumberOfClusters % 8 ) ? + ( LowNumberOfClusters/8 + 1 ) : + ( LowNumberOfClusters/8 ); + + _BitmapSize = QuadAlign( max(_BitmapSize, 1) ); + + + // Allocate space for the bitvector and initialize it. + + if( (_BitmapData = MALLOC( _BitmapSize )) == NULL || + !_Bitmap.Initialize( _BitmapSize * 8, + RESET, + (PPT)_BitmapData ) ) { + + // Note that Destroy will clean up _BitmapData + + Destroy(); + return FALSE; + } + + // If the bitmap is growable, then any padding bits are reset (free); + // if it is fixed size, they are set (allocated). + + if( _IsGrowable ) { + + _Bitmap.ResetBit( _NumberOfClusters.GetLowPart(), + _BitmapSize * 8 - _NumberOfClusters.GetLowPart() ); + + } else { + + _Bitmap.SetBit( _NumberOfClusters.GetLowPart(), + _BitmapSize * 8 - _NumberOfClusters.GetLowPart() ); + } + + // The bitmap is intialized with all clusters marked free. + // + SetFree( 0, _NumberOfClusters ); + + return TRUE; +} + + + +BOOLEAN +NTFS_BITMAP::Write( + IN OUT PNTFS_ATTRIBUTE BitmapAttribute, + IN OUT PNTFS_BITMAP VolumeBitmap + ) +/*++ + +Routine Description: + + This method writes the bitmap. + +Arguments: + + BitmapAttribute -- supplies the attribute which describes the + bitmap's location on disk. + VolumeBitmap -- supplies the volume's bitmap for possible + allocation during write. + +Return Value: + + TRUE upon successful completion. + +Notes: + + The attribute will, if necessary, allocate space from the + bitmap to write it. + +--*/ +{ + ULONG BytesWritten; + + DebugPtrAssert( _BitmapData ); + + + + return( CheckAttributeSize( BitmapAttribute, VolumeBitmap ) && + BitmapAttribute->Write( _BitmapData, + 0, + _BitmapSize, + &BytesWritten, + VolumeBitmap ) && + BytesWritten == _BitmapSize ); +} + + +BOOLEAN +NTFS_BITMAP::IsFree( + IN LCN Lcn, + IN BIG_INT RunLength + ) CONST +/*++ + +Routine Description: + + This method determines whether the specified cluster run is + marked as free in the bitmap. + +Arguments: + + Lcn -- supplies the LCN of the first cluster in the run + RunLength -- supplies the length of the run + +Return Value: + + TRUE if all clusters in the run are free in the bitmap. + +Notes: + + This method checks to make sure that the LCNs in question are in + range, i.e. less than the number of clusters in the bitmap. + +--*/ +{ + ULONG i, CurrentLcn; + + + if( Lcn < 0 || + Lcn + RunLength > _NumberOfClusters ) { + + return FALSE; + } + + // Note that, since _NumberOfClusters is not greater than the + // maximum ULONG, the high parts of Lcn and RunLength are + // sure to be zero. + + for( i = 0, CurrentLcn = Lcn.GetLowPart(); + i < RunLength.GetLowPart(); + i++, CurrentLcn++ ) { + + if( _Bitmap.IsBitSet( CurrentLcn ) ) { + + return FALSE; + } + } + + return TRUE; +} + + +BOOLEAN +NTFS_BITMAP::AllocateClusters( + IN LCN NearHere, + IN BIG_INT RunLength, + OUT PLCN FirstAllocatedLcn, + IN ULONG AlignmentFactor + ) +/*++ + +Routine Description: + + This method finds a free run of sectors and marks it as allocated. + + If the bitmap being allocated from is the volume bitmap, this method + will have a valid _Drive member. In this case, it will attempt to + verify that only usable clusters are allocated. If _Mft is also + set, any bad clusters found will be added to the bad cluster file. + +Arguments: + + NearHere -- supplies the LCN near which the caller would + like the space allocated. + RunLength -- supplies the number of clusters to be allocated + FirstAllocatedLcn -- receives the first LCN of the allocated run + AlignmentFactor -- supplies the alignment requirement for the + allocated run--it must start on a multiple + of AlignmentFactor. + +Return Value: + + TRUE upon successful completion; FirstAllocatedLcn receives the + LCN of the first cluster in the run. + +--*/ +{ + ULONG current_lcn; + LCN first_allocated_lcn; + ULONG count; + BOOLEAN verify_each; + + NTFS_BAD_CLUSTER_FILE badclus; + + if (NearHere == 0) { + NearHere = _NextAlloc; + } + + if (NearHere + RunLength > _NumberOfClusters) { + NearHere = _NumberOfClusters/2; + } + + if( RunLength.GetHighPart() != 0 ) { + + DebugAbort( "UNTFS: Trying to allocate too many sectors.\n" ); + return FALSE; + } + + // + // First we'll allocate a run and verify the whole thing at once. + // If that fails we'll go through the bitmap again, verifying each + // cluster. + // + + verify_each = FALSE; + +again: + + // Search forwards for a big enough block. + + count = RunLength.GetLowPart(); + for (current_lcn = NearHere.GetLowPart(); + count > 0 && current_lcn < _NumberOfClusters; + current_lcn += 1) { + + if (IsFree(current_lcn, 1)) { + + if (count == RunLength.GetLowPart() && current_lcn%AlignmentFactor != 0) { + continue; + } + + if (verify_each && NULL != _Drive) { + + // Insure that this cluster is functional and can accept IO. + + if (!_Drive->Verify(current_lcn * _ClusterFactor, + _ClusterFactor)) { + + // This cluster is bad. Set the bit in the bitmap so we + // won't waste time trying to allocate it again and start + // over. + + SetAllocated(current_lcn, 1); + count = RunLength.GetLowPart(); + + // If the bad cluster file is available, add this lcn + // to it. + + if (NULL != _Mft) { + if (!badclus.Initialize(_Mft) || + !badclus.Read() || + !badclus.Add(current_lcn) || + !badclus.Flush(this)) { + DebugPrintTrace(("Unable to update bad cluster file. Bad Cluster at: %x\n", + current_lcn)); + } + } + + continue; + } + } + + count -= 1; + + } else { + count = RunLength.GetLowPart(); + } + } + + // + // If the forward search succeeded then allocate and return the + // result. + // + + if (count == 0) { + + first_allocated_lcn = current_lcn - RunLength; + + if (NULL != _Drive && !_Drive->Verify(first_allocated_lcn * _ClusterFactor, + RunLength * _ClusterFactor, + NULL)) { + + if (verify_each) { + + // If we have been here before, then the whole block is bad as + // Verify cannot tell which individual cluster is/are bad + + SetAllocated(first_allocated_lcn, RunLength); + verify_each = FALSE; + NearHere = first_allocated_lcn + RunLength; + + if (NULL != _Mft) { + if (!badclus.Initialize(_Mft) || + !badclus.Read() || + !badclus.AddRun(first_allocated_lcn, RunLength) || + !badclus.Flush(this)) { + DebugPrintTrace(("Unable to update bad cluster file.\n" + "Bad Cluster starts at %x%x with run length %x%x\n", + first_allocated_lcn.GetHighPart(), + first_allocated_lcn.GetLowPart(), + RunLength.GetHighPart(), + RunLength.GetLowPart())); + } + } + + goto again; + } + + // + // Want to go through each cluster in the run we found and + // figure out which ones are bad. + // + + verify_each = TRUE; + NearHere = first_allocated_lcn; + goto again; + } + + *FirstAllocatedLcn = first_allocated_lcn; + SetAllocated(first_allocated_lcn, RunLength); + _NextAlloc = first_allocated_lcn + RunLength; + return TRUE; + } + + // + // We couldn't find any space by searching forwards, so let's + // search backwards. + // + + verify_each = FALSE; + +again_backward: + + count = RunLength.GetLowPart(); + for (current_lcn = NearHere.GetLowPart() + RunLength.GetLowPart() - 1; + count > 0 && current_lcn > 0; current_lcn -= 1) { + + if (IsFree(current_lcn, 1)) { + + if (count == RunLength.GetLowPart() && + (current_lcn - RunLength.GetLowPart() + 1)%AlignmentFactor != 0) { + + continue; + } + + if (verify_each && NULL != _Drive) { + + // Insure that this cluster is functional and can accept IO. + + if (!_Drive->Verify(current_lcn * _ClusterFactor, + _ClusterFactor)) { + + // This cluster is bad. Set the bit in the bitmap so we + // won't waste time trying to allocate it again and start + // over. + + SetAllocated(current_lcn, 1); + count = RunLength.GetLowPart(); + + // If the bad cluster file is available, add this lcn + // to it. + + if (NULL != _Mft) { + if (!badclus.Initialize(_Mft) || + !badclus.Read() || + !badclus.Add(current_lcn) || + !badclus.Flush(this)) { + DebugPrintTrace(("Unable to update bad cluster file. Bad Cluster at: %x\n", + current_lcn)); + } + } + + continue; + } + } + + count -= 1; + } else { + + count = RunLength.GetLowPart(); + } + } + + if (count != 0) { + return FALSE; + } + + first_allocated_lcn = current_lcn + 1; + + if (NULL != _Drive && !_Drive->Verify(first_allocated_lcn * _ClusterFactor, + RunLength * _ClusterFactor, + NULL)) { + + if (verify_each) { + + // If we have been here before, then the whole block is bad as + // Verify cannot tell which individual cluster is/are bad + + SetAllocated(first_allocated_lcn, RunLength); + verify_each = FALSE; + NearHere = first_allocated_lcn - RunLength; + + if (NULL != _Mft) { + if (!badclus.Initialize(_Mft) || + !badclus.Read() || + !badclus.AddRun(first_allocated_lcn, RunLength) || + !badclus.Flush(this)) { + DebugPrintTrace(("Unable to update bad cluster file.\n" + "Bad Cluster starts at %x%x with run length %x%x\n", + first_allocated_lcn.GetHighPart(), + first_allocated_lcn.GetLowPart(), + RunLength.GetHighPart(), + RunLength.GetLowPart())); + } + } + + goto again_backward; + } + + // + // Want to go through each cluster in the run we found and + // figure out which ones are bad. + // + + verify_each = TRUE; + NearHere = first_allocated_lcn + RunLength + 1; + goto again_backward; + } + + // + // Since we had to search backwards, we don't want to start + // our next search from here (and waste time searching forwards). + // Instead, set the roving pointer to zero. + // + + *FirstAllocatedLcn = first_allocated_lcn; + SetAllocated(first_allocated_lcn, RunLength); + _NextAlloc = 0; + + return TRUE; +} + + +BOOLEAN +NTFS_BITMAP::Resize( + IN BIG_INT NewNumberOfClusters + ) +/*++ + +Routine Description: + + This method changes the number of allocation units that the bitmap + covers. It may either grow or shrink the bitmap. + +Arguments: + + NewNumberOfClusters -- supplies the new number of allocation units + covered by this bitmap. + +Return Value: + + TRUE upon successful completion. + +Notes: + + The size (in bytes) of the bitmap is always kept quad-aligned, and + any padding bits are reset. + +--*/ +{ + PVOID NewBitmapData; + ULONG NewSize; + + DebugAssert( _IsGrowable ); + + + // Make sure that the number of clusters fits into a ULONG, + // so we can continue to use BITVECTOR. + + if( NewNumberOfClusters.GetHighPart() != 0 ) { + + DebugPrint( "bitmap.cxx: cannot manage a volume of this size.\n" ); + return FALSE; + } + + // Compute the new size of the bitmap, in bytes. Note that this + // size is always quad-aligned (ie. a multiple of 8). + + NewSize = ( NewNumberOfClusters.GetLowPart() % 8 ) ? + ( NewNumberOfClusters.GetLowPart()/8 + 1) : + ( NewNumberOfClusters.GetLowPart()/8 ); + + NewSize = QuadAlign( NewSize ); + + if( NewSize == _BitmapSize ) { + + // The bitmap is already the right size, so it's just a matter + // of diddling the private data. Since padding in a growable + // bitmap is always reset (free), the new space is by default + // free. + + _NumberOfClusters = NewNumberOfClusters; + return TRUE; + } + + // The bitmap has changed size, so we need to allocate new memory + // for it and copy it. + + if( (NewBitmapData = MALLOC( NewSize )) == NULL ) { + + return FALSE; + } + + // Note that, if we supply the memory, BITVECTOR::Initialize + // cannot fail, so we don't check its return value. + + _Bitmap.Initialize( NewSize * 8, + RESET, + (PPT)NewBitmapData ); + + if( NewNumberOfClusters < _NumberOfClusters ) + { + // Copy the part of the old bitmap that we wish to + // preserve into the new bitmap. + + memcpy( NewBitmapData, + _BitmapData, + NewSize ); + + } + else + { + // Copy the old bitmap into the new bitmap, and then + // mark all the newly claimed space as unused. + + memcpy( NewBitmapData, + _BitmapData, + _BitmapSize ); + + SetFree( _NumberOfClusters, + NewNumberOfClusters - _NumberOfClusters ); + } + + FREE( _BitmapData ); + _BitmapData = NewBitmapData; + + _BitmapSize = NewSize; + _NumberOfClusters = NewNumberOfClusters; + + // Make sure the padding bits are reset. + + _Bitmap.ResetBit( _NumberOfClusters.GetLowPart(), + _BitmapSize * 8 - _NumberOfClusters.GetLowPart() ); + + return TRUE; +} + diff --git a/untfs/src/ntfssa.cxx b/untfs/src/ntfssa.cxx new file mode 100644 index 0000000..54dc591 --- /dev/null +++ b/untfs/src/ntfssa.cxx @@ -0,0 +1,954 @@ +#include "stdafx.h" + + + +#include "ulib.hxx" +#include "ntfssa.hxx" +#include "message.hxx" + + +#include "array.hxx" +#include "arrayit.hxx" + +#include "mftfile.hxx" +#include "ntfsbit.hxx" +#include "frs.hxx" +#include "wstring.hxx" +#include "indxtree.hxx" +#include "badfile.hxx" +#include "bitfrs.hxx" +#include "attrib.hxx" +#include "attrrec.hxx" +#include "mft.hxx" +#include "upcase.hxx" +#include "upfile.hxx" +#include "ifssys.hxx" + + +#include "path.hxx" + + +UCHAR NTFS_SA::_MajorVersion = NTFS_CURRENT_MAJOR_VERSION, + NTFS_SA::_MinorVersion = NTFS_CURRENT_MINOR_VERSION; + +DEFINE_CONSTRUCTOR( NTFS_SA, SUPERAREA ); + + +VOID +NTFS_SA::Construct ( + ) +/*++ + +Routine Description: + + This routine sets an NTFS_SA to a default initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _cleanup_that_requires_reboot = FALSE; + _boot_sector = NULL; + memset(&_bpb, 0, sizeof(BIOS_PARAMETER_BLOCK)); + _boot2 = 0; + _boot3 = 0; + _NumberOfStages = 0; + _cvt_zone = 0; + _cvt_zone_size = 0; +} + + +VOID +NTFS_SA::Destroy( + ) +/*++ + +Routine Description: + + This routine returns an NTFS_SA to a default initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _cleanup_that_requires_reboot = FALSE; + _boot_sector = NULL; + memset(&_bpb, 0, sizeof(BIOS_PARAMETER_BLOCK)); + _boot2 = 0; + _boot3 = 0; + _cvt_zone = 0; + _cvt_zone_size = 0; +} + + + +NTFS_SA::~NTFS_SA( + ) +/*++ + +Routine Description: + + Destructor for NTFS_SA. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + + + +BOOLEAN +NTFS_SA::Initialize( + IN OUT PLOG_IO_DP_DRIVE Drive, + IN OUT PMESSAGE Message, + IN LCN CvtStartZone, + IN BIG_INT CvtZoneSize + ) +/*++ + +Routine Description: + + This routine returns an NTFS_SA to a default initial state. + +Arguments: + + Drive - Supplies the drive that this MultiSectorBuffer is on + Message - Supplies an outlet for messages. + CvtStartZone - Supplies the starting cluster of the convert zone + CvtZoneSize - Supplies the convert zone size in clusters + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + ULONG num_boot_sectors; + + Destroy(); + + DebugAssert(Drive); + DebugAssert(Message); + + num_boot_sectors = max(1, BYTES_PER_BOOT_SECTOR/Drive->QuerySectorSize()); + + if (!_hmem.Initialize() || + !SUPERAREA::Initialize(&_hmem, Drive, num_boot_sectors, Message)) + { + return FALSE; + } + + _boot_sector = (PPACKED_BOOT_SECTOR) SECRUN::GetBuf(); + + _cvt_zone = CvtStartZone; + _cvt_zone_size = CvtZoneSize; + + return TRUE; +} + + + +BOOLEAN +NTFS_SA::Read( + IN OUT PMESSAGE Message + ) +/*++ + +Routine Description: + + This routine reads the NTFS volume's boot sector from disk. + If the read fails then a message will be printed and then + we will attempt to find an alternate boot sector, looking + first at the end of the volume and then in the middle. + +Arguments: + + Message - Supplies an outlet for messages. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + DebugAssert(Message); + + if (!SECRUN::Read()) + { + Message->Out("The first NTFS boot sector is unreadable. Reading second NTFS boot sector instead."); + + _boot2 = _drive->QuerySectors() - 1; + Relocate(_boot2); + + if (!SECRUN::Read() || + !IFS_SYSTEM::IsThisNtfs(_drive->QuerySectors(), + _drive->QuerySectorSize(), + (PVOID)_boot_sector)) { + + _boot2 = _drive->QuerySectors()/2; + Relocate(_boot2); + + if (!SECRUN::Read() || + !IFS_SYSTEM::IsThisNtfs(_drive->QuerySectors(), + _drive->QuerySectorSize(), + (PVOID)_boot_sector)) + { + Message->Out("All NTFS boot sectors are unreadable. Cannot continue."); + + _boot2 = 0; + Relocate(0); + return FALSE; + } + } + + Relocate(0); + } + + UnpackBios(&_bpb, &(_boot_sector->PackedBpb)); + + if (QueryVolumeSectors() < _drive->QuerySectors()) { + _boot2 = _drive->QuerySectors() - 1; + } else { + _boot2 = _drive->QuerySectors() / 2; + } + + return TRUE; +} + + + +BOOLEAN +NTFS_SA::Write( + IN OUT PMESSAGE Message + ) +/*++ + +Routine Description: + + This routine writes both of the NTFS volume's boot sector to disk. + If the write fails on either of the boot sectors then a message + will be printed. + +Arguments: + + Message - Supplies an outlet for messages. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + DebugAssert(Message); + + PackBios(&_bpb, &(_boot_sector->PackedBpb)); + + if (SECRUN::Write()) { + + Relocate(_boot2); + + if (!SECRUN::Write()) { + + Message->Out("The second NTFS boot sector is unwriteable."); + return FALSE; + } + + Relocate(0); + + } else { + + Message->Out("The first NTFS boot sector is unwriteable."); + + Relocate(_boot2); + + if (!SECRUN::Write()) { + Message->Out("All NTFS boot sectors are unwriteable. Cannot continue."); + Relocate(0); + return FALSE; + } + + Relocate(0); + } + + return TRUE; +} + +VOID +NTFS_SA::SetVersionNumber( + IN UCHAR Major, + IN UCHAR Minor + ) +{ + _MajorVersion = Major; + _MinorVersion = Minor; +} + + +VOID +NTFS_SA::QueryVersionNumber( + OUT PUCHAR Major, + OUT PUCHAR Minor + ) +{ + *Major = _MajorVersion; + *Minor = _MinorVersion; +} + + + +USHORT +NTFS_SA::QueryVolumeFlags( + OUT PBOOLEAN CorruptVolume, + OUT PUCHAR MajorVersion, + OUT PUCHAR MinorVersion + ) +/*++ + +Routine Description: + + This routine fetches the volume flags. + +Arguments: + + CorruptVolume - Returns whether or not a volume corruption was + detected. + MajorVersion - Returns the major file system version number. + MinorVersion - Returns the minor file system version number. + +Return Value: + + The flags describing this volume's state + +--*/ +{ + NTFS_FRS_STRUCTURE frs; + HMEM hmem; + LCN cluster_number, alternate; + ULONG cluster_offset, alternate_offset; + PVOID p; + NTFS_ATTRIBUTE_RECORD attr_rec; + PVOLUME_INFORMATION vol_info; + + if (CorruptVolume) { + *CorruptVolume = FALSE; + } + + if (MajorVersion) { + *MajorVersion = 0; + } + + if (MinorVersion) { + *MinorVersion = 0; + } + + ULONG cluster_size = QueryClusterFactor() * _drive->QuerySectorSize(); + + cluster_number = (VOLUME_DASD_NUMBER * QueryFrsSize())/ cluster_size + + QueryMftStartingLcn(); + + cluster_offset = (QueryMftStartingLcn()*cluster_size + + VOLUME_DASD_NUMBER * QueryFrsSize() - cluster_number * cluster_size).GetLowPart(); + + DebugAssert(cluster_offset < cluster_size); + + alternate = (VOLUME_DASD_NUMBER * QueryFrsSize())/ cluster_size + + QueryMft2StartingLcn(); + + alternate_offset = (QueryMft2StartingLcn()*cluster_size + + VOLUME_DASD_NUMBER * QueryFrsSize() - alternate * cluster_size).GetLowPart(); + + for (;;) { + + if (!hmem.Initialize() || + !frs.Initialize(&hmem, _drive, cluster_number, + QueryClusterFactor(), + QueryVolumeSectors(), + QueryFrsSize(), + NULL, + cluster_offset) || + !frs.Read()) { + + if (cluster_number == alternate) { + break; + } else { + cluster_number = alternate; + cluster_offset = alternate_offset; + continue; + } + } + + p = NULL; + while (p = frs.GetNextAttributeRecord(p)) + { + if (!attr_rec.Initialize(GetDrive(), p)) + { + // the attribute record containing the volume flags + // is not available--this means that the volume is + // dirty. + // + return VOLUME_DIRTY; + } + +#if ($VOLUME_NAME > $VOLUME_INFORMATION) +#error Attribute type $VOLUME_NAME should be smaller than that of $VOLUME_INFORMATION +#endif + + if (attr_rec.QueryTypeCode() == $VOLUME_INFORMATION && + attr_rec.QueryNameLength() == 0 && + attr_rec.QueryRecordLength() > SIZE_OF_RESIDENT_HEADER && + attr_rec.QueryResidentValueLength() < attr_rec.QueryRecordLength() && + (attr_rec.QueryRecordLength() - attr_rec.QueryResidentValueLength()) >= + attr_rec.QueryResidentValueOffset() && + attr_rec.QueryResidentValueLength() >= sizeof(VOLUME_INFORMATION) && + (vol_info = (PVOLUME_INFORMATION) attr_rec.GetResidentValue())) + { + if (MajorVersion) + { + *MajorVersion = vol_info->MajorVersion; + } + + if (MinorVersion) + { + *MinorVersion = vol_info->MinorVersion; + } + + if (vol_info->MajorVersion > 3) + { + break; // try the mirror copy + } + + return (vol_info->VolumeFlags); + } + } + + // If the desired attribute wasn't found in the first + // volume dasd file then check the mirror. + + if (cluster_number == alternate) { + break; + } else { + cluster_number = alternate; + cluster_offset = alternate_offset; + } + } + + if (CorruptVolume) { + *CorruptVolume = TRUE; + } + + return VOLUME_DIRTY; +} + + + +UCHAR +NTFS_SA::PostReadMultiSectorFixup( + IN OUT PVOID MultiSectorBuffer, + IN ULONG BufferSize, + IN PIO_DP_DRIVE Drive, + IN ULONG ValidSize + ) +/*++ + +Routine Description: + + This routine first verifies that the first element of the + update sequence array is written at the end of every + SEQUENCE_NUMBER_STRIDE bytes till it exceeds the given + valid size. If not, then this routine returns FALSE. + + Otherwise this routine swaps the following elements in the + update sequence array into the appropriate positions in the + multi sector buffer. + + This routine will also check to make sure that the update + sequence array is valid and that the BufferSize is appropriate + for this size of update sequence array. Otherwise, this + routine will not update the array sequence and return TRUE. + +Arguments: + + MultiSectorBuffer - Supplies the buffer to be updated. + BufferSize - Supplies the number of bytes in this + buffer. + Drive - Supplies the drive that this MultiSectorBuffer is on + ValidSize - Supplies the number of bytes that is + valid in this buffer + +Return Value: + + UpdateSequenceArrayCheckValueOk (always non-zero) + - If everything is ok. If any valid sector does not + contain the check value, the header signature will + be changed to 'BAAD'. + UpdateSequenceArrayMinorError + - Same as 1 except the check value beyond ValidSize + is incorrect. +--*/ +{ + PUNTFS_MULTI_SECTOR_HEADER pheader; + USHORT i, size, offset; + PUPDATE_SEQUENCE_NUMBER parray, pnumber; + UCHAR rtncode = UpdateSequenceArrayCheckValueOk; + + pheader = (PUNTFS_MULTI_SECTOR_HEADER) MultiSectorBuffer; + size = pheader->UpdateSequenceArraySize; + offset = pheader->UpdateSequenceArrayOffset; + + if (BufferSize%SEQUENCE_NUMBER_STRIDE || + offset%sizeof(UPDATE_SEQUENCE_NUMBER) || + offset + size*sizeof(UPDATE_SEQUENCE_NUMBER) > BufferSize || + BufferSize/SEQUENCE_NUMBER_STRIDE + 1 != size) { + + return rtncode; + } + + parray = (PUPDATE_SEQUENCE_NUMBER) ((PCHAR) pheader + offset); + + pnumber = (PUPDATE_SEQUENCE_NUMBER) + ((PCHAR) pheader + (SEQUENCE_NUMBER_STRIDE - + sizeof(UPDATE_SEQUENCE_NUMBER))); + + for (i = 1; i < size; i++) { + + if (*pnumber != parray[0]) { + if (ValidSize > 0) { + + DebugPrintTrace(("Incorrect USA check value at block %d.\n" + "The expected value is %d but found %d\n", + i, *pnumber, parray[0])); + + pheader->Signature[0] = 'B'; + pheader->Signature[1] = 'A'; + pheader->Signature[2] = 'A'; + pheader->Signature[3] = 'D'; + return rtncode; + } else + rtncode = UpdateSequenceArrayCheckValueMinorError; + } + + *pnumber = parray[i]; + + if (ValidSize >= SEQUENCE_NUMBER_STRIDE) + ValidSize -= SEQUENCE_NUMBER_STRIDE; + else + ValidSize = 0; + + pnumber = (PUPDATE_SEQUENCE_NUMBER) + ((PCHAR) pnumber + SEQUENCE_NUMBER_STRIDE); + } + + return rtncode; +} + + +VOID +NTFS_SA::PreWriteMultiSectorFixup( + IN OUT PVOID MultiSectorBuffer, + IN ULONG BufferSize + ) +/*++ + +Routine Description: + + This routine first checks to see if the update sequence + array is valid. If it is then this routine increments the + first element of the update sequence array. It then + writes the value of the first element into the buffer at + the end of every SEQUENCE_NUMBER_STRIDE bytes while + saving the old values of those locations in the following + elements of the update sequence arrary. + +Arguments: + + MultiSectorBuffer - Supplies the buffer to be updated. + BufferSize - Supplies the number of bytes in this + buffer. + +Return Value: + + None. + +--*/ +{ + PUNTFS_MULTI_SECTOR_HEADER pheader; + USHORT i, size, offset; + PUPDATE_SEQUENCE_NUMBER parray, pnumber; + + pheader = (PUNTFS_MULTI_SECTOR_HEADER) MultiSectorBuffer; + size = pheader->UpdateSequenceArraySize; + offset = pheader->UpdateSequenceArrayOffset; + + if (BufferSize%SEQUENCE_NUMBER_STRIDE || + offset%sizeof(UPDATE_SEQUENCE_NUMBER) || + offset + size*sizeof(UPDATE_SEQUENCE_NUMBER) > BufferSize || + BufferSize/SEQUENCE_NUMBER_STRIDE + 1 != size) { + + return; + } + + parray = (PUPDATE_SEQUENCE_NUMBER) ((PCHAR) pheader + offset); + + + // Don't allow 0 or all F's to be the update character. + + do { + parray[0]++; + } while (parray[0] == 0 || parray[0] == (UPDATE_SEQUENCE_NUMBER) -1); + + + for (i = 1; i < size; i++) { + + pnumber = (PUPDATE_SEQUENCE_NUMBER) + ((PCHAR) pheader + (i*SEQUENCE_NUMBER_STRIDE - + sizeof(UPDATE_SEQUENCE_NUMBER))); + + parray[i] = *pnumber; + *pnumber = parray[0]; + } +} + + +BOOLEAN +NTFS_SA::Read( + ) +/*++ + +Routine Description: + + This routine simply calls the other read with the default message + object. + +Arguments: + + None. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + MESSAGE msg; + + return Read(&msg); +} + + +UCHAR +NTFS_SA::QueryClusterFactor( + ) CONST +/*++ + +Routine Description: + + This routine returns the number of sectors per cluster. + +Arguments: + + None. + +Return Value: + + The number of sectors per cluster. + +--*/ +{ + return _bpb.SectorsPerCluster; +} + + +BOOLEAN +NTFS_SA::MarkBad( + IN __int64 firstPhysicalDriveSector, + IN __int64 lastPhysicalDriveSector, + IN OUT PMESSAGE Message +) +{ + NTFS_ATTRIBUTE BitmapAttribute; + NTFS_MFT_FILE MftFile; + NTFS_BITMAP_FILE BitmapFile; + NTFS_BAD_CLUSTER_FILE BadClusterFile; + NTFS_BITMAP VolumeBitmap; + NTFS_UPCASE_FILE UpcaseFile; + NTFS_ATTRIBUTE UpcaseAttribute; + NTFS_UPCASE_TABLE UpcaseTable; + BOOLEAN Error = FALSE; + UCHAR Major, Minor; + BOOLEAN CorruptVolume; + + + // Lock the drive. + + if (!_drive->Lock()) + { + Message->Out("Cannot lock the drive. The volume is still in use."); + return FALSE; + } + + // Determine the volume version information. + // + QueryVolumeFlags(&CorruptVolume, &Major, &Minor); + + if (CorruptVolume) + { + Message->Out("The volume is corrupt. Run CHKDSK."); + return FALSE; + } + + if (Major > 3 || (Major = 3 && Minor > 1)) + { + Message->Out("Unsupported NTFS version."); + return FALSE; + } + + SetVersionNumber(Major, Minor); + + // Initialize and read the MFT, the Bitmap File, the Bitmap, and the + // Bad Cluster File. + // + if (!VolumeBitmap.Initialize(QueryVolumeSectors() / + ((ULONG)QueryClusterFactor()), + FALSE, _drive, QueryClusterFactor()) || + !MftFile.Initialize(_drive, + QueryMftStartingLcn(), + QueryClusterFactor(), + QueryFrsSize(), + QueryVolumeSectors(), + &VolumeBitmap, + NULL)) + { + Message->Out("Out of memory."); + return FALSE; + } + + if (!MftFile.Read()) + { + //DebugPrint("NTFS_SA::RecoverFile: Cannot read MFT.\n"); + Message->Out("The volume is corrupt. Run CHKDSK."); + return FALSE; + } + + // Get the upcase table. + // + if (!UpcaseFile.Initialize(MftFile.GetMasterFileTable()) || + !UpcaseFile.Read() || + !UpcaseFile.QueryAttribute(&UpcaseAttribute, &Error, $DATA) || + !UpcaseTable.Initialize(&UpcaseAttribute)) + { + + //DebugPrint("UNTFS RecoverFile:Can't get the upcase table.\n"); + + Message->Out("The volume is corrupt. Run CHKDSK."); + return FALSE; + } + + MftFile.SetUpcaseTable((PNTFS_UPCASE_TABLE)&UpcaseTable); + MftFile.GetMasterFileTable()->SetUpcaseTable((PNTFS_UPCASE_TABLE)&UpcaseTable); + + + // Initialize the Bitmap file and the Bad Cluster file, and + // read the volume bitmap. + // + if (!BitmapFile.Initialize(MftFile.GetMasterFileTable()) || + !BadClusterFile.Initialize(MftFile.GetMasterFileTable())) + { + Message->Out("Out of memory."); + return FALSE; + } + + if (!BitmapFile.Read() || + !BitmapFile.QueryAttribute(&BitmapAttribute, &Error, $DATA) || + !VolumeBitmap.Read(&BitmapAttribute) || + !BadClusterFile.Read()) + { + + Message->Out("The volume is corrupt. Run CHKDSK."); + return FALSE; + } + + NUMBER_SET BadClusterList; + if (!BadClusterList.Initialize()) + { + return FALSE; + } + + + if (!MarkInFreeSpace(MftFile.GetMasterFileTable(), firstPhysicalDriveSector, lastPhysicalDriveSector, &BadClusterList, &BadClusterFile, Message)) + { + return FALSE; + } + + + if (BadClusterList.QueryCardinality() != 0) + { + // If any bad clusters were found, we need to flush the bad cluster + // file and the MFT and write the bitmap. If no bad clusters were + // found, then these structures will be unchanged. + + Message->Out("Adding ", BadClusterList.QueryCardinality().GetLowPart(), " clusters to the Bad Clusters File..."); + + if (BadClusterFile.Add(&BadClusterList)) + { + if (!BadClusterFile.Flush(&VolumeBitmap) || + !MftFile.Flush() || + !VolumeBitmap.Write(&BitmapAttribute, &VolumeBitmap)) + { + Message->Out("Insufficient disk space to record bad clusters."); + return FALSE; + } + } + else + { + Message->Out("Insufficient disk space to record bad clusters."); + return FALSE; + } + } + else + { + Message->Out("No clusters to add to the Bad Clusters File."); + } + + return TRUE; +} + + +BOOLEAN +NTFS_SA::MarkInFreeSpace( + IN OUT PNTFS_MASTER_FILE_TABLE Mft, + IN __int64 firstPhysicalDriveSector, + IN __int64 lastPhysicalDriveSector, + IN OUT PNUMBER_SET BadClusters, + IN PNTFS_BAD_CLUSTER_FILE BadClusterFile, + IN OUT PMESSAGE Message +) +/*++ + +Routine Description: + + This routine verifies all of the unused clusters on the disk. + It adds any that are bad to the given bad cluster list. + +Arguments: + + Mft - Supplies the master file table. + BadClusters - Supplies the current list of bad clusters. + Message - Supplies an outlet for messages. + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + BIG_INT firstPhysicalDriveSectorToMark = (unsigned __int64)firstPhysicalDriveSector; + BIG_INT lastPhysicalDriveSectorToMark = (unsigned __int64)lastPhysicalDriveSector; + + PLOG_IO_DP_DRIVE drive; + PNTFS_BITMAP bitmap; + BIG_INT i; + //ULONG percent_done; + //BIG_INT checked, total_to_check; + ULONG cluster_factor; + + Message->Out("Scanning free space..."); + + drive = Mft->GetDataAttribute()->GetDrive(); + + BIG_INT firstDriveSector = drive->QueryHiddenSectors(); + BIG_INT lastDriveSector = firstDriveSector + drive->QuerySectors() - 1; + + Message->Out("First volume sector: ", firstDriveSector.GetQuadPart()); + Message->Out("Last volume sector: ", lastDriveSector.GetQuadPart()); + + + bitmap = Mft->GetVolumeBitmap(); + cluster_factor = Mft->QueryClusterFactor(); + //max_len = bitmap->QuerySize() / 20 + 1; + //total_to_check = bitmap->QueryFreeClusters(); + //checked = 0; + + //percent_done = 0; + + BIG_INT skippedAlreadyBadClusters = 0; + BIG_INT skippedInUseClusters = 0; + BIG_INT markedClusters = 0; + for (i = 0; i < bitmap->QuerySize(); i += 1) + { + BIG_INT clusterNumber = i; + BIG_INT firstVolumeSectorOfCluster = clusterNumber * cluster_factor; + BIG_INT lastVolumeSectorOfCluster = firstVolumeSectorOfCluster + cluster_factor - 1; + + BIG_INT firstDriveSectorOfCluster = firstDriveSector + firstVolumeSectorOfCluster; + BIG_INT lastDriveSectorOfCluster = firstDriveSector + lastVolumeSectorOfCluster; + + if (lastDriveSectorOfCluster < firstPhysicalDriveSectorToMark) continue; //current cluster before selection + if (firstDriveSectorOfCluster > lastPhysicalDriveSectorToMark) break; //current cluster after selection + + if (BadClusterFile->IsInList(clusterNumber)) + { + skippedAlreadyBadClusters += (1); + continue; + } + + if (!bitmap->IsFree(clusterNumber, 1)) + { + skippedInUseClusters += (1); + continue; + } + + // Add the bad clusters to the bad cluster list. + if (!BadClusters->Add(clusterNumber, 1)) + { + Message->Out("An unspecified error occurred."); + return FALSE; + } + + // Mark the bad clusters as allocated in the bitmap. + bitmap->SetAllocated(clusterNumber, 1); + + markedClusters += (1); + + //checked += (1); + + //if (100 * checked / total_to_check > percent_done) + //{ + // percent_done = (100 * checked / total_to_check).GetLowPart(); + //} + } + + Message->Out("The number of clusters skipped since they already marked bad: ", skippedAlreadyBadClusters.GetQuadPart()); + Message->Out("The number of clusters skipped since they are in use: ", skippedInUseClusters.GetQuadPart()); + Message->Out("The number of selected clusters: ",markedClusters.GetQuadPart()); + + //percent_done = 100; + + return TRUE; +} + diff --git a/untfs/src/ntfsvol.cxx b/untfs/src/ntfsvol.cxx new file mode 100644 index 0000000..6dedf40 --- /dev/null +++ b/untfs/src/ntfsvol.cxx @@ -0,0 +1,150 @@ +#include "stdafx.h" + + + + + + +#include "ulib.hxx" +#include "ntfsvol.hxx" + +#include "bitfrs.hxx" +#include "mft.hxx" +#include "upfile.hxx" +#include "upcase.hxx" + +#include "message.hxx" +#include "mftfile.hxx" + +#include "wstring.hxx" + + +DEFINE_CONSTRUCTOR( NTFS_VOL, VOL_LIODPDRV ); + + +VOID +NTFS_VOL::Construct ( + ) + +/*++ + +Routine Description: + + Constructor for NTFS_VOL. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + // unreferenced parameters + (void)(this); +} + + +VOID +NTFS_VOL::Destroy( + ) +/*++ + +Routine Description: + + This routine returns a NTFS_VOL object to its initial state. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + // unreferenced parameters + (void)(this); +} + + +NTFS_VOL::~NTFS_VOL( + ) +/*++ + +Routine Description: + + Destructor for NTFS_VOL. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + +FORMAT_ERROR_CODE +NTFS_VOL::Initialize( + IN PCWSTRING NtDriveName, + IN OUT PMESSAGE Message + ) +/*++ + +Routine Description: + + This routine initializes a NTFS_VOL object. + +Arguments: + + NtDriveName - Supplies the drive path for the volume. + Message - Supplies an outlet for messages. + ExclusiveWrite - Supplies whether or not the drive should be + opened for exclusive write. + FormatMedia - Supplies whether or not to format the media. + MediaType - Supplies the type of media to format to. + ForceDismount - Supplies whether the volume should be dismounted + and locked + +Return Value: + + FALSE - Failure. + TRUE - Success. + +--*/ +{ + MESSAGE msg; + FORMAT_ERROR_CODE errcode; + + Destroy(); + + errcode = VOL_LIODPDRV::Initialize(NtDriveName, &_ntfssa, Message); + + if (errcode != NoError) + return errcode; + + if (!Message) + { + Message = &msg; + } + + if (!_ntfssa.Initialize(this, Message)) { + return GeneralError; + } + + if (!_ntfssa.Read(Message)) { + return GeneralError; + } + + return NoError; +} + + diff --git a/untfs/src/untfs.cxx b/untfs/src/untfs.cxx new file mode 100644 index 0000000..b95ae0a --- /dev/null +++ b/untfs/src/untfs.cxx @@ -0,0 +1,107 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + untfs.cxx + +Abstract: + + This module contains run-time, global support for the + NTFS IFS Utilities library (UNTFS). This support includes: + + - creation of CLASS_DESCRIPTORs + - Global objects +--*/ + + +#include "ulib.hxx" +#include "untfs.hxx" + + +DECLARE_CLASS( NTFS_ATTRIBUTE ); +DECLARE_CLASS( NTFS_ATTRIBUTE_LIST ); +DECLARE_CLASS( NTFS_ATTRIBUTE_RECORD ); +DECLARE_CLASS( NTFS_BAD_CLUSTER_FILE ); +DECLARE_CLASS( NTFS_BITMAP_FILE ); +DECLARE_CLASS( NTFS_CLUSTER_RUN ); +DECLARE_CLASS( NTFS_EXTENT ); +DECLARE_CLASS( NTFS_EXTENT_LIST ); +DECLARE_CLASS( NTFS_FILE_RECORD_SEGMENT ); +DECLARE_CLASS( NTFS_FRS_STRUCTURE ); +DECLARE_CLASS( NTFS_INDEX_BUFFER ); +DECLARE_CLASS( NTFS_INDEX_ROOT ); +DECLARE_CLASS( NTFS_INDEX_TREE ); +DECLARE_CLASS( NTFS_MASTER_FILE_TABLE ); +DECLARE_CLASS( NTFS_MFT_FILE ); +DECLARE_CLASS( NTFS_REFLECTED_MASTER_FILE_TABLE ); +DECLARE_CLASS( NTFS_BITMAP ); +DECLARE_CLASS( NTFS_UPCASE_FILE ); +DECLARE_CLASS( NTFS_UPCASE_TABLE ); +DECLARE_CLASS( NTFS_VOL ); +DECLARE_CLASS( NTFS_SA ); + +BOOLEAN +UntfsDefineClassDescriptors( + ) +{ + if( + DEFINE_CLASS_DESCRIPTOR( NTFS_ATTRIBUTE ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_ATTRIBUTE_LIST ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_ATTRIBUTE_RECORD ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_BAD_CLUSTER_FILE ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_BITMAP_FILE ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_CLUSTER_RUN ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_EXTENT ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_EXTENT_LIST ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_FILE_RECORD_SEGMENT ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_FRS_STRUCTURE ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_INDEX_BUFFER ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_INDEX_ROOT ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_INDEX_TREE ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_MASTER_FILE_TABLE ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_MFT_FILE ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_REFLECTED_MASTER_FILE_TABLE ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_UPCASE_FILE ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_UPCASE_TABLE ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_VOL ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_SA ) && + DEFINE_CLASS_DESCRIPTOR( NTFS_BITMAP ) ) { + + return TRUE; + + } else { + + DebugPrint( "Could not initialize class descriptors!"); + return FALSE; + } +} + +BOOLEAN +UntfsUndefineClassDescriptors( + ) +{ + UNDEFINE_CLASS_DESCRIPTOR( NTFS_ATTRIBUTE ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_ATTRIBUTE_LIST ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_ATTRIBUTE_RECORD ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_BAD_CLUSTER_FILE ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_BITMAP_FILE ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_CLUSTER_RUN ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_EXTENT ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_EXTENT_LIST ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_FILE_RECORD_SEGMENT ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_FRS_STRUCTURE ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_INDEX_BUFFER ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_INDEX_ROOT ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_INDEX_TREE ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_MASTER_FILE_TABLE ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_MFT_FILE ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_REFLECTED_MASTER_FILE_TABLE ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_UPCASE_FILE ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_UPCASE_TABLE ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_VOL ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_SA ); + UNDEFINE_CLASS_DESCRIPTOR( NTFS_BITMAP ); + return TRUE; +} diff --git a/untfs/src/upcase.cxx b/untfs/src/upcase.cxx new file mode 100644 index 0000000..b690336 --- /dev/null +++ b/untfs/src/upcase.cxx @@ -0,0 +1,309 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + upcase.cxx + +Abstract: + + This module contains the member function definitions for the + NTFS_UPCASE_TABLE class. This class models the upcase table + stored on an NTFS volume, which is used to upper-case characters + in attribute and file names for comparison. + + +--*/ + + + +#include "ulib.hxx" + +#include "untfs.hxx" + +#include "attrib.hxx" +#include "ntfsbit.hxx" +#include "upcase.hxx" + +extern "C" { +#include +} + + +DEFINE_CONSTRUCTOR( NTFS_UPCASE_TABLE, OBJECT ); + + +NTFS_UPCASE_TABLE::~NTFS_UPCASE_TABLE( + ) +/*++ + +Routine Description: + + This method is the destructor for the class. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + Destroy(); +} + + +VOID +NTFS_UPCASE_TABLE::Construct( + ) +/*++ + +routine Description: + + This method is the helper function for object construction. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + _Data = NULL; + _Length = 0; +} + +VOID +NTFS_UPCASE_TABLE::Destroy( + ) +/*++ + +Routine Description: + + This method cleans up the object in preparation for destruction + or reinitialization. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ + FREE( _Data ); + _Length = 0; +} + + + + +BOOLEAN +NTFS_UPCASE_TABLE::Initialize( + IN PNTFS_ATTRIBUTE Attribute + ) +/*++ + +Routine Description: + + This method initializes the object based on the value of + an NTFS Attribute. + +Arguments: + + Attribute -- Supplies the attribute whose value is the + upcase table. + + +Return Value: + + TRUE upon successful completion. + +--*/ +{ + ULONG BytesInValue, BytesRead; + + + DebugPtrAssert( Attribute ); + + Destroy(); + + // Perform validity checks on the attribute; it must be + // small enough that the length fits in a ULONG, and + // its length must be a multiple of sizeof(WCHAR). + + if( Attribute->QueryValueLength().GetHighPart() != 0 ) { + + DebugAbort( "Upcase table is impossibly large.\n" ); + return FALSE; + } + + BytesInValue = Attribute->QueryValueLength().GetLowPart(); + + if( BytesInValue % sizeof(WCHAR) != 0 ) { + + DebugAbort( "Upcase table is an odd number of bytes.\n" ); + return FALSE; + } + + // Allocate the buffer for the upcase data and read the attribute + // value into it. + + if( (_Data = (PWCHAR)MALLOC( BytesInValue )) == NULL || + !Attribute->Read( _Data, + 0, + BytesInValue, + &BytesRead ) || + BytesRead != BytesInValue ) { + + Destroy(); + return FALSE; + } + + // _Length is the number of WCHAR's in the table. + + _Length = BytesInValue / sizeof(WCHAR); + + return TRUE; +} + + + +LONG +NtfsUpcaseCompare( + IN PCWSTR LeftName, + IN ULONG LeftNameLength, + IN PCWSTR RightName, + IN ULONG RightNameLength, + IN PCNTFS_UPCASE_TABLE UpcaseTable, + IN BOOLEAN CaseSensitive + ) +/*++ + +Routine Description: + + This function compares two NTFS names. + +Arguments: + + LeftName -- Supplies the left-hand operand of the comparison. + LeftNameLength -- Supplies the length in characters of LeftName. + RightName -- Supplies the Right-hand operand of the comparison. + RightNameLength -- Supplies the length in characters of RightName. + UpcaseTable -- Supplies the volume upcase table. + CaseSensitive -- Supplies a flag which, if TRUE, indicates that + the comparison is case-sensitive. + +Return Value: + + <0 if LeftName is less than RightName. + 0 if the names are equal. + >0 if LeftName is greater than RightName. + +Notes: + + UpcaseTable may be NULL if either or both names are zero-length, + or if the names are exactly identical. Otherwise, it must point + at an initialized Upcase Table object. + + If the comparison is case-sensitive, then the names are first + compared case-insensitive. If that comparison evaluates to equality, + then they are compared case-sensitive. Attribute names and + non-DOS, non-NTFS file names are compared case-sensitive. + +--*/ +{ + ULONG ShorterLength, i; + LONG Result; + + // First, if both names have zero length, then they're equal. + // + if( LeftNameLength == 0 && RightNameLength == 0 ) { + + return 0; + } + + // At least one has a non-zero-length name. If the other has + // a zero-length name, it's the lesser of the two. + // + if( LeftNameLength == 0 ) { + + return -1; + } + + if( RightNameLength == 0 ) { + + return 1; + } + + // Both have non-zero length names. If they have the same length, + // do a quick memcmp to see if they're identical. + // + if( LeftNameLength == RightNameLength && + memcmp( LeftName, RightName, LeftNameLength * sizeof(WCHAR) ) == 0 ) { + + return 0; + } + + // Perform case-insensitive comparison. This requires + // UpcaseTable to be valid. + // + DebugPtrAssert( UpcaseTable ); + + ShorterLength = MIN( LeftNameLength, RightNameLength ); + + for( i = 0; i < ShorterLength; i++ ) { + + Result = UpcaseTable->UpperCase( LeftName[i] ) - + UpcaseTable->UpperCase( RightName[i] ); + + if( Result != 0 ) { + + return Result; + } + } + + // The names are case-insensitive equal for the length of + // the shorter name; if they are of different lengths, the + // shorter is the lesser. + // + Result = LeftNameLength - RightNameLength; + + if( Result != 0 ) { + + return Result; + } + + // The names are equal except for case. If this is an + // case-sensitive comparison, perform a final comparison; + // otherwise, they're equal. + // + if( !CaseSensitive ) { + + return 0; + + } else { + + // We already know they're of the same length. + // + for( i = 0; i < LeftNameLength; i++ ) { + + Result = LeftName[i] - RightName[i]; + + if( Result != 0 ) { + + return Result; + } + } + + return 0; + } +} diff --git a/untfs/src/upfile.cxx b/untfs/src/upfile.cxx new file mode 100644 index 0000000..77d7c23 --- /dev/null +++ b/untfs/src/upfile.cxx @@ -0,0 +1,125 @@ +#include "stdafx.h" + +/*++ + +Module Name: + + upfile.cxx + +Abstract: + + This module contains the declarations for the NTFS_UPCASE_FILE + class, which models the upcase-table file for an NTFS volume. + This class' main purpose in life is to encapsulate the creation + of this file. + +--*/ + + + +#include "ulib.hxx" + +#include "untfs.hxx" + +#include "ntfsbit.hxx" +#include "drive.hxx" +#include "attrib.hxx" +#include "bitfrs.hxx" +#include "upfile.hxx" +#include "upcase.hxx" +#include "message.hxx" + + +DEFINE_CONSTRUCTOR( NTFS_UPCASE_FILE, + NTFS_FILE_RECORD_SEGMENT ); + + +NTFS_UPCASE_FILE::~NTFS_UPCASE_FILE( + ) +{ + Destroy(); +} + + +VOID +NTFS_UPCASE_FILE::Construct( + ) +/*++ + +Routine Description: + + Worker function for the construtor. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ +} + + +VOID +NTFS_UPCASE_FILE::Destroy( + ) +/*++ + +Routine Description: + + Clean up an NTFS_UPCASE_FILE object in preparation for + destruction or reinitialization. + +Arguments: + + None. + +Return Value: + + None. + +--*/ +{ +} + + + + +BOOLEAN +NTFS_UPCASE_FILE::Initialize( + IN OUT PNTFS_MASTER_FILE_TABLE Mft + ) +/*++ + +Routine Description: + + This method initializes an Upcase File object. The only special + knowledge that it adds to the File Record Segment initialization + is the location within the Master File Table of the Upcase table + file. + +Arguments: + + Mft -- Supplies the volume MasterFile Table. + UpcaseTable -- Supplies the volume upcase table. + +Return Value: + + TRUE upon successful completion + +Notes: + + This class is reinitializable. + + +--*/ +{ + Destroy(); + + return( NTFS_FILE_RECORD_SEGMENT::Initialize( UPCASE_TABLE_NUMBER, + Mft ) ); +} +