1 /* 2 * ReactOS kernel 3 * Copyright (C) 2002, 2003, 2014 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/dirctl.c 22 * PURPOSE: NTFS filesystem driver 23 * PROGRAMMERS: Eric Kohl 24 * Pierre Schweitzer (pierre@reactos.org) 25 * Hervé Poussineau (hpoussin@reactos.org) 26 */ 27 28 /* INCLUDES *****************************************************************/ 29 30 #include "ntfs.h" 31 32 #define NDEBUG 33 #include <debug.h> 34 35 /* FUNCTIONS ****************************************************************/ 36 37 ULONGLONG 38 NtfsGetFileSize(PDEVICE_EXTENSION DeviceExt, 39 PFILE_RECORD_HEADER FileRecord, 40 PCWSTR Stream, 41 ULONG StreamLength, 42 PULONGLONG AllocatedSize) 43 { 44 ULONGLONG Size = 0ULL; 45 ULONGLONG Allocated = 0ULL; 46 NTSTATUS Status; 47 PNTFS_ATTR_CONTEXT DataContext; 48 49 Status = FindAttribute(DeviceExt, FileRecord, AttributeData, Stream, StreamLength, &DataContext, NULL); 50 if (NT_SUCCESS(Status)) 51 { 52 Size = AttributeDataLength(DataContext->pRecord); 53 Allocated = AttributeAllocatedLength(DataContext->pRecord); 54 ReleaseAttributeContext(DataContext); 55 } 56 57 if (AllocatedSize != NULL) *AllocatedSize = Allocated; 58 59 return Size; 60 } 61 62 63 #define ULONG_ROUND_UP(x) ROUND_UP((x), (sizeof(ULONG))) 64 65 66 static NTSTATUS 67 NtfsGetNamesInformation(PDEVICE_EXTENSION DeviceExt, 68 PFILE_RECORD_HEADER FileRecord, 69 ULONGLONG MFTIndex, 70 PFILE_NAMES_INFORMATION Info, 71 ULONG BufferLength, 72 PULONG Written, 73 BOOLEAN First) 74 { 75 ULONG Length; 76 NTSTATUS Status; 77 ULONG BytesToCopy = 0; 78 PFILENAME_ATTRIBUTE FileName; 79 80 DPRINT("NtfsGetNamesInformation() called\n"); 81 82 *Written = 0; 83 Status = STATUS_BUFFER_OVERFLOW; 84 if (FIELD_OFFSET(FILE_NAMES_INFORMATION, FileName) > BufferLength) 85 { 86 return Status; 87 } 88 89 FileName = GetBestFileNameFromRecord(DeviceExt, FileRecord); 90 if (FileName == NULL) 91 { 92 DPRINT1("No name information for file ID: %#I64x\n", MFTIndex); 93 NtfsDumpFileAttributes(DeviceExt, FileRecord); 94 return STATUS_OBJECT_NAME_NOT_FOUND; 95 } 96 97 Length = FileName->NameLength * sizeof (WCHAR); 98 if (First || (BufferLength >= FIELD_OFFSET(FILE_NAMES_INFORMATION, FileName) + Length)) 99 { 100 Info->FileNameLength = Length; 101 102 *Written = FIELD_OFFSET(FILE_NAMES_INFORMATION, FileName); 103 Info->NextEntryOffset = 0; 104 if (BufferLength > FIELD_OFFSET(FILE_NAMES_INFORMATION, FileName)) 105 { 106 BytesToCopy = min(Length, BufferLength - FIELD_OFFSET(FILE_NAMES_INFORMATION, FileName)); 107 RtlCopyMemory(Info->FileName, FileName->Name, BytesToCopy); 108 *Written += BytesToCopy; 109 110 if (BytesToCopy == Length) 111 { 112 Info->NextEntryOffset = ULONG_ROUND_UP(sizeof(FILE_NAMES_INFORMATION) + 113 BytesToCopy); 114 Status = STATUS_SUCCESS; 115 } 116 } 117 } 118 119 return Status; 120 } 121 122 123 static NTSTATUS 124 NtfsGetDirectoryInformation(PDEVICE_EXTENSION DeviceExt, 125 PFILE_RECORD_HEADER FileRecord, 126 ULONGLONG MFTIndex, 127 PFILE_DIRECTORY_INFORMATION Info, 128 ULONG BufferLength, 129 PULONG Written, 130 BOOLEAN First) 131 { 132 ULONG Length; 133 NTSTATUS Status; 134 ULONG BytesToCopy = 0; 135 PFILENAME_ATTRIBUTE FileName; 136 PSTANDARD_INFORMATION StdInfo; 137 138 DPRINT("NtfsGetDirectoryInformation() called\n"); 139 140 *Written = 0; 141 Status = STATUS_BUFFER_OVERFLOW; 142 if (FIELD_OFFSET(FILE_DIRECTORY_INFORMATION, FileName) > BufferLength) 143 { 144 return Status; 145 } 146 147 FileName = GetBestFileNameFromRecord(DeviceExt, FileRecord); 148 if (FileName == NULL) 149 { 150 DPRINT1("No name information for file ID: %#I64x\n", MFTIndex); 151 NtfsDumpFileAttributes(DeviceExt, FileRecord); 152 return STATUS_OBJECT_NAME_NOT_FOUND; 153 } 154 155 StdInfo = GetStandardInformationFromRecord(DeviceExt, FileRecord); 156 ASSERT(StdInfo != NULL); 157 158 Length = FileName->NameLength * sizeof (WCHAR); 159 if (First || (BufferLength >= FIELD_OFFSET(FILE_DIRECTORY_INFORMATION, FileName) + Length)) 160 { 161 Info->FileNameLength = Length; 162 163 *Written = FIELD_OFFSET(FILE_DIRECTORY_INFORMATION, FileName); 164 Info->NextEntryOffset = 0; 165 if (BufferLength > FIELD_OFFSET(FILE_DIRECTORY_INFORMATION, FileName)) 166 { 167 BytesToCopy = min(Length, BufferLength - FIELD_OFFSET(FILE_DIRECTORY_INFORMATION, FileName)); 168 RtlCopyMemory(Info->FileName, FileName->Name, BytesToCopy); 169 *Written += BytesToCopy; 170 171 if (BytesToCopy == Length) 172 { 173 Info->NextEntryOffset = ULONG_ROUND_UP(sizeof(FILE_DIRECTORY_INFORMATION) + 174 BytesToCopy); 175 Status = STATUS_SUCCESS; 176 } 177 } 178 179 Info->CreationTime.QuadPart = FileName->CreationTime; 180 Info->LastAccessTime.QuadPart = FileName->LastAccessTime; 181 Info->LastWriteTime.QuadPart = FileName->LastWriteTime; 182 Info->ChangeTime.QuadPart = FileName->ChangeTime; 183 184 /* Convert file flags */ 185 NtfsFileFlagsToAttributes(FileName->FileAttributes | StdInfo->FileAttribute, &Info->FileAttributes); 186 187 Info->EndOfFile.QuadPart = NtfsGetFileSize(DeviceExt, FileRecord, L"", 0, (PULONGLONG)&Info->AllocationSize.QuadPart); 188 189 Info->FileIndex = MFTIndex; 190 } 191 192 return Status; 193 } 194 195 196 static NTSTATUS 197 NtfsGetFullDirectoryInformation(PDEVICE_EXTENSION DeviceExt, 198 PFILE_RECORD_HEADER FileRecord, 199 ULONGLONG MFTIndex, 200 PFILE_FULL_DIRECTORY_INFORMATION Info, 201 ULONG BufferLength, 202 PULONG Written, 203 BOOLEAN First) 204 { 205 ULONG Length; 206 NTSTATUS Status; 207 ULONG BytesToCopy = 0; 208 PFILENAME_ATTRIBUTE FileName; 209 PSTANDARD_INFORMATION StdInfo; 210 211 DPRINT("NtfsGetFullDirectoryInformation() called\n"); 212 213 *Written = 0; 214 Status = STATUS_BUFFER_OVERFLOW; 215 if (FIELD_OFFSET(FILE_FULL_DIR_INFORMATION, FileName) > BufferLength) 216 { 217 return Status; 218 } 219 220 FileName = GetBestFileNameFromRecord(DeviceExt, FileRecord); 221 if (FileName == NULL) 222 { 223 DPRINT1("No name information for file ID: %#I64x\n", MFTIndex); 224 NtfsDumpFileAttributes(DeviceExt, FileRecord); 225 return STATUS_OBJECT_NAME_NOT_FOUND; 226 } 227 228 StdInfo = GetStandardInformationFromRecord(DeviceExt, FileRecord); 229 ASSERT(StdInfo != NULL); 230 231 Length = FileName->NameLength * sizeof (WCHAR); 232 if (First || (BufferLength >= FIELD_OFFSET(FILE_FULL_DIR_INFORMATION, FileName) + Length)) 233 { 234 Info->FileNameLength = Length; 235 236 *Written = FIELD_OFFSET(FILE_FULL_DIR_INFORMATION, FileName); 237 Info->NextEntryOffset = 0; 238 if (BufferLength > FIELD_OFFSET(FILE_FULL_DIR_INFORMATION, FileName)) 239 { 240 BytesToCopy = min(Length, BufferLength - FIELD_OFFSET(FILE_FULL_DIR_INFORMATION, FileName)); 241 RtlCopyMemory(Info->FileName, FileName->Name, BytesToCopy); 242 *Written += BytesToCopy; 243 244 if (BytesToCopy == Length) 245 { 246 Info->NextEntryOffset = ULONG_ROUND_UP(sizeof(FILE_FULL_DIR_INFORMATION) + 247 BytesToCopy); 248 Status = STATUS_SUCCESS; 249 } 250 } 251 252 Info->CreationTime.QuadPart = FileName->CreationTime; 253 Info->LastAccessTime.QuadPart = FileName->LastAccessTime; 254 Info->LastWriteTime.QuadPart = FileName->LastWriteTime; 255 Info->ChangeTime.QuadPart = FileName->ChangeTime; 256 257 /* Convert file flags */ 258 NtfsFileFlagsToAttributes(FileName->FileAttributes | StdInfo->FileAttribute, &Info->FileAttributes); 259 260 Info->EndOfFile.QuadPart = NtfsGetFileSize(DeviceExt, FileRecord, L"", 0, (PULONGLONG)&Info->AllocationSize.QuadPart); 261 262 Info->FileIndex = MFTIndex; 263 Info->EaSize = 0; 264 } 265 266 return Status; 267 } 268 269 270 static NTSTATUS 271 NtfsGetBothDirectoryInformation(PDEVICE_EXTENSION DeviceExt, 272 PFILE_RECORD_HEADER FileRecord, 273 ULONGLONG MFTIndex, 274 PFILE_BOTH_DIR_INFORMATION Info, 275 ULONG BufferLength, 276 PULONG Written, 277 BOOLEAN First) 278 { 279 ULONG Length; 280 NTSTATUS Status; 281 ULONG BytesToCopy = 0; 282 PFILENAME_ATTRIBUTE FileName, ShortFileName; 283 PSTANDARD_INFORMATION StdInfo; 284 285 DPRINT("NtfsGetBothDirectoryInformation() called\n"); 286 287 *Written = 0; 288 Status = STATUS_BUFFER_OVERFLOW; 289 if (FIELD_OFFSET(FILE_BOTH_DIR_INFORMATION, FileName) > BufferLength) 290 { 291 return Status; 292 } 293 294 FileName = GetBestFileNameFromRecord(DeviceExt, FileRecord); 295 if (FileName == NULL) 296 { 297 DPRINT1("No name information for file ID: %#I64x\n", MFTIndex); 298 NtfsDumpFileAttributes(DeviceExt, FileRecord); 299 return STATUS_OBJECT_NAME_NOT_FOUND; 300 } 301 ShortFileName = GetFileNameFromRecord(DeviceExt, FileRecord, NTFS_FILE_NAME_DOS); 302 303 StdInfo = GetStandardInformationFromRecord(DeviceExt, FileRecord); 304 ASSERT(StdInfo != NULL); 305 306 Length = FileName->NameLength * sizeof (WCHAR); 307 if (First || (BufferLength >= FIELD_OFFSET(FILE_BOTH_DIR_INFORMATION, FileName) + Length)) 308 { 309 Info->FileNameLength = Length; 310 311 *Written = FIELD_OFFSET(FILE_BOTH_DIR_INFORMATION, FileName); 312 Info->NextEntryOffset = 0; 313 if (BufferLength > FIELD_OFFSET(FILE_BOTH_DIR_INFORMATION, FileName)) 314 { 315 BytesToCopy = min(Length, BufferLength - FIELD_OFFSET(FILE_BOTH_DIR_INFORMATION, FileName)); 316 RtlCopyMemory(Info->FileName, FileName->Name, BytesToCopy); 317 *Written += BytesToCopy; 318 319 if (BytesToCopy == Length) 320 { 321 Info->NextEntryOffset = ULONG_ROUND_UP(sizeof(FILE_BOTH_DIR_INFORMATION) + 322 BytesToCopy); 323 Status = STATUS_SUCCESS; 324 } 325 } 326 327 if (ShortFileName) 328 { 329 /* Should we upcase the filename? */ 330 ASSERT(ShortFileName->NameLength <= ARRAYSIZE(Info->ShortName)); 331 Info->ShortNameLength = ShortFileName->NameLength * sizeof(WCHAR); 332 RtlCopyMemory(Info->ShortName, ShortFileName->Name, Info->ShortNameLength); 333 } 334 else 335 { 336 Info->ShortName[0] = 0; 337 Info->ShortNameLength = 0; 338 } 339 340 Info->CreationTime.QuadPart = FileName->CreationTime; 341 Info->LastAccessTime.QuadPart = FileName->LastAccessTime; 342 Info->LastWriteTime.QuadPart = FileName->LastWriteTime; 343 Info->ChangeTime.QuadPart = FileName->ChangeTime; 344 345 /* Convert file flags */ 346 NtfsFileFlagsToAttributes(FileName->FileAttributes | StdInfo->FileAttribute, &Info->FileAttributes); 347 348 Info->EndOfFile.QuadPart = NtfsGetFileSize(DeviceExt, FileRecord, L"", 0, (PULONGLONG)&Info->AllocationSize.QuadPart); 349 350 Info->FileIndex = MFTIndex; 351 Info->EaSize = 0; 352 } 353 354 return Status; 355 } 356 357 358 NTSTATUS 359 NtfsQueryDirectory(PNTFS_IRP_CONTEXT IrpContext) 360 { 361 PIRP Irp; 362 PDEVICE_OBJECT DeviceObject; 363 PDEVICE_EXTENSION DeviceExtension; 364 LONG BufferLength = 0; 365 PUNICODE_STRING SearchPattern = NULL; 366 FILE_INFORMATION_CLASS FileInformationClass; 367 ULONG FileIndex = 0; 368 PUCHAR Buffer = NULL; 369 PFILE_NAMES_INFORMATION Buffer0 = NULL; 370 PNTFS_FCB Fcb; 371 PNTFS_CCB Ccb; 372 BOOLEAN First = FALSE; 373 PIO_STACK_LOCATION Stack; 374 PFILE_OBJECT FileObject; 375 NTSTATUS Status = STATUS_SUCCESS; 376 PFILE_RECORD_HEADER FileRecord; 377 ULONGLONG MFTRecord, OldMFTRecord = 0; 378 UNICODE_STRING Pattern; 379 ULONG Written; 380 381 DPRINT1("NtfsQueryDirectory() called\n"); 382 383 ASSERT(IrpContext); 384 Irp = IrpContext->Irp; 385 DeviceObject = IrpContext->DeviceObject; 386 387 DeviceExtension = DeviceObject->DeviceExtension; 388 Stack = IoGetCurrentIrpStackLocation(Irp); 389 FileObject = Stack->FileObject; 390 391 Ccb = (PNTFS_CCB)FileObject->FsContext2; 392 Fcb = (PNTFS_FCB)FileObject->FsContext; 393 394 /* Obtain the callers parameters */ 395 BufferLength = Stack->Parameters.QueryDirectory.Length; 396 SearchPattern = Stack->Parameters.QueryDirectory.FileName; 397 FileInformationClass = Stack->Parameters.QueryDirectory.FileInformationClass; 398 FileIndex = Stack->Parameters.QueryDirectory.FileIndex; 399 400 if (NtfsFCBIsCompressed(Fcb)) 401 { 402 DPRINT1("Compressed directory!\n"); 403 UNIMPLEMENTED; 404 return STATUS_NOT_IMPLEMENTED; 405 } 406 407 if (!ExAcquireResourceSharedLite(&Fcb->MainResource, 408 BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT))) 409 { 410 return STATUS_PENDING; 411 } 412 413 if (SearchPattern != NULL) 414 { 415 if (!Ccb->DirectorySearchPattern) 416 { 417 First = TRUE; 418 Pattern.Length = 0; 419 Pattern.MaximumLength = SearchPattern->Length + sizeof(WCHAR); 420 Ccb->DirectorySearchPattern = Pattern.Buffer = 421 ExAllocatePoolWithTag(NonPagedPool, Pattern.MaximumLength, TAG_NTFS); 422 if (!Ccb->DirectorySearchPattern) 423 { 424 ExReleaseResourceLite(&Fcb->MainResource); 425 return STATUS_INSUFFICIENT_RESOURCES; 426 } 427 428 memcpy(Ccb->DirectorySearchPattern, SearchPattern->Buffer, SearchPattern->Length); 429 Ccb->DirectorySearchPattern[SearchPattern->Length / sizeof(WCHAR)] = 0; 430 } 431 } 432 else if (!Ccb->DirectorySearchPattern) 433 { 434 First = TRUE; 435 Ccb->DirectorySearchPattern = ExAllocatePoolWithTag(NonPagedPool, 2 * sizeof(WCHAR), TAG_NTFS); 436 if (!Ccb->DirectorySearchPattern) 437 { 438 ExReleaseResourceLite(&Fcb->MainResource); 439 return STATUS_INSUFFICIENT_RESOURCES; 440 } 441 442 Ccb->DirectorySearchPattern[0] = L'*'; 443 Ccb->DirectorySearchPattern[1] = 0; 444 } 445 446 RtlInitUnicodeString(&Pattern, Ccb->DirectorySearchPattern); 447 DPRINT("Search pattern '%S'\n", Ccb->DirectorySearchPattern); 448 DPRINT("In: '%S'\n", Fcb->PathName); 449 450 /* Determine directory index */ 451 if (Stack->Flags & SL_INDEX_SPECIFIED) 452 { 453 Ccb->Entry = FileIndex; 454 } 455 else if (First || (Stack->Flags & SL_RESTART_SCAN)) 456 { 457 Ccb->Entry = 0; 458 } 459 460 /* Get Buffer for result */ 461 Buffer = NtfsGetUserBuffer(Irp, FALSE); 462 463 DPRINT("Buffer=%p tofind=%S\n", Buffer, Ccb->DirectorySearchPattern); 464 465 if (!ExAcquireResourceExclusiveLite(&DeviceExtension->DirResource, 466 BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT))) 467 { 468 ExReleaseResourceLite(&Fcb->MainResource); 469 return STATUS_PENDING; 470 } 471 472 Written = 0; 473 while (Status == STATUS_SUCCESS && BufferLength > 0) 474 { 475 Status = NtfsFindFileAt(DeviceExtension, 476 &Pattern, 477 &Ccb->Entry, 478 &FileRecord, 479 &MFTRecord, 480 Fcb->MFTIndex, 481 BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE)); 482 483 if (NT_SUCCESS(Status)) 484 { 485 /* HACK: files with both a short name and a long name are present twice in the index. 486 * Ignore the second entry, if it is immediately following the first one. 487 */ 488 if (MFTRecord == OldMFTRecord) 489 { 490 DPRINT1("Ignoring duplicate MFT entry 0x%x\n", MFTRecord); 491 Ccb->Entry++; 492 ExFreeToNPagedLookasideList(&DeviceExtension->FileRecLookasideList, FileRecord); 493 continue; 494 } 495 OldMFTRecord = MFTRecord; 496 497 switch (FileInformationClass) 498 { 499 case FileNamesInformation: 500 Status = NtfsGetNamesInformation(DeviceExtension, 501 FileRecord, 502 MFTRecord, 503 (PFILE_NAMES_INFORMATION)Buffer, 504 BufferLength, 505 &Written, 506 Buffer0 == NULL); 507 break; 508 509 case FileDirectoryInformation: 510 Status = NtfsGetDirectoryInformation(DeviceExtension, 511 FileRecord, 512 MFTRecord, 513 (PFILE_DIRECTORY_INFORMATION)Buffer, 514 BufferLength, 515 &Written, 516 Buffer0 == NULL); 517 break; 518 519 case FileFullDirectoryInformation: 520 Status = NtfsGetFullDirectoryInformation(DeviceExtension, 521 FileRecord, 522 MFTRecord, 523 (PFILE_FULL_DIRECTORY_INFORMATION)Buffer, 524 BufferLength, 525 &Written, 526 Buffer0 == NULL); 527 break; 528 529 case FileBothDirectoryInformation: 530 Status = NtfsGetBothDirectoryInformation(DeviceExtension, 531 FileRecord, 532 MFTRecord, 533 (PFILE_BOTH_DIR_INFORMATION)Buffer, 534 BufferLength, 535 &Written, 536 Buffer0 == NULL); 537 break; 538 539 default: 540 Status = STATUS_INVALID_INFO_CLASS; 541 } 542 543 if (Status == STATUS_BUFFER_OVERFLOW || Status == STATUS_INVALID_INFO_CLASS) 544 { 545 break; 546 } 547 } 548 else 549 { 550 Status = (First ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES); 551 break; 552 } 553 554 Buffer0 = (PFILE_NAMES_INFORMATION)Buffer; 555 Buffer0->FileIndex = FileIndex++; 556 Ccb->Entry++; 557 BufferLength -= Buffer0->NextEntryOffset; 558 559 ExFreeToNPagedLookasideList(&DeviceExtension->FileRecLookasideList, FileRecord); 560 561 if (Stack->Flags & SL_RETURN_SINGLE_ENTRY) 562 { 563 break; 564 } 565 566 Buffer += Buffer0->NextEntryOffset; 567 } 568 569 if (Buffer0) 570 { 571 Buffer0->NextEntryOffset = 0; 572 Status = STATUS_SUCCESS; 573 IrpContext->Irp->IoStatus.Information = Stack->Parameters.QueryDirectory.Length - BufferLength; 574 } 575 else 576 { 577 ASSERT(Status != STATUS_SUCCESS || BufferLength == 0); 578 ASSERT(Written <= Stack->Parameters.QueryDirectory.Length); 579 IrpContext->Irp->IoStatus.Information = Written; 580 } 581 582 ExReleaseResourceLite(&DeviceExtension->DirResource); 583 ExReleaseResourceLite(&Fcb->MainResource); 584 585 return Status; 586 } 587 588 589 NTSTATUS 590 NtfsDirectoryControl(PNTFS_IRP_CONTEXT IrpContext) 591 { 592 NTSTATUS Status = STATUS_UNSUCCESSFUL; 593 594 DPRINT1("NtfsDirectoryControl() called\n"); 595 596 switch (IrpContext->MinorFunction) 597 { 598 case IRP_MN_QUERY_DIRECTORY: 599 Status = NtfsQueryDirectory(IrpContext); 600 break; 601 602 case IRP_MN_NOTIFY_CHANGE_DIRECTORY: 603 DPRINT1("IRP_MN_NOTIFY_CHANGE_DIRECTORY\n"); 604 Status = STATUS_NOT_IMPLEMENTED; 605 break; 606 607 default: 608 Status = STATUS_INVALID_DEVICE_REQUEST; 609 break; 610 } 611 612 if (Status == STATUS_PENDING && IrpContext->Flags & IRPCONTEXT_COMPLETE) 613 { 614 return NtfsMarkIrpContextForQueue(IrpContext); 615 } 616 617 IrpContext->Irp->IoStatus.Information = 0; 618 619 return Status; 620 } 621 622 /* EOF */ 623