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_ATTR_RECORD)((PCHAR)Context->NonResidentStart + ListSize); 1262 return STATUS_SUCCESS; 1263 } 1264 1265 static 1266 PNTFS_ATTR_RECORD 1267 InternalGetNextAttribute(PFIND_ATTR_CONTXT Context) 1268 { 1269 PNTFS_ATTR_RECORD NextAttribute; 1270 1271 if (Context->CurrAttr == (PVOID)-1) 1272 { 1273 return NULL; 1274 } 1275 1276 if (Context->CurrAttr >= Context->FirstAttr && 1277 Context->CurrAttr < Context->LastAttr) 1278 { 1279 if (Context->CurrAttr->Length == 0) 1280 { 1281 DPRINT1("Broken length!\n"); 1282 Context->CurrAttr = (PVOID)-1; 1283 return NULL; 1284 } 1285 1286 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length); 1287 1288 if (NextAttribute > Context->LastAttr || NextAttribute < Context->FirstAttr) 1289 { 1290 DPRINT1("Broken length: 0x%lx!\n", Context->CurrAttr->Length); 1291 Context->CurrAttr = (PVOID)-1; 1292 return NULL; 1293 } 1294 1295 Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr); 1296 Context->CurrAttr = NextAttribute; 1297 1298 if (Context->CurrAttr < Context->LastAttr && 1299 Context->CurrAttr->Type != AttributeEnd) 1300 { 1301 return Context->CurrAttr; 1302 } 1303 } 1304 1305 if (Context->NonResidentStart == NULL) 1306 { 1307 Context->CurrAttr = (PVOID)-1; 1308 return NULL; 1309 } 1310 1311 if (Context->CurrAttr < Context->NonResidentStart || 1312 Context->CurrAttr >= Context->NonResidentEnd) 1313 { 1314 Context->CurrAttr = Context->NonResidentStart; 1315 } 1316 else if (Context->CurrAttr->Length != 0) 1317 { 1318 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length); 1319 Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr); 1320 Context->CurrAttr = NextAttribute; 1321 } 1322 else 1323 { 1324 DPRINT1("Broken length!\n"); 1325 Context->CurrAttr = (PVOID)-1; 1326 return NULL; 1327 } 1328 1329 if (Context->CurrAttr < Context->NonResidentEnd && 1330 Context->CurrAttr->Type != AttributeEnd) 1331 { 1332 return Context->CurrAttr; 1333 } 1334 1335 Context->CurrAttr = (PVOID)-1; 1336 return NULL; 1337 } 1338 1339 NTSTATUS 1340 FindFirstAttribute(PFIND_ATTR_CONTXT Context, 1341 PDEVICE_EXTENSION Vcb, 1342 PFILE_RECORD_HEADER FileRecord, 1343 BOOLEAN OnlyResident, 1344 PNTFS_ATTR_RECORD * Attribute) 1345 { 1346 NTSTATUS Status; 1347 1348 DPRINT("FindFistAttribute(%p, %p, %p, %p, %u, %p)\n", Context, Vcb, FileRecord, OnlyResident, Attribute); 1349 1350 Context->Vcb = Vcb; 1351 Context->OnlyResident = OnlyResident; 1352 Context->FirstAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset); 1353 Context->CurrAttr = Context->FirstAttr; 1354 Context->LastAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse); 1355 Context->NonResidentStart = NULL; 1356 Context->NonResidentEnd = NULL; 1357 Context->Offset = FileRecord->AttributeOffset; 1358 1359 if (Context->FirstAttr->Type == AttributeEnd) 1360 { 1361 Context->CurrAttr = (PVOID)-1; 1362 return STATUS_END_OF_FILE; 1363 } 1364 else if (Context->FirstAttr->Type == AttributeAttributeList) 1365 { 1366 Status = InternalReadNonResidentAttributes(Context); 1367 if (!NT_SUCCESS(Status)) 1368 { 1369 return Status; 1370 } 1371 1372 *Attribute = InternalGetNextAttribute(Context); 1373 if (*Attribute == NULL) 1374 { 1375 return STATUS_END_OF_FILE; 1376 } 1377 } 1378 else 1379 { 1380 *Attribute = Context->CurrAttr; 1381 Context->Offset = (UCHAR*)Context->CurrAttr - (UCHAR*)FileRecord; 1382 } 1383 1384 return STATUS_SUCCESS; 1385 } 1386 1387 NTSTATUS 1388 FindNextAttribute(PFIND_ATTR_CONTXT Context, 1389 PNTFS_ATTR_RECORD * Attribute) 1390 { 1391 NTSTATUS Status; 1392 1393 DPRINT("FindNextAttribute(%p, %p)\n", Context, Attribute); 1394 1395 *Attribute = InternalGetNextAttribute(Context); 1396 if (*Attribute == NULL) 1397 { 1398 return STATUS_END_OF_FILE; 1399 } 1400 1401 if (Context->CurrAttr->Type != AttributeAttributeList) 1402 { 1403 return STATUS_SUCCESS; 1404 } 1405 1406 Status = InternalReadNonResidentAttributes(Context); 1407 if (!NT_SUCCESS(Status)) 1408 { 1409 return Status; 1410 } 1411 1412 *Attribute = InternalGetNextAttribute(Context); 1413 if (*Attribute == NULL) 1414 { 1415 return STATUS_END_OF_FILE; 1416 } 1417 1418 return STATUS_SUCCESS; 1419 } 1420 1421 VOID 1422 FindCloseAttribute(PFIND_ATTR_CONTXT Context) 1423 { 1424 if (Context->NonResidentStart != NULL) 1425 { 1426 ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS); 1427 Context->NonResidentStart = NULL; 1428 } 1429 } 1430 1431 static 1432 VOID 1433 NtfsDumpFileNameAttribute(PNTFS_ATTR_RECORD Attribute) 1434 { 1435 PFILENAME_ATTRIBUTE FileNameAttr; 1436 1437 DbgPrint(" $FILE_NAME "); 1438 1439 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset); 1440 1441 FileNameAttr = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1442 DbgPrint(" (%x) '%.*S' ", FileNameAttr->NameType, FileNameAttr->NameLength, FileNameAttr->Name); 1443 DbgPrint(" '%x' \n", FileNameAttr->FileAttributes); 1444 DbgPrint(" AllocatedSize: %I64u\nDataSize: %I64u\n", FileNameAttr->AllocatedSize, FileNameAttr->DataSize); 1445 DbgPrint(" File reference: 0x%016I64x\n", FileNameAttr->DirectoryFileReferenceNumber); 1446 } 1447 1448 1449 static 1450 VOID 1451 NtfsDumpStandardInformationAttribute(PNTFS_ATTR_RECORD Attribute) 1452 { 1453 PSTANDARD_INFORMATION StandardInfoAttr; 1454 1455 DbgPrint(" $STANDARD_INFORMATION "); 1456 1457 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset); 1458 1459 StandardInfoAttr = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1460 DbgPrint(" '%x' ", StandardInfoAttr->FileAttribute); 1461 } 1462 1463 1464 static 1465 VOID 1466 NtfsDumpVolumeNameAttribute(PNTFS_ATTR_RECORD Attribute) 1467 { 1468 PWCHAR VolumeName; 1469 1470 DbgPrint(" $VOLUME_NAME "); 1471 1472 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset); 1473 1474 VolumeName = (PWCHAR)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1475 DbgPrint(" '%.*S' ", Attribute->Resident.ValueLength / sizeof(WCHAR), VolumeName); 1476 } 1477 1478 1479 static 1480 VOID 1481 NtfsDumpVolumeInformationAttribute(PNTFS_ATTR_RECORD Attribute) 1482 { 1483 PVOLINFO_ATTRIBUTE VolInfoAttr; 1484 1485 DbgPrint(" $VOLUME_INFORMATION "); 1486 1487 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset); 1488 1489 VolInfoAttr = (PVOLINFO_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1490 DbgPrint(" NTFS Version %u.%u Flags 0x%04hx ", 1491 VolInfoAttr->MajorVersion, 1492 VolInfoAttr->MinorVersion, 1493 VolInfoAttr->Flags); 1494 } 1495 1496 1497 static 1498 VOID 1499 NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute) 1500 { 1501 PINDEX_ROOT_ATTRIBUTE IndexRootAttr; 1502 ULONG CurrentOffset; 1503 ULONG CurrentNode; 1504 1505 IndexRootAttr = (PINDEX_ROOT_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1506 1507 if (IndexRootAttr->AttributeType == AttributeFileName) 1508 ASSERT(IndexRootAttr->CollationRule == COLLATION_FILE_NAME); 1509 1510 DbgPrint(" $INDEX_ROOT (%u bytes per index record, %u clusters) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord); 1511 1512 if (IndexRootAttr->Header.Flags == INDEX_ROOT_SMALL) 1513 { 1514 DbgPrint(" (small)\n"); 1515 } 1516 else 1517 { 1518 ASSERT(IndexRootAttr->Header.Flags == INDEX_ROOT_LARGE); 1519 DbgPrint(" (large)\n"); 1520 } 1521 1522 DbgPrint(" Offset to first index: 0x%lx\n Total size of index entries: 0x%lx\n Allocated size of node: 0x%lx\n", 1523 IndexRootAttr->Header.FirstEntryOffset, 1524 IndexRootAttr->Header.TotalSizeOfEntries, 1525 IndexRootAttr->Header.AllocatedSize); 1526 CurrentOffset = IndexRootAttr->Header.FirstEntryOffset; 1527 CurrentNode = 0; 1528 // print details of every node in the index 1529 while (CurrentOffset < IndexRootAttr->Header.TotalSizeOfEntries) 1530 { 1531 PINDEX_ENTRY_ATTRIBUTE currentIndexExtry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRootAttr + 0x10 + CurrentOffset); 1532 DbgPrint(" Index Node Entry %lu", CurrentNode++); 1533 if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_NODE)) 1534 DbgPrint(" (Branch)"); 1535 else 1536 DbgPrint(" (Leaf)"); 1537 if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_END)) 1538 { 1539 DbgPrint(" (Dummy Key)"); 1540 } 1541 DbgPrint("\n File Reference: 0x%016I64x\n", currentIndexExtry->Data.Directory.IndexedFile); 1542 DbgPrint(" Index Entry Length: 0x%x\n", currentIndexExtry->Length); 1543 DbgPrint(" Index Key Length: 0x%x\n", currentIndexExtry->KeyLength); 1544 1545 // if this isn't the final (dummy) node, print info about the key (Filename attribute) 1546 if (!(currentIndexExtry->Flags & NTFS_INDEX_ENTRY_END)) 1547 { 1548 UNICODE_STRING Name; 1549 DbgPrint(" Parent File Reference: 0x%016I64x\n", currentIndexExtry->FileName.DirectoryFileReferenceNumber); 1550 DbgPrint(" $FILENAME indexed: "); 1551 Name.Length = currentIndexExtry->FileName.NameLength * sizeof(WCHAR); 1552 Name.MaximumLength = Name.Length; 1553 Name.Buffer = currentIndexExtry->FileName.Name; 1554 DbgPrint("'%wZ'\n", &Name); 1555 } 1556 1557 // if this node has a sub-node beneath it 1558 if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE) 1559 { 1560 // Print the VCN of the sub-node 1561 PULONGLONG SubNodeVCN = (PULONGLONG)((ULONG_PTR)currentIndexExtry + currentIndexExtry->Length - sizeof(ULONGLONG)); 1562 DbgPrint(" VCN of sub-node: 0x%llx\n", *SubNodeVCN); 1563 } 1564 1565 CurrentOffset += currentIndexExtry->Length; 1566 ASSERT(currentIndexExtry->Length); 1567 } 1568 1569 } 1570 1571 1572 static 1573 VOID 1574 NtfsDumpAttribute(PDEVICE_EXTENSION Vcb, 1575 PNTFS_ATTR_RECORD Attribute) 1576 { 1577 UNICODE_STRING Name; 1578 1579 ULONGLONG lcn = 0; 1580 ULONGLONG runcount = 0; 1581 1582 switch (Attribute->Type) 1583 { 1584 case AttributeFileName: 1585 NtfsDumpFileNameAttribute(Attribute); 1586 break; 1587 1588 case AttributeStandardInformation: 1589 NtfsDumpStandardInformationAttribute(Attribute); 1590 break; 1591 1592 case AttributeObjectId: 1593 DbgPrint(" $OBJECT_ID "); 1594 break; 1595 1596 case AttributeSecurityDescriptor: 1597 DbgPrint(" $SECURITY_DESCRIPTOR "); 1598 break; 1599 1600 case AttributeVolumeName: 1601 NtfsDumpVolumeNameAttribute(Attribute); 1602 break; 1603 1604 case AttributeVolumeInformation: 1605 NtfsDumpVolumeInformationAttribute(Attribute); 1606 break; 1607 1608 case AttributeData: 1609 DbgPrint(" $DATA "); 1610 //DataBuf = ExAllocatePool(NonPagedPool,AttributeLengthAllocated(Attribute)); 1611 break; 1612 1613 case AttributeIndexRoot: 1614 NtfsDumpIndexRootAttribute(Attribute); 1615 break; 1616 1617 case AttributeIndexAllocation: 1618 DbgPrint(" $INDEX_ALLOCATION "); 1619 break; 1620 1621 case AttributeBitmap: 1622 DbgPrint(" $BITMAP "); 1623 break; 1624 1625 case AttributeReparsePoint: 1626 DbgPrint(" $REPARSE_POINT "); 1627 break; 1628 1629 case AttributeEAInformation: 1630 DbgPrint(" $EA_INFORMATION "); 1631 break; 1632 1633 case AttributeEA: 1634 DbgPrint(" $EA "); 1635 break; 1636 1637 case AttributePropertySet: 1638 DbgPrint(" $PROPERTY_SET "); 1639 break; 1640 1641 case AttributeLoggedUtilityStream: 1642 DbgPrint(" $LOGGED_UTILITY_STREAM "); 1643 break; 1644 1645 default: 1646 DbgPrint(" Attribute %lx ", 1647 Attribute->Type); 1648 break; 1649 } 1650 1651 if (Attribute->Type != AttributeAttributeList) 1652 { 1653 if (Attribute->NameLength != 0) 1654 { 1655 Name.Length = Attribute->NameLength * sizeof(WCHAR); 1656 Name.MaximumLength = Name.Length; 1657 Name.Buffer = (PWCHAR)((ULONG_PTR)Attribute + Attribute->NameOffset); 1658 1659 DbgPrint("'%wZ' ", &Name); 1660 } 1661 1662 DbgPrint("(%s)\n", 1663 Attribute->IsNonResident ? "non-resident" : "resident"); 1664 1665 if (Attribute->IsNonResident) 1666 { 1667 FindRun(Attribute,0,&lcn, &runcount); 1668 1669 DbgPrint(" AllocatedSize %I64u DataSize %I64u InitilizedSize %I64u\n", 1670 Attribute->NonResident.AllocatedSize, Attribute->NonResident.DataSize, Attribute->NonResident.InitializedSize); 1671 DbgPrint(" logical clusters: %I64u - %I64u\n", 1672 lcn, lcn + runcount - 1); 1673 } 1674 else 1675 DbgPrint(" %u bytes of data\n", Attribute->Resident.ValueLength); 1676 } 1677 } 1678 1679 1680 VOID NtfsDumpDataRunData(PUCHAR DataRun) 1681 { 1682 UCHAR DataRunOffsetSize; 1683 UCHAR DataRunLengthSize; 1684 CHAR i; 1685 1686 DbgPrint("%02x ", *DataRun); 1687 1688 if (*DataRun == 0) 1689 return; 1690 1691 DataRunOffsetSize = (*DataRun >> 4) & 0xF; 1692 DataRunLengthSize = *DataRun & 0xF; 1693 1694 DataRun++; 1695 for (i = 0; i < DataRunLengthSize; i++) 1696 { 1697 DbgPrint("%02x ", *DataRun); 1698 DataRun++; 1699 } 1700 1701 for (i = 0; i < DataRunOffsetSize; i++) 1702 { 1703 DbgPrint("%02x ", *DataRun); 1704 DataRun++; 1705 } 1706 1707 NtfsDumpDataRunData(DataRun); 1708 } 1709 1710 1711 VOID 1712 NtfsDumpDataRuns(PVOID StartOfRun, 1713 ULONGLONG CurrentLCN) 1714 { 1715 PUCHAR DataRun = StartOfRun; 1716 LONGLONG DataRunOffset; 1717 ULONGLONG DataRunLength; 1718 1719 if (CurrentLCN == 0) 1720 { 1721 DPRINT1("Dumping data runs.\n\tData:\n\t\t"); 1722 NtfsDumpDataRunData(StartOfRun); 1723 DbgPrint("\n\tRuns:\n\t\tOff\t\tLCN\t\tLength\n"); 1724 } 1725 1726 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); 1727 1728 if (DataRunOffset != -1) 1729 CurrentLCN += DataRunOffset; 1730 1731 DbgPrint("\t\t%I64d\t", DataRunOffset); 1732 if (DataRunOffset < 99999) 1733 DbgPrint("\t"); 1734 DbgPrint("%I64u\t", CurrentLCN); 1735 if (CurrentLCN < 99999) 1736 DbgPrint("\t"); 1737 DbgPrint("%I64u\n", DataRunLength); 1738 1739 if (*DataRun == 0) 1740 DbgPrint("\t\t00\n"); 1741 else 1742 NtfsDumpDataRuns(DataRun, CurrentLCN); 1743 } 1744 1745 1746 VOID 1747 NtfsDumpFileAttributes(PDEVICE_EXTENSION Vcb, 1748 PFILE_RECORD_HEADER FileRecord) 1749 { 1750 NTSTATUS Status; 1751 FIND_ATTR_CONTXT Context; 1752 PNTFS_ATTR_RECORD Attribute; 1753 1754 Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute); 1755 while (NT_SUCCESS(Status)) 1756 { 1757 NtfsDumpAttribute(Vcb, Attribute); 1758 1759 Status = FindNextAttribute(&Context, &Attribute); 1760 } 1761 1762 FindCloseAttribute(&Context); 1763 } 1764 1765 PFILENAME_ATTRIBUTE 1766 GetFileNameFromRecord(PDEVICE_EXTENSION Vcb, 1767 PFILE_RECORD_HEADER FileRecord, 1768 UCHAR NameType) 1769 { 1770 FIND_ATTR_CONTXT Context; 1771 PNTFS_ATTR_RECORD Attribute; 1772 PFILENAME_ATTRIBUTE Name; 1773 NTSTATUS Status; 1774 1775 Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute); 1776 while (NT_SUCCESS(Status)) 1777 { 1778 if (Attribute->Type == AttributeFileName) 1779 { 1780 Name = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1781 if (Name->NameType == NameType || 1782 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_WIN32) || 1783 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_DOS)) 1784 { 1785 FindCloseAttribute(&Context); 1786 return Name; 1787 } 1788 } 1789 1790 Status = FindNextAttribute(&Context, &Attribute); 1791 } 1792 1793 FindCloseAttribute(&Context); 1794 return NULL; 1795 } 1796 1797 /** 1798 * GetPackedByteCount 1799 * Returns the minimum number of bytes needed to represent the value of a 1800 * 64-bit number. Used to encode data runs. 1801 */ 1802 UCHAR 1803 GetPackedByteCount(LONGLONG NumberToPack, 1804 BOOLEAN IsSigned) 1805 { 1806 if (!IsSigned) 1807 { 1808 if (NumberToPack >= 0x0100000000000000) 1809 return 8; 1810 if (NumberToPack >= 0x0001000000000000) 1811 return 7; 1812 if (NumberToPack >= 0x0000010000000000) 1813 return 6; 1814 if (NumberToPack >= 0x0000000100000000) 1815 return 5; 1816 if (NumberToPack >= 0x0000000001000000) 1817 return 4; 1818 if (NumberToPack >= 0x0000000000010000) 1819 return 3; 1820 if (NumberToPack >= 0x0000000000000100) 1821 return 2; 1822 return 1; 1823 } 1824 1825 if (NumberToPack > 0) 1826 { 1827 // we have to make sure the number that gets encoded won't be interpreted as negative 1828 if (NumberToPack >= 0x0080000000000000) 1829 return 8; 1830 if (NumberToPack >= 0x0000800000000000) 1831 return 7; 1832 if (NumberToPack >= 0x0000008000000000) 1833 return 6; 1834 if (NumberToPack >= 0x0000000080000000) 1835 return 5; 1836 if (NumberToPack >= 0x0000000000800000) 1837 return 4; 1838 if (NumberToPack >= 0x0000000000008000) 1839 return 3; 1840 if (NumberToPack >= 0x0000000000000080) 1841 return 2; 1842 } 1843 else 1844 { 1845 // negative number 1846 if (NumberToPack <= 0xff80000000000000) 1847 return 8; 1848 if (NumberToPack <= 0xffff800000000000) 1849 return 7; 1850 if (NumberToPack <= 0xffffff8000000000) 1851 return 6; 1852 if (NumberToPack <= 0xffffffff80000000) 1853 return 5; 1854 if (NumberToPack <= 0xffffffffff800000) 1855 return 4; 1856 if (NumberToPack <= 0xffffffffffff8000) 1857 return 3; 1858 if (NumberToPack <= 0xffffffffffffff80) 1859 return 2; 1860 } 1861 return 1; 1862 } 1863 1864 NTSTATUS 1865 GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_RECORD Attribute, PULONGLONG LastCluster) 1866 { 1867 LONGLONG DataRunOffset; 1868 ULONGLONG DataRunLength; 1869 LONGLONG DataRunStartLCN; 1870 1871 ULONGLONG LastLCN = 0; 1872 PUCHAR DataRun = (PUCHAR)Attribute + Attribute->NonResident.MappingPairsOffset; 1873 1874 if (!Attribute->IsNonResident) 1875 return STATUS_INVALID_PARAMETER; 1876 1877 while (1) 1878 { 1879 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); 1880 1881 if (DataRunOffset != -1) 1882 { 1883 // Normal data run. 1884 DataRunStartLCN = LastLCN + DataRunOffset; 1885 LastLCN = DataRunStartLCN; 1886 *LastCluster = LastLCN + DataRunLength - 1; 1887 } 1888 1889 if (*DataRun == 0) 1890 break; 1891 } 1892 1893 return STATUS_SUCCESS; 1894 } 1895 1896 PSTANDARD_INFORMATION 1897 GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb, 1898 PFILE_RECORD_HEADER FileRecord) 1899 { 1900 NTSTATUS Status; 1901 FIND_ATTR_CONTXT Context; 1902 PNTFS_ATTR_RECORD Attribute; 1903 PSTANDARD_INFORMATION StdInfo; 1904 1905 Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute); 1906 while (NT_SUCCESS(Status)) 1907 { 1908 if (Attribute->Type == AttributeStandardInformation) 1909 { 1910 StdInfo = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 1911 FindCloseAttribute(&Context); 1912 return StdInfo; 1913 } 1914 1915 Status = FindNextAttribute(&Context, &Attribute); 1916 } 1917 1918 FindCloseAttribute(&Context); 1919 return NULL; 1920 } 1921 1922 /** 1923 * @name GetFileNameAttributeLength 1924 * @implemented 1925 * 1926 * Returns the size of a given FILENAME_ATTRIBUTE, in bytes. 1927 * 1928 * @param FileNameAttribute 1929 * Pointer to a FILENAME_ATTRIBUTE to determine the size of. 1930 * 1931 * @remarks 1932 * The length of a FILENAME_ATTRIBUTE is variable and is dependent on the length of the file name stored at the end. 1933 * This function operates on the FILENAME_ATTRIBUTE proper, so don't try to pass it a PNTFS_ATTR_RECORD. 1934 */ 1935 ULONG GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute) 1936 { 1937 ULONG Length = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + (FileNameAttribute->NameLength * sizeof(WCHAR)); 1938 return Length; 1939 } 1940 1941 PFILENAME_ATTRIBUTE 1942 GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb, 1943 PFILE_RECORD_HEADER FileRecord) 1944 { 1945 PFILENAME_ATTRIBUTE FileName; 1946 1947 FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_POSIX); 1948 if (FileName == NULL) 1949 { 1950 FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_WIN32); 1951 if (FileName == NULL) 1952 { 1953 FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_DOS); 1954 } 1955 } 1956 1957 return FileName; 1958 } 1959 1960 /* EOF */ 1961