1 /* 2 * ReactOS kernel 3 * Copyright (C) 2002,2003 ReactOS Team 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 18 * 19 * COPYRIGHT: See COPYING in the top level directory 20 * PROJECT: ReactOS kernel 21 * FILE: drivers/filesystem/ntfs/attrib.c 22 * PURPOSE: NTFS filesystem driver 23 * PROGRAMMERS: Eric Kohl 24 * Valentin Verkhovsky 25 * Hervé Poussineau (hpoussin@reactos.org) 26 * Pierre Schweitzer (pierre@reactos.org) 27 */ 28 29 /* INCLUDES *****************************************************************/ 30 31 #include "ntfs.h" 32 #include <ntintsafe.h> 33 34 #define NDEBUG 35 #include <debug.h> 36 37 /* FUNCTIONS ****************************************************************/ 38 39 /** 40 * @name AddBitmap 41 * @implemented 42 * 43 * Adds a $BITMAP attribute to a given FileRecord. 44 * 45 * @param Vcb 46 * Pointer to an NTFS_VCB for the destination volume. 47 * 48 * @param FileRecord 49 * Pointer to a complete file record to add the attribute to. 50 * 51 * @param AttributeAddress 52 * Pointer to the region of memory that will receive the $INDEX_ALLOCATION attribute. 53 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). 54 * 55 * @param Name 56 * Pointer to a string of 16-bit Unicode characters naming the attribute. Most often L"$I30". 57 * 58 * @param NameLength 59 * The number of wide-characters in the name. L"$I30" Would use 4 here. 60 * 61 * @return 62 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end 63 * of the given file record, or if the file record isn't large enough for the attribute. 64 * 65 * @remarks 66 * Only adding the attribute to the end of the file record is supported; AttributeAddress must 67 * be of type AttributeEnd. 68 * This could be improved by adding an $ATTRIBUTE_LIST to the file record if there's not enough space. 69 * 70 */ 71 NTSTATUS 72 AddBitmap(PNTFS_VCB Vcb, 73 PFILE_RECORD_HEADER FileRecord, 74 PNTFS_ATTR_RECORD AttributeAddress, 75 PCWSTR Name, 76 USHORT NameLength) 77 { 78 ULONG AttributeLength; 79 // Calculate the header length 80 ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR); 81 ULONG FileRecordEnd = AttributeAddress->Length; 82 ULONG NameOffset; 83 ULONG ValueOffset; 84 // We'll start out with 8 bytes of bitmap data 85 ULONG ValueLength = 8; 86 ULONG BytesAvailable; 87 88 if (AttributeAddress->Type != AttributeEnd) 89 { 90 DPRINT1("FIXME: Can only add $BITMAP attribute to the end of a file record.\n"); 91 return STATUS_NOT_IMPLEMENTED; 92 } 93 94 NameOffset = ResidentHeaderLength; 95 96 // Calculate ValueOffset, which will be aligned to a 4-byte boundary 97 ValueOffset = ALIGN_UP_BY(NameOffset + (sizeof(WCHAR) * NameLength), VALUE_OFFSET_ALIGNMENT); 98 99 // Calculate length of attribute 100 AttributeLength = ValueOffset + ValueLength; 101 AttributeLength = ALIGN_UP_BY(AttributeLength, ATTR_RECORD_ALIGNMENT); 102 103 // Make sure the file record is large enough for the new attribute 104 BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse; 105 if (BytesAvailable < AttributeLength) 106 { 107 DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n"); 108 return STATUS_NOT_IMPLEMENTED; 109 } 110 111 // Set Attribute fields 112 RtlZeroMemory(AttributeAddress, AttributeLength); 113 114 AttributeAddress->Type = AttributeBitmap; 115 AttributeAddress->Length = AttributeLength; 116 AttributeAddress->NameLength = NameLength; 117 AttributeAddress->NameOffset = NameOffset; 118 AttributeAddress->Instance = FileRecord->NextAttributeNumber++; 119 120 AttributeAddress->Resident.ValueLength = ValueLength; 121 AttributeAddress->Resident.ValueOffset = ValueOffset; 122 123 // Set the name 124 RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR)); 125 126 // move the attribute-end and file-record-end markers to the end of the file record 127 AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length); 128 SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd); 129 130 return STATUS_SUCCESS; 131 } 132 133 /** 134 * @name AddData 135 * @implemented 136 * 137 * Adds a $DATA attribute to a given FileRecord. 138 * 139 * @param FileRecord 140 * Pointer to a complete file record to add the attribute to. Caller is responsible for 141 * ensuring FileRecord is large enough to contain $DATA. 142 * 143 * @param AttributeAddress 144 * Pointer to the region of memory that will receive the $DATA attribute. 145 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). 146 * 147 * @return 148 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end 149 * of the given file record. 150 * 151 * @remarks 152 * Only adding the attribute to the end of the file record is supported; AttributeAddress must 153 * be of type AttributeEnd. 154 * As it's implemented, this function is only intended to assist in creating new file records. It 155 * could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST. 156 * It's the caller's responsibility to ensure the given file record has enough memory allocated 157 * for the attribute. 158 */ 159 NTSTATUS 160 AddData(PFILE_RECORD_HEADER FileRecord, 161 PNTFS_ATTR_RECORD AttributeAddress) 162 { 163 ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR); 164 ULONG FileRecordEnd = AttributeAddress->Length; 165 166 if (AttributeAddress->Type != AttributeEnd) 167 { 168 DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n"); 169 return STATUS_NOT_IMPLEMENTED; 170 } 171 172 AttributeAddress->Type = AttributeData; 173 AttributeAddress->Length = ResidentHeaderLength; 174 AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT); 175 AttributeAddress->Resident.ValueLength = 0; 176 AttributeAddress->Resident.ValueOffset = ResidentHeaderLength; 177 178 // for unnamed $DATA attributes, NameOffset equals header length 179 AttributeAddress->NameOffset = ResidentHeaderLength; 180 AttributeAddress->Instance = FileRecord->NextAttributeNumber++; 181 182 // move the attribute-end and file-record-end markers to the end of the file record 183 AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length); 184 SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd); 185 186 return STATUS_SUCCESS; 187 } 188 189 /** 190 * @name AddFileName 191 * @implemented 192 * 193 * Adds a $FILE_NAME attribute to a given FileRecord. 194 * 195 * @param FileRecord 196 * Pointer to a complete file record to add the attribute to. Caller is responsible for 197 * ensuring FileRecord is large enough to contain $FILE_NAME. 198 * 199 * @param AttributeAddress 200 * Pointer to the region of memory that will receive the $FILE_NAME attribute. 201 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). 202 * 203 * @param DeviceExt 204 * Points to the target disk's DEVICE_EXTENSION. 205 * 206 * @param FileObject 207 * Pointer to the FILE_OBJECT which represents the new name. 208 * This parameter is used to determine the filename and parent directory. 209 * 210 * @param CaseSensitive 211 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE 212 * if an application opened the file with the FILE_FLAG_POSIX_SEMANTICS flag. 213 * 214 * @param ParentMftIndex 215 * Pointer to a ULONGLONG which will receive the index of the parent directory. 216 * 217 * @return 218 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end 219 * of the given file record. 220 * 221 * @remarks 222 * Only adding the attribute to the end of the file record is supported; AttributeAddress must 223 * be of type AttributeEnd. 224 * As it's implemented, this function is only intended to assist in creating new file records. It 225 * could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST. 226 * It's the caller's responsibility to ensure the given file record has enough memory allocated 227 * for the attribute. 228 */ 229 NTSTATUS 230 AddFileName(PFILE_RECORD_HEADER FileRecord, 231 PNTFS_ATTR_RECORD AttributeAddress, 232 PDEVICE_EXTENSION DeviceExt, 233 PFILE_OBJECT FileObject, 234 BOOLEAN CaseSensitive, 235 PULONGLONG ParentMftIndex) 236 { 237 ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR); 238 PFILENAME_ATTRIBUTE FileNameAttribute; 239 LARGE_INTEGER SystemTime; 240 ULONG FileRecordEnd = AttributeAddress->Length; 241 ULONGLONG CurrentMFTIndex = NTFS_FILE_ROOT; 242 UNICODE_STRING Current, Remaining, FilenameNoPath; 243 NTSTATUS Status = STATUS_SUCCESS; 244 ULONG FirstEntry; 245 246 if (AttributeAddress->Type != AttributeEnd) 247 { 248 DPRINT1("FIXME: Can only add $FILE_NAME attribute to the end of a file record.\n"); 249 return STATUS_NOT_IMPLEMENTED; 250 } 251 252 AttributeAddress->Type = AttributeFileName; 253 AttributeAddress->Instance = FileRecord->NextAttributeNumber++; 254 255 FileNameAttribute = (PFILENAME_ATTRIBUTE)((LONG_PTR)AttributeAddress + ResidentHeaderLength); 256 257 // set timestamps 258 KeQuerySystemTime(&SystemTime); 259 FileNameAttribute->CreationTime = SystemTime.QuadPart; 260 FileNameAttribute->ChangeTime = SystemTime.QuadPart; 261 FileNameAttribute->LastWriteTime = SystemTime.QuadPart; 262 FileNameAttribute->LastAccessTime = SystemTime.QuadPart; 263 264 // Is this a directory? 265 if(FileRecord->Flags & FRH_DIRECTORY) 266 FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_DIRECTORY; 267 else 268 FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_ARCHIVE; 269 270 // we need to extract the filename from the path 271 DPRINT1("Pathname: %wZ\n", &FileObject->FileName); 272 273 FsRtlDissectName(FileObject->FileName, &Current, &Remaining); 274 275 FilenameNoPath.Buffer = Current.Buffer; 276 FilenameNoPath.MaximumLength = FilenameNoPath.Length = Current.Length; 277 278 while (Current.Length != 0) 279 { 280 DPRINT1("Current: %wZ\n", &Current); 281 282 if (Remaining.Length != 0) 283 { 284 FilenameNoPath.Buffer = Remaining.Buffer; 285 FilenameNoPath.Length = FilenameNoPath.MaximumLength = Remaining.Length; 286 } 287 288 FirstEntry = 0; 289 Status = NtfsFindMftRecord(DeviceExt, 290 CurrentMFTIndex, 291 &Current, 292 &FirstEntry, 293 FALSE, 294 CaseSensitive, 295 &CurrentMFTIndex); 296 if (!NT_SUCCESS(Status)) 297 break; 298 299 if (Remaining.Length == 0 ) 300 { 301 if (Current.Length != 0) 302 { 303 FilenameNoPath.Buffer = Current.Buffer; 304 FilenameNoPath.Length = FilenameNoPath.MaximumLength = Current.Length; 305 } 306 break; 307 } 308 309 FsRtlDissectName(Remaining, &Current, &Remaining); 310 } 311 312 DPRINT1("MFT Index of parent: %I64u\n", CurrentMFTIndex); 313 314 // set reference to parent directory 315 FileNameAttribute->DirectoryFileReferenceNumber = CurrentMFTIndex; 316 *ParentMftIndex = CurrentMFTIndex; 317 318 DPRINT1("SequenceNumber: 0x%02x\n", FileRecord->SequenceNumber); 319 320 // The highest 2 bytes should be the sequence number, unless the parent happens to be root 321 if (CurrentMFTIndex == NTFS_FILE_ROOT) 322 FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)NTFS_FILE_ROOT << 48; 323 else 324 FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)FileRecord->SequenceNumber << 48; 325 326 DPRINT1("FileNameAttribute->DirectoryFileReferenceNumber: 0x%016I64x\n", FileNameAttribute->DirectoryFileReferenceNumber); 327 328 FileNameAttribute->NameLength = FilenameNoPath.Length / sizeof(WCHAR); 329 RtlCopyMemory(FileNameAttribute->Name, FilenameNoPath.Buffer, FilenameNoPath.Length); 330 331 // For now, we're emulating the way Windows behaves when 8.3 name generation is disabled 332 // TODO: add DOS Filename as needed 333 if (!CaseSensitive && RtlIsNameLegalDOS8Dot3(&FilenameNoPath, NULL, NULL)) 334 FileNameAttribute->NameType = NTFS_FILE_NAME_WIN32_AND_DOS; 335 else 336 FileNameAttribute->NameType = NTFS_FILE_NAME_POSIX; 337 338 FileRecord->LinkCount++; 339 340 AttributeAddress->Length = ResidentHeaderLength + 341 FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length; 342 AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT); 343 344 AttributeAddress->Resident.ValueLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length; 345 AttributeAddress->Resident.ValueOffset = ResidentHeaderLength; 346 AttributeAddress->Resident.Flags = RA_INDEXED; 347 348 // move the attribute-end and file-record-end markers to the end of the file record 349 AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length); 350 SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd); 351 352 return Status; 353 } 354 355 /** 356 * @name AddIndexAllocation 357 * @implemented 358 * 359 * Adds an $INDEX_ALLOCATION attribute to a given FileRecord. 360 * 361 * @param Vcb 362 * Pointer to an NTFS_VCB for the destination volume. 363 * 364 * @param FileRecord 365 * Pointer to a complete file record to add the attribute to. 366 * 367 * @param AttributeAddress 368 * Pointer to the region of memory that will receive the $INDEX_ALLOCATION attribute. 369 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). 370 * 371 * @param Name 372 * Pointer to a string of 16-bit Unicode characters naming the attribute. Most often, this will be L"$I30". 373 * 374 * @param NameLength 375 * The number of wide-characters in the name. L"$I30" Would use 4 here. 376 * 377 * @return 378 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end 379 * of the given file record, or if the file record isn't large enough for the attribute. 380 * 381 * @remarks 382 * Only adding the attribute to the end of the file record is supported; AttributeAddress must 383 * be of type AttributeEnd. 384 * This could be improved by adding an $ATTRIBUTE_LIST to the file record if there's not enough space. 385 * 386 */ 387 NTSTATUS 388 AddIndexAllocation(PNTFS_VCB Vcb, 389 PFILE_RECORD_HEADER FileRecord, 390 PNTFS_ATTR_RECORD AttributeAddress, 391 PCWSTR Name, 392 USHORT NameLength) 393 { 394 ULONG RecordLength; 395 ULONG FileRecordEnd; 396 ULONG NameOffset; 397 ULONG DataRunOffset; 398 ULONG BytesAvailable; 399 400 if (AttributeAddress->Type != AttributeEnd) 401 { 402 DPRINT1("FIXME: Can only add $INDEX_ALLOCATION attribute to the end of a file record.\n"); 403 return STATUS_NOT_IMPLEMENTED; 404 } 405 406 // Calculate the name offset 407 NameOffset = FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize); 408 409 // Calculate the offset to the first data run 410 DataRunOffset = (sizeof(WCHAR) * NameLength) + NameOffset; 411 // The data run offset must be aligned to a 4-byte boundary 412 DataRunOffset = ALIGN_UP_BY(DataRunOffset, DATA_RUN_ALIGNMENT); 413 414 // Calculate the length of the new attribute; the empty data run will consist of a single byte 415 RecordLength = DataRunOffset + 1; 416 417 // The size of the attribute itself must be aligned to an 8 - byte boundary 418 RecordLength = ALIGN_UP_BY(RecordLength, ATTR_RECORD_ALIGNMENT); 419 420 // Back up the last 4-bytes of the file record (even though this value doesn't matter) 421 FileRecordEnd = AttributeAddress->Length; 422 423 // Make sure the file record can contain the new attribute 424 BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse; 425 if (BytesAvailable < RecordLength) 426 { 427 DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n"); 428 return STATUS_NOT_IMPLEMENTED; 429 } 430 431 // Set fields of attribute header 432 RtlZeroMemory(AttributeAddress, RecordLength); 433 434 AttributeAddress->Type = AttributeIndexAllocation; 435 AttributeAddress->Length = RecordLength; 436 AttributeAddress->IsNonResident = TRUE; 437 AttributeAddress->NameLength = NameLength; 438 AttributeAddress->NameOffset = NameOffset; 439 AttributeAddress->Instance = FileRecord->NextAttributeNumber++; 440 441 AttributeAddress->NonResident.MappingPairsOffset = DataRunOffset; 442 AttributeAddress->NonResident.HighestVCN = (LONGLONG)-1; 443 444 // Set the name 445 RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR)); 446 447 // move the attribute-end and file-record-end markers to the end of the file record 448 AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length); 449 SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd); 450 451 return STATUS_SUCCESS; 452 } 453 454 /** 455 * @name AddIndexRoot 456 * @implemented 457 * 458 * Adds an $INDEX_ROOT attribute to a given FileRecord. 459 * 460 * @param Vcb 461 * Pointer to an NTFS_VCB for the destination volume. 462 * 463 * @param FileRecord 464 * Pointer to a complete file record to add the attribute to. Caller is responsible for 465 * ensuring FileRecord is large enough to contain $INDEX_ROOT. 466 * 467 * @param AttributeAddress 468 * Pointer to the region of memory that will receive the $INDEX_ROOT attribute. 469 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). 470 * 471 * @param NewIndexRoot 472 * Pointer to an INDEX_ROOT_ATTRIBUTE containing the index root that will be copied to the new attribute. 473 * 474 * @param RootLength 475 * The length of NewIndexRoot, in bytes. 476 * 477 * @param Name 478 * Pointer to a string of 16-bit Unicode characters naming the attribute. Most often, this will be L"$I30". 479 * 480 * @param NameLength 481 * The number of wide-characters in the name. L"$I30" Would use 4 here. 482 * 483 * @return 484 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end 485 * of the given file record. 486 * 487 * @remarks 488 * This function is intended to assist in creating new folders. 489 * Only adding the attribute to the end of the file record is supported; AttributeAddress must 490 * be of type AttributeEnd. 491 * It's the caller's responsibility to ensure the given file record has enough memory allocated 492 * for the attribute, and this memory must have been zeroed. 493 */ 494 NTSTATUS 495 AddIndexRoot(PNTFS_VCB Vcb, 496 PFILE_RECORD_HEADER FileRecord, 497 PNTFS_ATTR_RECORD AttributeAddress, 498 PINDEX_ROOT_ATTRIBUTE NewIndexRoot, 499 ULONG RootLength, 500 PCWSTR Name, 501 USHORT NameLength) 502 { 503 ULONG AttributeLength; 504 // Calculate the header length 505 ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR); 506 // Back up the file record's final ULONG (even though it doesn't matter) 507 ULONG FileRecordEnd = AttributeAddress->Length; 508 ULONG NameOffset; 509 ULONG ValueOffset; 510 ULONG BytesAvailable; 511 512 if (AttributeAddress->Type != AttributeEnd) 513 { 514 DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n"); 515 return STATUS_NOT_IMPLEMENTED; 516 } 517 518 NameOffset = ResidentHeaderLength; 519 520 // Calculate ValueOffset, which will be aligned to a 4-byte boundary 521 ValueOffset = ALIGN_UP_BY(NameOffset + (sizeof(WCHAR) * NameLength), VALUE_OFFSET_ALIGNMENT); 522 523 // Calculate length of attribute 524 AttributeLength = ValueOffset + RootLength; 525 AttributeLength = ALIGN_UP_BY(AttributeLength, ATTR_RECORD_ALIGNMENT); 526 527 // Make sure the file record is large enough for the new attribute 528 BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse; 529 if (BytesAvailable < AttributeLength) 530 { 531 DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n"); 532 return STATUS_NOT_IMPLEMENTED; 533 } 534 535 // Set Attribute fields 536 RtlZeroMemory(AttributeAddress, AttributeLength); 537 538 AttributeAddress->Type = AttributeIndexRoot; 539 AttributeAddress->Length = AttributeLength; 540 AttributeAddress->NameLength = NameLength; 541 AttributeAddress->NameOffset = NameOffset; 542 AttributeAddress->Instance = FileRecord->NextAttributeNumber++; 543 544 AttributeAddress->Resident.ValueLength = RootLength; 545 AttributeAddress->Resident.ValueOffset = ValueOffset; 546 547 // Set the name 548 RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR)); 549 550 // Copy the index root attribute 551 RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + ValueOffset), NewIndexRoot, RootLength); 552 553 // move the attribute-end and file-record-end markers to the end of the file record 554 AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length); 555 SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd); 556 557 return STATUS_SUCCESS; 558 } 559 560 /** 561 * @name AddRun 562 * @implemented 563 * 564 * Adds a run of allocated clusters to a non-resident attribute. 565 * 566 * @param Vcb 567 * Pointer to an NTFS_VCB for the destination volume. 568 * 569 * @param AttrContext 570 * Pointer to an NTFS_ATTR_CONTEXT describing the destination attribute. 571 * 572 * @param AttrOffset 573 * Byte offset of the destination attribute relative to its file record. 574 * 575 * @param FileRecord 576 * Pointer to a complete copy of the file record containing the destination attribute. Must be at least 577 * Vcb->NtfsInfo.BytesPerFileRecord bytes long. 578 * 579 * @param NextAssignedCluster 580 * Logical cluster number of the start of the data run being added. 581 * 582 * @param RunLength 583 * How many clusters are in the data run being added. Can't be 0. 584 * 585 * @return 586 * STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute. 587 * STATUS_INSUFFICIENT_RESOURCES if ConvertDataRunsToLargeMCB() fails or if we fail to allocate a 588 * buffer for the new data runs. 589 * STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if FsRtlAddLargeMcbEntry() fails. 590 * STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails. 591 * STATUS_NOT_IMPLEMENTED if we need to migrate the attribute to an attribute list (TODO). 592 * 593 * @remarks 594 * Clusters should have been allocated previously with NtfsAllocateClusters(). 595 * 596 * 597 */ 598 NTSTATUS 599 AddRun(PNTFS_VCB Vcb, 600 PNTFS_ATTR_CONTEXT AttrContext, 601 ULONG AttrOffset, 602 PFILE_RECORD_HEADER FileRecord, 603 ULONGLONG NextAssignedCluster, 604 ULONG RunLength) 605 { 606 NTSTATUS Status; 607 int DataRunMaxLength; 608 PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); 609 ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length; 610 ULONGLONG NextVBN = 0; 611 612 PUCHAR RunBuffer; 613 ULONG RunBufferSize; 614 615 if (!AttrContext->pRecord->IsNonResident) 616 return STATUS_INVALID_PARAMETER; 617 618 if (AttrContext->pRecord->NonResident.AllocatedSize != 0) 619 NextVBN = AttrContext->pRecord->NonResident.HighestVCN + 1; 620 621 // Add newly-assigned clusters to mcb 622 _SEH2_TRY 623 { 624 if (!FsRtlAddLargeMcbEntry(&AttrContext->DataRunsMCB, 625 NextVBN, 626 NextAssignedCluster, 627 RunLength)) 628 { 629 ExRaiseStatus(STATUS_UNSUCCESSFUL); 630 } 631 } 632 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 633 { 634 DPRINT1("Failed to add LargeMcb Entry!\n"); 635 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 636 } 637 _SEH2_END; 638 639 RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); 640 if (!RunBuffer) 641 { 642 DPRINT1("ERROR: Couldn't allocate memory for data runs!\n"); 643 return STATUS_INSUFFICIENT_RESOURCES; 644 } 645 646 // Convert the map control block back to encoded data runs 647 ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize); 648 649 // Get the amount of free space between the start of the of the first data run and the attribute end 650 DataRunMaxLength = AttrContext->pRecord->Length - AttrContext->pRecord->NonResident.MappingPairsOffset; 651 652 // Do we need to extend the attribute (or convert to attribute list)? 653 if (DataRunMaxLength < RunBufferSize) 654 { 655 PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); 656 PNTFS_ATTR_RECORD NewRecord; 657 658 // Add free space at the end of the file record to DataRunMaxLength 659 DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse; 660 661 // Can we resize the attribute? 662 if (DataRunMaxLength < RunBufferSize) 663 { 664 DPRINT1("FIXME: Need to create attribute list! Max Data Run Length available: %d, RunBufferSize: %d\n", DataRunMaxLength, RunBufferSize); 665 ExFreePoolWithTag(RunBuffer, TAG_NTFS); 666 return STATUS_NOT_IMPLEMENTED; 667 } 668 669 // Are there more attributes after the one we're resizing? 670 if (NextAttribute->Type != AttributeEnd) 671 { 672 PNTFS_ATTR_RECORD FinalAttribute; 673 674 // Calculate where to move the trailing attributes 675 ULONG_PTR MoveTo = (ULONG_PTR)DestinationAttribute + AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize; 676 MoveTo = ALIGN_UP_BY(MoveTo, ATTR_RECORD_ALIGNMENT); 677 678 DPRINT1("Moving attribute(s) after this one starting with type 0x%lx\n", NextAttribute->Type); 679 680 // Move the trailing attributes; FinalAttribute will point to the end marker 681 FinalAttribute = MoveAttributes(Vcb, NextAttribute, NextAttributeOffset, MoveTo); 682 683 // set the file record end 684 SetFileRecordEnd(FileRecord, FinalAttribute, FILE_RECORD_END); 685 } 686 687 // calculate position of end markers 688 NextAttributeOffset = AttrOffset + AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize; 689 NextAttributeOffset = ALIGN_UP_BY(NextAttributeOffset, ATTR_RECORD_ALIGNMENT); 690 691 // Update the length of the destination attribute 692 DestinationAttribute->Length = NextAttributeOffset - AttrOffset; 693 694 // Create a new copy of the attribute record 695 NewRecord = ExAllocatePoolWithTag(NonPagedPool, DestinationAttribute->Length, TAG_NTFS); 696 RtlCopyMemory(NewRecord, AttrContext->pRecord, AttrContext->pRecord->Length); 697 NewRecord->Length = DestinationAttribute->Length; 698 699 // Free the old copy of the attribute record, which won't be large enough 700 ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS); 701 702 // Set the attribute context's record to the new copy 703 AttrContext->pRecord = NewRecord; 704 705 // if NextAttribute is the AttributeEnd marker 706 if (NextAttribute->Type == AttributeEnd) 707 { 708 // End the file record 709 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); 710 SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END); 711 } 712 } 713 714 // Update HighestVCN 715 DestinationAttribute->NonResident.HighestVCN = 716 AttrContext->pRecord->NonResident.HighestVCN = max(NextVBN - 1 + RunLength, 717 AttrContext->pRecord->NonResident.HighestVCN); 718 719 // Write data runs to destination attribute 720 RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 721 RunBuffer, 722 RunBufferSize); 723 724 // Update the attribute record in the attribute context 725 RtlCopyMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + AttrContext->pRecord->NonResident.MappingPairsOffset), 726 RunBuffer, 727 RunBufferSize); 728 729 // Update the file record 730 Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord); 731 732 ExFreePoolWithTag(RunBuffer, TAG_NTFS); 733 734 NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0); 735 736 return Status; 737 } 738 739 /** 740 * @name AddStandardInformation 741 * @implemented 742 * 743 * Adds a $STANDARD_INFORMATION attribute to a given FileRecord. 744 * 745 * @param FileRecord 746 * Pointer to a complete file record to add the attribute to. Caller is responsible for 747 * ensuring FileRecord is large enough to contain $STANDARD_INFORMATION. 748 * 749 * @param AttributeAddress 750 * Pointer to the region of memory that will receive the $STANDARD_INFORMATION attribute. 751 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). 752 * 753 * @return 754 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end 755 * of the given file record. 756 * 757 * @remarks 758 * Only adding the attribute to the end of the file record is supported; AttributeAddress must 759 * be of type AttributeEnd. 760 * As it's implemented, this function is only intended to assist in creating new file records. It 761 * could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST. 762 * It's the caller's responsibility to ensure the given file record has enough memory allocated 763 * for the attribute. 764 */ 765 NTSTATUS 766 AddStandardInformation(PFILE_RECORD_HEADER FileRecord, 767 PNTFS_ATTR_RECORD AttributeAddress) 768 { 769 ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR); 770 PSTANDARD_INFORMATION StandardInfo = (PSTANDARD_INFORMATION)((LONG_PTR)AttributeAddress + ResidentHeaderLength); 771 LARGE_INTEGER SystemTime; 772 ULONG FileRecordEnd = AttributeAddress->Length; 773 774 if (AttributeAddress->Type != AttributeEnd) 775 { 776 DPRINT1("FIXME: Can only add $STANDARD_INFORMATION attribute to the end of a file record.\n"); 777 return STATUS_NOT_IMPLEMENTED; 778 } 779 780 AttributeAddress->Type = AttributeStandardInformation; 781 AttributeAddress->Length = sizeof(STANDARD_INFORMATION) + ResidentHeaderLength; 782 AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT); 783 AttributeAddress->Resident.ValueLength = sizeof(STANDARD_INFORMATION); 784 AttributeAddress->Resident.ValueOffset = ResidentHeaderLength; 785 AttributeAddress->Instance = FileRecord->NextAttributeNumber++; 786 787 // set dates and times 788 KeQuerySystemTime(&SystemTime); 789 StandardInfo->CreationTime = SystemTime.QuadPart; 790 StandardInfo->ChangeTime = SystemTime.QuadPart; 791 StandardInfo->LastWriteTime = SystemTime.QuadPart; 792 StandardInfo->LastAccessTime = SystemTime.QuadPart; 793 StandardInfo->FileAttribute = NTFS_FILE_TYPE_ARCHIVE; 794 795 // move the attribute-end and file-record-end markers to the end of the file record 796 AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length); 797 SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd); 798 799 return STATUS_SUCCESS; 800 } 801 802 /** 803 * @name ConvertDataRunsToLargeMCB 804 * @implemented 805 * 806 * Converts binary data runs to a map control block. 807 * 808 * @param DataRun 809 * Pointer to the run data 810 * 811 * @param DataRunsMCB 812 * Pointer to an unitialized LARGE_MCB structure. 813 * 814 * @return 815 * STATUS_SUCCESS on success, STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if we fail to 816 * initialize the mcb or add an entry. 817 * 818 * @remarks 819 * Initializes the LARGE_MCB pointed to by DataRunsMCB. If this function succeeds, you 820 * need to call FsRtlUninitializeLargeMcb() when you're done with DataRunsMCB. This 821 * function will ensure the LargeMCB has been unitialized in case of failure. 822 * 823 */ 824 NTSTATUS 825 ConvertDataRunsToLargeMCB(PUCHAR DataRun, 826 PLARGE_MCB DataRunsMCB, 827 PULONGLONG pNextVBN) 828 { 829 LONGLONG DataRunOffset; 830 ULONGLONG DataRunLength; 831 LONGLONG DataRunStartLCN; 832 ULONGLONG LastLCN = 0; 833 834 // Initialize the MCB, potentially catch an exception 835 _SEH2_TRY{ 836 FsRtlInitializeLargeMcb(DataRunsMCB, NonPagedPool); 837 } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { 838 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 839 } _SEH2_END; 840 841 while (*DataRun != 0) 842 { 843 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); 844 845 if (DataRunOffset != -1) 846 { 847 // Normal data run. 848 DataRunStartLCN = LastLCN + DataRunOffset; 849 LastLCN = DataRunStartLCN; 850 851 _SEH2_TRY{ 852 if (!FsRtlAddLargeMcbEntry(DataRunsMCB, 853 *pNextVBN, 854 DataRunStartLCN, 855 DataRunLength)) 856 { 857 ExRaiseStatus(STATUS_UNSUCCESSFUL); 858 } 859 } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { 860 FsRtlUninitializeLargeMcb(DataRunsMCB); 861 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 862 } _SEH2_END; 863 864 } 865 866 *pNextVBN += DataRunLength; 867 } 868 869 return STATUS_SUCCESS; 870 } 871 872 /** 873 * @name ConvertLargeMCBToDataRuns 874 * @implemented 875 * 876 * Converts a map control block to a series of encoded data runs (used by non-resident attributes). 877 * 878 * @param DataRunsMCB 879 * Pointer to a LARGE_MCB structure describing the data runs. 880 * 881 * @param RunBuffer 882 * Pointer to the buffer that will receive the encoded data runs. 883 * 884 * @param MaxBufferSize 885 * Size of RunBuffer, in bytes. 886 * 887 * @param UsedBufferSize 888 * Pointer to a ULONG that will receive the size of the data runs in bytes. Can't be NULL. 889 * 890 * @return 891 * STATUS_SUCCESS on success, STATUS_BUFFER_TOO_SMALL if RunBuffer is too small to contain the 892 * complete output. 893 * 894 */ 895 NTSTATUS 896 ConvertLargeMCBToDataRuns(PLARGE_MCB DataRunsMCB, 897 PUCHAR RunBuffer, 898 ULONG MaxBufferSize, 899 PULONG UsedBufferSize) 900 { 901 NTSTATUS Status = STATUS_SUCCESS; 902 ULONG RunBufferOffset = 0; 903 LONGLONG DataRunOffset; 904 ULONGLONG LastLCN = 0; 905 LONGLONG Vbn, Lbn, Count; 906 ULONG i; 907 908 909 DPRINT("\t[Vbn, Lbn, Count]\n"); 910 911 // convert each mcb entry to a data run 912 for (i = 0; FsRtlGetNextLargeMcbEntry(DataRunsMCB, i, &Vbn, &Lbn, &Count); i++) 913 { 914 UCHAR DataRunOffsetSize = 0; 915 UCHAR DataRunLengthSize = 0; 916 UCHAR ControlByte = 0; 917 918 // [vbn, lbn, count] 919 DPRINT("\t[%I64d, %I64d,%I64d]\n", Vbn, Lbn, Count); 920 921 // TODO: check for holes and convert to sparse runs 922 DataRunOffset = Lbn - LastLCN; 923 LastLCN = Lbn; 924 925 // now we need to determine how to represent DataRunOffset with the minimum number of bytes 926 DPRINT("Determining how many bytes needed to represent %I64x\n", DataRunOffset); 927 DataRunOffsetSize = GetPackedByteCount(DataRunOffset, TRUE); 928 DPRINT("%d bytes needed.\n", DataRunOffsetSize); 929 930 // determine how to represent DataRunLengthSize with the minimum number of bytes 931 DPRINT("Determining how many bytes needed to represent %I64x\n", Count); 932 DataRunLengthSize = GetPackedByteCount(Count, TRUE); 933 DPRINT("%d bytes needed.\n", DataRunLengthSize); 934 935 // ensure the next data run + end marker would be <= Max buffer size 936 if (RunBufferOffset + 2 + DataRunLengthSize + DataRunOffsetSize > MaxBufferSize) 937 { 938 Status = STATUS_BUFFER_TOO_SMALL; 939 DPRINT1("FIXME: Ran out of room in buffer for data runs!\n"); 940 break; 941 } 942 943 // pack and copy the control byte 944 ControlByte = (DataRunOffsetSize << 4) + DataRunLengthSize; 945 RunBuffer[RunBufferOffset++] = ControlByte; 946 947 // copy DataRunLength 948 RtlCopyMemory(RunBuffer + RunBufferOffset, &Count, DataRunLengthSize); 949 RunBufferOffset += DataRunLengthSize; 950 951 // copy DataRunOffset 952 RtlCopyMemory(RunBuffer + RunBufferOffset, &DataRunOffset, DataRunOffsetSize); 953 RunBufferOffset += DataRunOffsetSize; 954 } 955 956 // End of data runs 957 RunBuffer[RunBufferOffset++] = 0; 958 959 *UsedBufferSize = RunBufferOffset; 960 DPRINT("New Size of DataRuns: %ld\n", *UsedBufferSize); 961 962 return Status; 963 } 964 965 PUCHAR 966 DecodeRun(PUCHAR DataRun, 967 LONGLONG *DataRunOffset, 968 ULONGLONG *DataRunLength) 969 { 970 UCHAR DataRunOffsetSize; 971 UCHAR DataRunLengthSize; 972 CHAR i; 973 974 DataRunOffsetSize = (*DataRun >> 4) & 0xF; 975 DataRunLengthSize = *DataRun & 0xF; 976 *DataRunOffset = 0; 977 *DataRunLength = 0; 978 DataRun++; 979 for (i = 0; i < DataRunLengthSize; i++) 980 { 981 *DataRunLength += ((ULONG64)*DataRun) << (i * 8); 982 DataRun++; 983 } 984 985 /* NTFS 3+ sparse files */ 986 if (DataRunOffsetSize == 0) 987 { 988 *DataRunOffset = -1; 989 } 990 else 991 { 992 for (i = 0; i < DataRunOffsetSize - 1; i++) 993 { 994 *DataRunOffset += ((ULONG64)*DataRun) << (i * 8); 995 DataRun++; 996 } 997 /* The last byte contains sign so we must process it different way. */ 998 *DataRunOffset = ((LONG64)(CHAR)(*(DataRun++)) << (i * 8)) + *DataRunOffset; 999 } 1000 1001 DPRINT("DataRunOffsetSize: %x\n", DataRunOffsetSize); 1002 DPRINT("DataRunLengthSize: %x\n", DataRunLengthSize); 1003 DPRINT("DataRunOffset: %x\n", *DataRunOffset); 1004 DPRINT("DataRunLength: %x\n", *DataRunLength); 1005 1006 return DataRun; 1007 } 1008 1009 BOOLEAN 1010 FindRun(PNTFS_ATTR_RECORD NresAttr, 1011 ULONGLONG vcn, 1012 PULONGLONG lcn, 1013 PULONGLONG count) 1014 { 1015 if (vcn < NresAttr->NonResident.LowestVCN || vcn > NresAttr->NonResident.HighestVCN) 1016 return FALSE; 1017 1018 DecodeRun((PUCHAR)((ULONG_PTR)NresAttr + NresAttr->NonResident.MappingPairsOffset), (PLONGLONG)lcn, count); 1019 1020 return TRUE; 1021 } 1022 1023 /** 1024 * @name FreeClusters 1025 * @implemented 1026 * 1027 * Shrinks the allocation size of a non-resident attribute by a given number of clusters. 1028 * Frees the clusters from the volume's $BITMAP file as well as the attribute's data runs. 1029 * 1030 * @param Vcb 1031 * Pointer to an NTFS_VCB for the destination volume. 1032 * 1033 * @param AttrContext 1034 * Pointer to an NTFS_ATTR_CONTEXT describing the attribute from which the clusters will be freed. 1035 * 1036 * @param AttrOffset 1037 * Byte offset of the destination attribute relative to its file record. 1038 * 1039 * @param FileRecord 1040 * Pointer to a complete copy of the file record containing the attribute. Must be at least 1041 * Vcb->NtfsInfo.BytesPerFileRecord bytes long. 1042 * 1043 * @param ClustersToFree 1044 * Number of clusters that should be freed from the end of the data stream. Must be no more 1045 * Than the number of clusters assigned to the attribute (HighestVCN + 1). 1046 * 1047 * @return 1048 * STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute, 1049 * or if the caller requested more clusters be freed than the attribute has been allocated. 1050 * STATUS_INSUFFICIENT_RESOURCES if allocating a buffer for the data runs fails or 1051 * if ConvertDataRunsToLargeMCB() fails. 1052 * STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails. 1053 * 1054 * 1055 */ 1056 NTSTATUS 1057 FreeClusters(PNTFS_VCB Vcb, 1058 PNTFS_ATTR_CONTEXT AttrContext, 1059 ULONG AttrOffset, 1060 PFILE_RECORD_HEADER FileRecord, 1061 ULONG ClustersToFree) 1062 { 1063 NTSTATUS Status = STATUS_SUCCESS; 1064 ULONG ClustersLeftToFree = ClustersToFree; 1065 1066 PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); 1067 ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length; 1068 PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); 1069 1070 PUCHAR RunBuffer; 1071 ULONG RunBufferSize = 0; 1072 1073 PFILE_RECORD_HEADER BitmapRecord; 1074 PNTFS_ATTR_CONTEXT DataContext; 1075 ULONGLONG BitmapDataSize; 1076 PUCHAR BitmapData; 1077 RTL_BITMAP Bitmap; 1078 ULONG LengthWritten; 1079 1080 if (!AttrContext->pRecord->IsNonResident) 1081 { 1082 return STATUS_INVALID_PARAMETER; 1083 } 1084 1085 // Read the $Bitmap file 1086 BitmapRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList); 1087 if (BitmapRecord == NULL) 1088 { 1089 DPRINT1("Error: Unable to allocate memory for bitmap file record!\n"); 1090 return STATUS_NO_MEMORY; 1091 } 1092 1093 Status = ReadFileRecord(Vcb, NTFS_FILE_BITMAP, BitmapRecord); 1094 if (!NT_SUCCESS(Status)) 1095 { 1096 DPRINT1("Error: Unable to read file record for bitmap!\n"); 1097 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord); 1098 return 0; 1099 } 1100 1101 Status = FindAttribute(Vcb, BitmapRecord, AttributeData, L"", 0, &DataContext, NULL); 1102 if (!NT_SUCCESS(Status)) 1103 { 1104 DPRINT1("Error: Unable to find data attribute for bitmap file!\n"); 1105 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord); 1106 return 0; 1107 } 1108 1109 BitmapDataSize = AttributeDataLength(DataContext->pRecord); 1110 BitmapDataSize = min(BitmapDataSize, ULONG_MAX); 1111 ASSERT((BitmapDataSize * 8) >= Vcb->NtfsInfo.ClusterCount); 1112 BitmapData = ExAllocatePoolWithTag(NonPagedPool, ROUND_UP(BitmapDataSize, Vcb->NtfsInfo.BytesPerSector), TAG_NTFS); 1113 if (BitmapData == NULL) 1114 { 1115 DPRINT1("Error: Unable to allocate memory for bitmap file data!\n"); 1116 ReleaseAttributeContext(DataContext); 1117 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord); 1118 return 0; 1119 } 1120 1121 ReadAttribute(Vcb, DataContext, 0, (PCHAR)BitmapData, (ULONG)BitmapDataSize); 1122 1123 RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, Vcb->NtfsInfo.ClusterCount); 1124 1125 // free clusters in $BITMAP file 1126 while (ClustersLeftToFree > 0) 1127 { 1128 LONGLONG LargeVbn, LargeLbn; 1129 1130 if (!FsRtlLookupLastLargeMcbEntry(&AttrContext->DataRunsMCB, &LargeVbn, &LargeLbn)) 1131 { 1132 Status = STATUS_INVALID_PARAMETER; 1133 DPRINT1("DRIVER ERROR: FreeClusters called to free %lu clusters, which is %lu more clusters than are assigned to attribute!", 1134 ClustersToFree, 1135 ClustersLeftToFree); 1136 break; 1137 } 1138 1139 if (LargeLbn != -1) 1140 { 1141 // deallocate this cluster 1142 RtlClearBits(&Bitmap, LargeLbn, 1); 1143 } 1144 FsRtlTruncateLargeMcb(&AttrContext->DataRunsMCB, AttrContext->pRecord->NonResident.HighestVCN); 1145 1146 // decrement HighestVCN, but don't let it go below 0 1147 AttrContext->pRecord->NonResident.HighestVCN = min(AttrContext->pRecord->NonResident.HighestVCN, AttrContext->pRecord->NonResident.HighestVCN - 1); 1148 ClustersLeftToFree--; 1149 } 1150 1151 // update $BITMAP file on disk 1152 Status = WriteAttribute(Vcb, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten, FileRecord); 1153 if (!NT_SUCCESS(Status)) 1154 { 1155 ReleaseAttributeContext(DataContext); 1156 ExFreePoolWithTag(BitmapData, TAG_NTFS); 1157 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord); 1158 return Status; 1159 } 1160 1161 ReleaseAttributeContext(DataContext); 1162 ExFreePoolWithTag(BitmapData, TAG_NTFS); 1163 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord); 1164 1165 // Save updated data runs to file record 1166 1167 // Allocate some memory for a new RunBuffer 1168 RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); 1169 if (!RunBuffer) 1170 { 1171 DPRINT1("ERROR: Couldn't allocate memory for data runs!\n"); 1172 return STATUS_INSUFFICIENT_RESOURCES; 1173 } 1174 1175 // Convert the map control block back to encoded data runs 1176 ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize); 1177 1178 // Update HighestVCN 1179 DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN; 1180 1181 // Write data runs to destination attribute 1182 RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 1183 RunBuffer, 1184 RunBufferSize); 1185 1186 // Is DestinationAttribute the last attribute in the file record? 1187 if (NextAttribute->Type == AttributeEnd) 1188 { 1189 // update attribute length 1190 DestinationAttribute->Length = ALIGN_UP_BY(AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize, 1191 ATTR_RECORD_ALIGNMENT); 1192 1193 ASSERT(DestinationAttribute->Length <= AttrContext->pRecord->Length); 1194 1195 AttrContext->pRecord->Length = DestinationAttribute->Length; 1196 1197 // write end markers 1198 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length); 1199 SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END); 1200 } 1201 1202 // Update the file record 1203 Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord); 1204 1205 ExFreePoolWithTag(RunBuffer, TAG_NTFS); 1206 1207 NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0); 1208 1209 return Status; 1210 } 1211 1212 static 1213 NTSTATUS 1214 InternalReadNonResidentAttributes(PFIND_ATTR_CONTXT Context) 1215 { 1216 ULONGLONG ListSize; 1217 PNTFS_ATTR_RECORD Attribute; 1218 PNTFS_ATTR_CONTEXT ListContext; 1219 1220 DPRINT("InternalReadNonResidentAttributes(%p)\n", Context); 1221 1222 Attribute = Context->CurrAttr; 1223 ASSERT(Attribute->Type == AttributeAttributeList); 1224 1225 if (Context->OnlyResident) 1226 { 1227 Context->NonResidentStart = NULL; 1228 Context->NonResidentEnd = NULL; 1229 return STATUS_SUCCESS; 1230 } 1231 1232 if (Context->NonResidentStart != NULL) 1233 { 1234 return STATUS_FILE_CORRUPT_ERROR; 1235 } 1236 1237 ListContext = PrepareAttributeContext(Attribute); 1238 ListSize = AttributeDataLength(ListContext->pRecord); 1239 if (ListSize > 0xFFFFFFFF) 1240 { 1241 ReleaseAttributeContext(ListContext); 1242 return STATUS_BUFFER_OVERFLOW; 1243 } 1244 1245 Context->NonResidentStart = ExAllocatePoolWithTag(NonPagedPool, (ULONG)ListSize, TAG_NTFS); 1246 if (Context->NonResidentStart == NULL) 1247 { 1248 ReleaseAttributeContext(ListContext); 1249 return STATUS_INSUFFICIENT_RESOURCES; 1250 } 1251 1252 if (ReadAttribute(Context->Vcb, ListContext, 0, (PCHAR)Context->NonResidentStart, (ULONG)ListSize) != ListSize) 1253 { 1254 ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS); 1255 Context->NonResidentStart = NULL; 1256 ReleaseAttributeContext(ListContext); 1257 return STATUS_FILE_CORRUPT_ERROR; 1258 } 1259 1260 ReleaseAttributeContext(ListContext); 1261 Context->NonResidentEnd = (PNTFS_ATTRIBUTE_LIST_ITEM)((PCHAR)Context->NonResidentStart + ListSize); 1262 return STATUS_SUCCESS; 1263 } 1264 1265 static 1266 PNTFS_ATTRIBUTE_LIST_ITEM 1267 InternalGetNextAttributeListItem(PFIND_ATTR_CONTXT Context) 1268 { 1269 PNTFS_ATTRIBUTE_LIST_ITEM NextItem; 1270 1271 if (Context->NonResidentCur == (PVOID)-1) 1272 { 1273 return NULL; 1274 } 1275 1276 if (Context->NonResidentCur == NULL || Context->NonResidentCur->Type == AttributeEnd) 1277 { 1278 Context->NonResidentCur = (PVOID)-1; 1279 return NULL; 1280 } 1281 1282 if (Context->NonResidentCur->Length == 0) 1283 { 1284 DPRINT1("Broken length list entry length !"); 1285 Context->NonResidentCur = (PVOID)-1; 1286 return NULL; 1287 } 1288 1289 NextItem = (PNTFS_ATTRIBUTE_LIST_ITEM)((PCHAR)Context->NonResidentCur + Context->NonResidentCur->Length); 1290 if (NextItem->Length == 0 || NextItem->Type == AttributeEnd) 1291 { 1292 Context->NonResidentCur = (PVOID)-1; 1293 return NULL; 1294 } 1295 1296 if (NextItem < Context->NonResidentStart || NextItem > Context->NonResidentEnd) 1297 { 1298 Context->NonResidentCur = (PVOID)-1; 1299 return NULL; 1300 } 1301 1302 Context->NonResidentCur = NextItem; 1303 return NextItem; 1304 } 1305 1306 NTSTATUS 1307 FindFirstAttributeListItem(PFIND_ATTR_CONTXT Context, 1308 PNTFS_ATTRIBUTE_LIST_ITEM *Item) 1309 { 1310 if (Context->NonResidentStart == NULL || Context->NonResidentStart->Type == AttributeEnd) 1311 { 1312 return STATUS_UNSUCCESSFUL; 1313 } 1314 1315 Context->NonResidentCur = Context->NonResidentStart; 1316 *Item = Context->NonResidentCur; 1317 return STATUS_SUCCESS; 1318 } 1319 1320 NTSTATUS 1321 FindNextAttributeListItem(PFIND_ATTR_CONTXT Context, 1322 PNTFS_ATTRIBUTE_LIST_ITEM *Item) 1323 { 1324 *Item = InternalGetNextAttributeListItem(Context); 1325 if (*Item == NULL) 1326 { 1327 return STATUS_UNSUCCESSFUL; 1328 } 1329 return STATUS_SUCCESS; 1330 } 1331 1332 static 1333 PNTFS_ATTR_RECORD 1334 InternalGetNextAttribute(PFIND_ATTR_CONTXT Context) 1335 { 1336 PNTFS_ATTR_RECORD NextAttribute; 1337 1338 if (Context->CurrAttr == (PVOID)-1) 1339 { 1340 return NULL; 1341 } 1342 1343 if (Context->CurrAttr >= Context->FirstAttr && 1344 Context->CurrAttr < Context->LastAttr) 1345 { 1346 if (Context->CurrAttr->Length == 0) 1347 { 1348 DPRINT1("Broken length!\n"); 1349 Context->CurrAttr = (PVOID)-1; 1350 return NULL; 1351 } 1352 1353 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length); 1354 1355 if (NextAttribute > Context->LastAttr || NextAttribute < Context->FirstAttr) 1356 { 1357 DPRINT1("Broken length: 0x%lx!\n", Context->CurrAttr->Length); 1358 Context->CurrAttr = (PVOID)-1; 1359 return NULL; 1360 } 1361 1362 Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr); 1363 Context->CurrAttr = NextAttribute; 1364 1365 if (Context->CurrAttr < Context->LastAttr && 1366 Context->CurrAttr->Type != AttributeEnd) 1367 { 1368 return Context->CurrAttr; 1369 } 1370 } 1371 1372 if (Context->NonResidentStart == NULL) 1373 { 1374 Context->CurrAttr = (PVOID)-1; 1375 return NULL; 1376 } 1377 1378 Context->CurrAttr = (PVOID)-1; 1379 return NULL; 1380 } 1381 1382 NTSTATUS 1383 FindFirstAttribute(PFIND_ATTR_CONTXT Context, 1384 PDEVICE_EXTENSION Vcb, 1385 PFILE_RECORD_HEADER FileRecord, 1386 BOOLEAN OnlyResident, 1387 PNTFS_ATTR_RECORD * Attribute) 1388 { 1389 NTSTATUS Status; 1390 1391 DPRINT("FindFistAttribute(%p, %p, %p, %p, %u, %p)\n", Context, Vcb, FileRecord, OnlyResident, Attribute); 1392 1393 Context->Vcb = Vcb; 1394 Context->OnlyResident = OnlyResident; 1395 Context->FirstAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset); 1396 Context->CurrAttr = Context->FirstAttr; 1397 Context->LastAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse); 1398 Context->NonResidentStart = NULL; 1399 Context->NonResidentEnd = NULL; 1400 Context->Offset = FileRecord->AttributeOffset; 1401 1402 if (Context->FirstAttr->Type == AttributeEnd) 1403 { 1404 Context->CurrAttr = (PVOID)-1; 1405 return STATUS_END_OF_FILE; 1406 } 1407 else if (Context->FirstAttr->Type == AttributeAttributeList) 1408 { 1409 Status = InternalReadNonResidentAttributes(Context); 1410 if (!NT_SUCCESS(Status)) 1411 { 1412 return Status; 1413 } 1414 1415 *Attribute = InternalGetNextAttribute(Context); 1416 if (*Attribute == NULL) 1417 { 1418 return STATUS_END_OF_FILE; 1419 } 1420 } 1421 else 1422 { 1423 *Attribute = Context->CurrAttr; 1424 Context->Offset = (UCHAR*)Context->CurrAttr - (UCHAR*)FileRecord; 1425 } 1426 1427 return STATUS_SUCCESS; 1428 } 1429 1430 NTSTATUS 1431 FindNextAttribute(PFIND_ATTR_CONTXT Context, 1432 PNTFS_ATTR_RECORD * Attribute) 1433 { 1434 NTSTATUS Status; 1435 1436 DPRINT("FindNextAttribute(%p, %p)\n", Context, Attribute); 1437 1438 *Attribute = InternalGetNextAttribute(Context); 1439 if (*Attribute == NULL) 1440 { 1441 return STATUS_END_OF_FILE; 1442 } 1443 1444 if (Context->CurrAttr->Type != AttributeAttributeList) 1445 { 1446 return STATUS_SUCCESS; 1447 } 1448 1449 Status = InternalReadNonResidentAttributes(Context); 1450 if (!NT_SUCCESS(Status)) 1451 { 1452 return Status; 1453 } 1454 1455 *Attribute = InternalGetNextAttribute(Context); 1456 if (*Attribute == NULL) 1457 { 1458 return STATUS_END_OF_FILE; 1459 } 1460 1461 return STATUS_SUCCESS; 1462 } 1463 1464 VOID 1465 FindCloseAttribute(PFIND_ATTR_CONTXT Context) 1466 { 1467 if (Context->NonResidentStart != NULL) 1468 { 1469 ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS); 1470 Context->NonResidentStart = NULL; 1471 } 1472 } 1473 1474 static 1475 VOID 1476 NtfsDumpFileNameAttribute(PNTFS_ATTR_RECORD Attribute) 1477 { 1478 PFILENAME_ATTRIBUTE FileNameAttr; 1479 1480 DbgPrint(" $FILE_NAME "); 1481 1482 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset); 1483 1484 FileNameAttr = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1485 DbgPrint(" (%x) '%.*S' ", FileNameAttr->NameType, FileNameAttr->NameLength, FileNameAttr->Name); 1486 DbgPrint(" '%x' \n", FileNameAttr->FileAttributes); 1487 DbgPrint(" AllocatedSize: %I64u\nDataSize: %I64u\n", FileNameAttr->AllocatedSize, FileNameAttr->DataSize); 1488 DbgPrint(" File reference: 0x%016I64x\n", FileNameAttr->DirectoryFileReferenceNumber); 1489 } 1490 1491 1492 static 1493 VOID 1494 NtfsDumpStandardInformationAttribute(PNTFS_ATTR_RECORD Attribute) 1495 { 1496 PSTANDARD_INFORMATION StandardInfoAttr; 1497 1498 DbgPrint(" $STANDARD_INFORMATION "); 1499 1500 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset); 1501 1502 StandardInfoAttr = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1503 DbgPrint(" '%x' ", StandardInfoAttr->FileAttribute); 1504 } 1505 1506 1507 static 1508 VOID 1509 NtfsDumpVolumeNameAttribute(PNTFS_ATTR_RECORD Attribute) 1510 { 1511 PWCHAR VolumeName; 1512 1513 DbgPrint(" $VOLUME_NAME "); 1514 1515 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset); 1516 1517 VolumeName = (PWCHAR)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1518 DbgPrint(" '%.*S' ", Attribute->Resident.ValueLength / sizeof(WCHAR), VolumeName); 1519 } 1520 1521 1522 static 1523 VOID 1524 NtfsDumpVolumeInformationAttribute(PNTFS_ATTR_RECORD Attribute) 1525 { 1526 PVOLINFO_ATTRIBUTE VolInfoAttr; 1527 1528 DbgPrint(" $VOLUME_INFORMATION "); 1529 1530 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset); 1531 1532 VolInfoAttr = (PVOLINFO_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1533 DbgPrint(" NTFS Version %u.%u Flags 0x%04hx ", 1534 VolInfoAttr->MajorVersion, 1535 VolInfoAttr->MinorVersion, 1536 VolInfoAttr->Flags); 1537 } 1538 1539 1540 static 1541 VOID 1542 NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute) 1543 { 1544 PINDEX_ROOT_ATTRIBUTE IndexRootAttr; 1545 ULONG CurrentOffset; 1546 ULONG CurrentNode; 1547 1548 IndexRootAttr = (PINDEX_ROOT_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1549 1550 if (IndexRootAttr->AttributeType == AttributeFileName) 1551 ASSERT(IndexRootAttr->CollationRule == COLLATION_FILE_NAME); 1552 1553 DbgPrint(" $INDEX_ROOT (%u bytes per index record, %u clusters) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord); 1554 1555 if (IndexRootAttr->Header.Flags == INDEX_ROOT_SMALL) 1556 { 1557 DbgPrint(" (small)\n"); 1558 } 1559 else 1560 { 1561 ASSERT(IndexRootAttr->Header.Flags == INDEX_ROOT_LARGE); 1562 DbgPrint(" (large)\n"); 1563 } 1564 1565 DbgPrint(" Offset to first index: 0x%lx\n Total size of index entries: 0x%lx\n Allocated size of node: 0x%lx\n", 1566 IndexRootAttr->Header.FirstEntryOffset, 1567 IndexRootAttr->Header.TotalSizeOfEntries, 1568 IndexRootAttr->Header.AllocatedSize); 1569 CurrentOffset = IndexRootAttr->Header.FirstEntryOffset; 1570 CurrentNode = 0; 1571 // print details of every node in the index 1572 while (CurrentOffset < IndexRootAttr->Header.TotalSizeOfEntries) 1573 { 1574 PINDEX_ENTRY_ATTRIBUTE currentIndexExtry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRootAttr + 0x10 + CurrentOffset); 1575 DbgPrint(" Index Node Entry %lu", CurrentNode++); 1576 if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_NODE)) 1577 DbgPrint(" (Branch)"); 1578 else 1579 DbgPrint(" (Leaf)"); 1580 if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_END)) 1581 { 1582 DbgPrint(" (Dummy Key)"); 1583 } 1584 DbgPrint("\n File Reference: 0x%016I64x\n", currentIndexExtry->Data.Directory.IndexedFile); 1585 DbgPrint(" Index Entry Length: 0x%x\n", currentIndexExtry->Length); 1586 DbgPrint(" Index Key Length: 0x%x\n", currentIndexExtry->KeyLength); 1587 1588 // if this isn't the final (dummy) node, print info about the key (Filename attribute) 1589 if (!(currentIndexExtry->Flags & NTFS_INDEX_ENTRY_END)) 1590 { 1591 UNICODE_STRING Name; 1592 DbgPrint(" Parent File Reference: 0x%016I64x\n", currentIndexExtry->FileName.DirectoryFileReferenceNumber); 1593 DbgPrint(" $FILENAME indexed: "); 1594 Name.Length = currentIndexExtry->FileName.NameLength * sizeof(WCHAR); 1595 Name.MaximumLength = Name.Length; 1596 Name.Buffer = currentIndexExtry->FileName.Name; 1597 DbgPrint("'%wZ'\n", &Name); 1598 } 1599 1600 // if this node has a sub-node beneath it 1601 if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE) 1602 { 1603 // Print the VCN of the sub-node 1604 PULONGLONG SubNodeVCN = (PULONGLONG)((ULONG_PTR)currentIndexExtry + currentIndexExtry->Length - sizeof(ULONGLONG)); 1605 DbgPrint(" VCN of sub-node: 0x%llx\n", *SubNodeVCN); 1606 } 1607 1608 CurrentOffset += currentIndexExtry->Length; 1609 ASSERT(currentIndexExtry->Length); 1610 } 1611 1612 } 1613 1614 1615 static 1616 VOID 1617 NtfsDumpAttribute(PDEVICE_EXTENSION Vcb, 1618 PNTFS_ATTR_RECORD Attribute) 1619 { 1620 UNICODE_STRING Name; 1621 1622 ULONGLONG lcn = 0; 1623 ULONGLONG runcount = 0; 1624 1625 switch (Attribute->Type) 1626 { 1627 case AttributeFileName: 1628 NtfsDumpFileNameAttribute(Attribute); 1629 break; 1630 1631 case AttributeStandardInformation: 1632 NtfsDumpStandardInformationAttribute(Attribute); 1633 break; 1634 1635 case AttributeObjectId: 1636 DbgPrint(" $OBJECT_ID "); 1637 break; 1638 1639 case AttributeSecurityDescriptor: 1640 DbgPrint(" $SECURITY_DESCRIPTOR "); 1641 break; 1642 1643 case AttributeVolumeName: 1644 NtfsDumpVolumeNameAttribute(Attribute); 1645 break; 1646 1647 case AttributeVolumeInformation: 1648 NtfsDumpVolumeInformationAttribute(Attribute); 1649 break; 1650 1651 case AttributeData: 1652 DbgPrint(" $DATA "); 1653 //DataBuf = ExAllocatePool(NonPagedPool,AttributeLengthAllocated(Attribute)); 1654 break; 1655 1656 case AttributeIndexRoot: 1657 NtfsDumpIndexRootAttribute(Attribute); 1658 break; 1659 1660 case AttributeIndexAllocation: 1661 DbgPrint(" $INDEX_ALLOCATION "); 1662 break; 1663 1664 case AttributeBitmap: 1665 DbgPrint(" $BITMAP "); 1666 break; 1667 1668 case AttributeReparsePoint: 1669 DbgPrint(" $REPARSE_POINT "); 1670 break; 1671 1672 case AttributeEAInformation: 1673 DbgPrint(" $EA_INFORMATION "); 1674 break; 1675 1676 case AttributeEA: 1677 DbgPrint(" $EA "); 1678 break; 1679 1680 case AttributePropertySet: 1681 DbgPrint(" $PROPERTY_SET "); 1682 break; 1683 1684 case AttributeLoggedUtilityStream: 1685 DbgPrint(" $LOGGED_UTILITY_STREAM "); 1686 break; 1687 1688 default: 1689 DbgPrint(" Attribute %lx ", 1690 Attribute->Type); 1691 break; 1692 } 1693 1694 if (Attribute->Type != AttributeAttributeList) 1695 { 1696 if (Attribute->NameLength != 0) 1697 { 1698 Name.Length = Attribute->NameLength * sizeof(WCHAR); 1699 Name.MaximumLength = Name.Length; 1700 Name.Buffer = (PWCHAR)((ULONG_PTR)Attribute + Attribute->NameOffset); 1701 1702 DbgPrint("'%wZ' ", &Name); 1703 } 1704 1705 DbgPrint("(%s)\n", 1706 Attribute->IsNonResident ? "non-resident" : "resident"); 1707 1708 if (Attribute->IsNonResident) 1709 { 1710 FindRun(Attribute,0,&lcn, &runcount); 1711 1712 DbgPrint(" AllocatedSize %I64u DataSize %I64u InitilizedSize %I64u\n", 1713 Attribute->NonResident.AllocatedSize, Attribute->NonResident.DataSize, Attribute->NonResident.InitializedSize); 1714 DbgPrint(" logical clusters: %I64u - %I64u\n", 1715 lcn, lcn + runcount - 1); 1716 } 1717 else 1718 DbgPrint(" %u bytes of data\n", Attribute->Resident.ValueLength); 1719 } 1720 } 1721 1722 1723 VOID NtfsDumpDataRunData(PUCHAR DataRun) 1724 { 1725 UCHAR DataRunOffsetSize; 1726 UCHAR DataRunLengthSize; 1727 CHAR i; 1728 1729 DbgPrint("%02x ", *DataRun); 1730 1731 if (*DataRun == 0) 1732 return; 1733 1734 DataRunOffsetSize = (*DataRun >> 4) & 0xF; 1735 DataRunLengthSize = *DataRun & 0xF; 1736 1737 DataRun++; 1738 for (i = 0; i < DataRunLengthSize; i++) 1739 { 1740 DbgPrint("%02x ", *DataRun); 1741 DataRun++; 1742 } 1743 1744 for (i = 0; i < DataRunOffsetSize; i++) 1745 { 1746 DbgPrint("%02x ", *DataRun); 1747 DataRun++; 1748 } 1749 1750 NtfsDumpDataRunData(DataRun); 1751 } 1752 1753 1754 VOID 1755 NtfsDumpDataRuns(PVOID StartOfRun, 1756 ULONGLONG CurrentLCN) 1757 { 1758 PUCHAR DataRun = StartOfRun; 1759 LONGLONG DataRunOffset; 1760 ULONGLONG DataRunLength; 1761 1762 if (CurrentLCN == 0) 1763 { 1764 DPRINT1("Dumping data runs.\n\tData:\n\t\t"); 1765 NtfsDumpDataRunData(StartOfRun); 1766 DbgPrint("\n\tRuns:\n\t\tOff\t\tLCN\t\tLength\n"); 1767 } 1768 1769 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); 1770 1771 if (DataRunOffset != -1) 1772 CurrentLCN += DataRunOffset; 1773 1774 DbgPrint("\t\t%I64d\t", DataRunOffset); 1775 if (DataRunOffset < 99999) 1776 DbgPrint("\t"); 1777 DbgPrint("%I64u\t", CurrentLCN); 1778 if (CurrentLCN < 99999) 1779 DbgPrint("\t"); 1780 DbgPrint("%I64u\n", DataRunLength); 1781 1782 if (*DataRun == 0) 1783 DbgPrint("\t\t00\n"); 1784 else 1785 NtfsDumpDataRuns(DataRun, CurrentLCN); 1786 } 1787 1788 1789 VOID 1790 NtfsDumpFileAttributes(PDEVICE_EXTENSION Vcb, 1791 PFILE_RECORD_HEADER FileRecord) 1792 { 1793 NTSTATUS Status; 1794 FIND_ATTR_CONTXT Context; 1795 PNTFS_ATTR_RECORD Attribute; 1796 1797 Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute); 1798 while (NT_SUCCESS(Status)) 1799 { 1800 NtfsDumpAttribute(Vcb, Attribute); 1801 1802 Status = FindNextAttribute(&Context, &Attribute); 1803 } 1804 1805 FindCloseAttribute(&Context); 1806 } 1807 1808 PFILENAME_ATTRIBUTE 1809 GetFileNameFromRecord(PDEVICE_EXTENSION Vcb, 1810 PFILE_RECORD_HEADER FileRecord, 1811 UCHAR NameType) 1812 { 1813 FIND_ATTR_CONTXT Context; 1814 PNTFS_ATTR_RECORD Attribute; 1815 PFILENAME_ATTRIBUTE Name; 1816 NTSTATUS Status; 1817 1818 Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute); 1819 while (NT_SUCCESS(Status)) 1820 { 1821 if (Attribute->Type == AttributeFileName) 1822 { 1823 Name = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1824 if (Name->NameType == NameType || 1825 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_WIN32) || 1826 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_DOS)) 1827 { 1828 FindCloseAttribute(&Context); 1829 return Name; 1830 } 1831 } 1832 1833 Status = FindNextAttribute(&Context, &Attribute); 1834 } 1835 1836 FindCloseAttribute(&Context); 1837 return NULL; 1838 } 1839 1840 /** 1841 * GetPackedByteCount 1842 * Returns the minimum number of bytes needed to represent the value of a 1843 * 64-bit number. Used to encode data runs. 1844 */ 1845 UCHAR 1846 GetPackedByteCount(LONGLONG NumberToPack, 1847 BOOLEAN IsSigned) 1848 { 1849 if (!IsSigned) 1850 { 1851 if (NumberToPack >= 0x0100000000000000) 1852 return 8; 1853 if (NumberToPack >= 0x0001000000000000) 1854 return 7; 1855 if (NumberToPack >= 0x0000010000000000) 1856 return 6; 1857 if (NumberToPack >= 0x0000000100000000) 1858 return 5; 1859 if (NumberToPack >= 0x0000000001000000) 1860 return 4; 1861 if (NumberToPack >= 0x0000000000010000) 1862 return 3; 1863 if (NumberToPack >= 0x0000000000000100) 1864 return 2; 1865 return 1; 1866 } 1867 1868 if (NumberToPack > 0) 1869 { 1870 // we have to make sure the number that gets encoded won't be interpreted as negative 1871 if (NumberToPack >= 0x0080000000000000) 1872 return 8; 1873 if (NumberToPack >= 0x0000800000000000) 1874 return 7; 1875 if (NumberToPack >= 0x0000008000000000) 1876 return 6; 1877 if (NumberToPack >= 0x0000000080000000) 1878 return 5; 1879 if (NumberToPack >= 0x0000000000800000) 1880 return 4; 1881 if (NumberToPack >= 0x0000000000008000) 1882 return 3; 1883 if (NumberToPack >= 0x0000000000000080) 1884 return 2; 1885 } 1886 else 1887 { 1888 // negative number 1889 if (NumberToPack <= 0xff80000000000000) 1890 return 8; 1891 if (NumberToPack <= 0xffff800000000000) 1892 return 7; 1893 if (NumberToPack <= 0xffffff8000000000) 1894 return 6; 1895 if (NumberToPack <= 0xffffffff80000000) 1896 return 5; 1897 if (NumberToPack <= 0xffffffffff800000) 1898 return 4; 1899 if (NumberToPack <= 0xffffffffffff8000) 1900 return 3; 1901 if (NumberToPack <= 0xffffffffffffff80) 1902 return 2; 1903 } 1904 return 1; 1905 } 1906 1907 NTSTATUS 1908 GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_RECORD Attribute, PULONGLONG LastCluster) 1909 { 1910 LONGLONG DataRunOffset; 1911 ULONGLONG DataRunLength; 1912 LONGLONG DataRunStartLCN; 1913 1914 ULONGLONG LastLCN = 0; 1915 PUCHAR DataRun = (PUCHAR)Attribute + Attribute->NonResident.MappingPairsOffset; 1916 1917 if (!Attribute->IsNonResident) 1918 return STATUS_INVALID_PARAMETER; 1919 1920 while (1) 1921 { 1922 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); 1923 1924 if (DataRunOffset != -1) 1925 { 1926 // Normal data run. 1927 DataRunStartLCN = LastLCN + DataRunOffset; 1928 LastLCN = DataRunStartLCN; 1929 *LastCluster = LastLCN + DataRunLength - 1; 1930 } 1931 1932 if (*DataRun == 0) 1933 break; 1934 } 1935 1936 return STATUS_SUCCESS; 1937 } 1938 1939 PSTANDARD_INFORMATION 1940 GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb, 1941 PFILE_RECORD_HEADER FileRecord) 1942 { 1943 NTSTATUS Status; 1944 FIND_ATTR_CONTXT Context; 1945 PNTFS_ATTR_RECORD Attribute; 1946 PSTANDARD_INFORMATION StdInfo; 1947 1948 Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute); 1949 while (NT_SUCCESS(Status)) 1950 { 1951 if (Attribute->Type == AttributeStandardInformation) 1952 { 1953 StdInfo = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1954 FindCloseAttribute(&Context); 1955 return StdInfo; 1956 } 1957 1958 Status = FindNextAttribute(&Context, &Attribute); 1959 } 1960 1961 FindCloseAttribute(&Context); 1962 return NULL; 1963 } 1964 1965 /** 1966 * @name GetFileNameAttributeLength 1967 * @implemented 1968 * 1969 * Returns the size of a given FILENAME_ATTRIBUTE, in bytes. 1970 * 1971 * @param FileNameAttribute 1972 * Pointer to a FILENAME_ATTRIBUTE to determine the size of. 1973 * 1974 * @remarks 1975 * The length of a FILENAME_ATTRIBUTE is variable and is dependent on the length of the file name stored at the end. 1976 * This function operates on the FILENAME_ATTRIBUTE proper, so don't try to pass it a PNTFS_ATTR_RECORD. 1977 */ 1978 ULONG GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute) 1979 { 1980 ULONG Length = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + (FileNameAttribute->NameLength * sizeof(WCHAR)); 1981 return Length; 1982 } 1983 1984 PFILENAME_ATTRIBUTE 1985 GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb, 1986 PFILE_RECORD_HEADER FileRecord) 1987 { 1988 PFILENAME_ATTRIBUTE FileName; 1989 1990 FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_POSIX); 1991 if (FileName == NULL) 1992 { 1993 FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_WIN32); 1994 if (FileName == NULL) 1995 { 1996 FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_DOS); 1997 } 1998 } 1999 2000 return FileName; 2001 } 2002 2003 /* EOF */ 2004