1 /*++ 2 3 Copyright (c) 1989-2000 Microsoft Corporation 4 5 Module Name: 6 7 DirCtrl.c 8 9 Abstract: 10 11 This module implements the File Directory Control routines for Cdfs called 12 by the Fsd/Fsp dispatch drivers. 13 14 15 --*/ 16 17 #include "cdprocs.h" 18 19 // 20 // The Bug check file id for this module 21 // 22 23 #define BugCheckFileId (CDFS_BUG_CHECK_DIRCTRL) 24 25 // 26 // Local support routines 27 // 28 29 _Requires_lock_held_(_Global_critical_region_) 30 NTSTATUS 31 CdQueryDirectory ( 32 _Inout_ PIRP_CONTEXT IrpContext, 33 _Inout_ PIRP Irp, 34 _In_ PIO_STACK_LOCATION IrpSp, 35 _In_ PFCB Fcb, 36 _In_ PCCB Ccb 37 ); 38 39 _Requires_lock_held_(_Global_critical_region_) 40 NTSTATUS 41 CdNotifyChangeDirectory ( 42 _Inout_ PIRP_CONTEXT IrpContext, 43 _Inout_ PIRP Irp, 44 _In_ PIO_STACK_LOCATION IrpSp, 45 _In_ PCCB Ccb 46 ); 47 48 VOID 49 CdInitializeEnumeration ( 50 _In_ PIRP_CONTEXT IrpContext, 51 _In_ PIO_STACK_LOCATION IrpSp, 52 _In_ PFCB Fcb, 53 _Inout_ PCCB Ccb, 54 _Inout_ PFILE_ENUM_CONTEXT FileContext, 55 _Out_ PBOOLEAN ReturnNextEntry, 56 _Out_ PBOOLEAN ReturnSingleEntry, 57 _Out_ PBOOLEAN InitialQuery 58 ); 59 60 BOOLEAN 61 CdEnumerateIndex ( 62 _In_ PIRP_CONTEXT IrpContext, 63 _In_ PCCB Ccb, 64 _Inout_ PFILE_ENUM_CONTEXT FileContext, 65 _In_ BOOLEAN ReturnNextEntry 66 ); 67 68 #ifdef ALLOC_PRAGMA 69 #pragma alloc_text(PAGE, CdCommonDirControl) 70 #pragma alloc_text(PAGE, CdEnumerateIndex) 71 #pragma alloc_text(PAGE, CdInitializeEnumeration) 72 #pragma alloc_text(PAGE, CdNotifyChangeDirectory) 73 #pragma alloc_text(PAGE, CdQueryDirectory) 74 #endif 75 76 77 78 _Requires_lock_held_(_Global_critical_region_) 79 NTSTATUS 80 CdCommonDirControl ( 81 _Inout_ PIRP_CONTEXT IrpContext, 82 _Inout_ PIRP Irp 83 ) 84 85 /*++ 86 87 Routine Description: 88 89 This routine is the entry point for the directory control operations. These 90 are directory enumerations and directory notify calls. We verify the 91 user's handle is for a directory and then call the appropriate routine. 92 93 Arguments: 94 95 Irp - Irp for this request. 96 97 Return Value: 98 99 NTSTATUS - Status returned from the lower level routines. 100 101 --*/ 102 103 { 104 NTSTATUS Status; 105 PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp ); 106 107 PFCB Fcb; 108 PCCB Ccb; 109 110 PAGED_CODE(); 111 112 // 113 // Decode the user file object and fail this request if it is not 114 // a user directory. 115 // 116 117 if (CdDecodeFileObject( IrpContext, 118 IrpSp->FileObject, 119 &Fcb, 120 &Ccb ) != UserDirectoryOpen) { 121 122 CdCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); 123 return STATUS_INVALID_PARAMETER; 124 } 125 126 // 127 // We know this is a directory control so we'll case on the 128 // minor function, and call a internal worker routine to complete 129 // the irp. 130 // 131 132 switch (IrpSp->MinorFunction) { 133 134 case IRP_MN_QUERY_DIRECTORY: 135 136 Status = CdQueryDirectory( IrpContext, Irp, IrpSp, Fcb, Ccb ); 137 break; 138 139 case IRP_MN_NOTIFY_CHANGE_DIRECTORY: 140 141 Status = CdNotifyChangeDirectory( IrpContext, Irp, IrpSp, Ccb ); 142 break; 143 144 default: 145 146 CdCompleteRequest( IrpContext, Irp, STATUS_INVALID_DEVICE_REQUEST ); 147 Status = STATUS_INVALID_DEVICE_REQUEST; 148 break; 149 } 150 151 return Status; 152 } 153 154 155 // 156 // Local support routines 157 // 158 159 _Requires_lock_held_(_Global_critical_region_) 160 NTSTATUS 161 CdQueryDirectory ( 162 _Inout_ PIRP_CONTEXT IrpContext, 163 _Inout_ PIRP Irp, 164 _In_ PIO_STACK_LOCATION IrpSp, 165 _In_ PFCB Fcb, 166 _In_ PCCB Ccb 167 ) 168 169 /*++ 170 171 Routine Description: 172 173 This routine performs the query directory operation. It is responsible 174 for either completing of enqueuing the input Irp. We store the state of the 175 search in the Ccb. 176 177 Arguments: 178 179 Irp - Supplies the Irp to process 180 181 IrpSp - Stack location for this Irp. 182 183 Fcb - Fcb for this directory. 184 185 Ccb - Ccb for this directory open. 186 187 Return Value: 188 189 NTSTATUS - The return status for the operation 190 191 --*/ 192 193 { 194 NTSTATUS Status = STATUS_SUCCESS; 195 ULONG Information = 0; 196 197 ULONG LastEntry = 0; 198 ULONG NextEntry = 0; 199 200 ULONG FileNameBytes; 201 ULONG SeparatorBytes; 202 ULONG VersionStringBytes; 203 204 FILE_ENUM_CONTEXT FileContext; 205 PDIRENT ThisDirent = NULL; 206 BOOLEAN InitialQuery; 207 BOOLEAN ReturnNextEntry = FALSE; 208 BOOLEAN ReturnSingleEntry; 209 BOOLEAN Found; 210 BOOLEAN DoCcbUpdate = FALSE; 211 212 PCHAR UserBuffer; 213 ULONG BytesRemainingInBuffer; 214 215 ULONG BaseLength; 216 217 PFILE_BOTH_DIR_INFORMATION DirInfo = NULL; 218 PFILE_NAMES_INFORMATION NamesInfo; 219 PFILE_ID_FULL_DIR_INFORMATION IdFullDirInfo; 220 PFILE_ID_BOTH_DIR_INFORMATION IdBothDirInfo; 221 222 PAGED_CODE(); 223 224 // 225 // Check if we support this search mode. Also remember the size of the base part of 226 // each of these structures. 227 // 228 229 switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) { 230 231 case FileDirectoryInformation: 232 233 BaseLength = FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, 234 FileName[0] ); 235 break; 236 237 case FileFullDirectoryInformation: 238 239 BaseLength = FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, 240 FileName[0] ); 241 break; 242 243 case FileIdFullDirectoryInformation: 244 245 BaseLength = FIELD_OFFSET( FILE_ID_FULL_DIR_INFORMATION, 246 FileName[0] ); 247 break; 248 249 case FileNamesInformation: 250 251 BaseLength = FIELD_OFFSET( FILE_NAMES_INFORMATION, 252 FileName[0] ); 253 break; 254 255 case FileBothDirectoryInformation: 256 257 BaseLength = FIELD_OFFSET( FILE_BOTH_DIR_INFORMATION, 258 FileName[0] ); 259 break; 260 261 case FileIdBothDirectoryInformation: 262 263 BaseLength = FIELD_OFFSET( FILE_ID_BOTH_DIR_INFORMATION, 264 FileName[0] ); 265 break; 266 267 default: 268 269 CdCompleteRequest( IrpContext, Irp, STATUS_INVALID_INFO_CLASS ); 270 return STATUS_INVALID_INFO_CLASS; 271 } 272 273 // 274 // Get the user buffer. 275 // 276 277 CdMapUserBuffer( IrpContext, &UserBuffer); 278 279 // 280 // Initialize our search context. 281 // 282 283 CdInitializeFileContext( IrpContext, &FileContext ); 284 285 // 286 // Acquire the directory. 287 // 288 289 CdAcquireFileShared( IrpContext, Fcb ); 290 291 // 292 // Use a try-finally to facilitate cleanup. 293 // 294 295 _SEH2_TRY { 296 297 // 298 // Verify the Fcb is still good. 299 // 300 301 CdVerifyFcbOperation( IrpContext, Fcb ); 302 303 // 304 // Start by getting the initial state for the enumeration. This will set up the Ccb with 305 // the initial search parameters and let us know the starting offset in the directory 306 // to search. 307 // 308 309 CdInitializeEnumeration( IrpContext, 310 IrpSp, 311 Fcb, 312 Ccb, 313 &FileContext, 314 &ReturnNextEntry, 315 &ReturnSingleEntry, 316 &InitialQuery ); 317 318 // 319 // The current dirent is stored in the InitialDirent field. We capture 320 // this here so that we have a valid restart point even if we don't 321 // find a single entry. 322 // 323 324 ThisDirent = &FileContext.InitialDirent->Dirent; 325 326 // 327 // At this point we are about to enter our query loop. We have 328 // determined the index into the directory file to begin the 329 // search. LastEntry and NextEntry are used to index into the user 330 // buffer. LastEntry is the last entry we've added, NextEntry is 331 // current one we're working on. If NextEntry is non-zero, then 332 // at least one entry was added. 333 // 334 335 while (TRUE) { 336 337 // 338 // If the user had requested only a single match and we have 339 // returned that, then we stop at this point. We update the Ccb with 340 // the status based on the last entry returned. 341 // 342 343 if ((NextEntry != 0) && ReturnSingleEntry) { 344 345 DoCcbUpdate = TRUE; 346 try_leave( Status ); 347 } 348 349 // 350 // We try to locate the next matching dirent. Our search if based on a starting 351 // dirent offset, whether we should return the current or next entry, whether 352 // we should be doing a short name search and finally whether we should be 353 // checking for a version match. 354 // 355 356 Found = CdEnumerateIndex( IrpContext, Ccb, &FileContext, ReturnNextEntry ); 357 358 // 359 // Initialize the value for the next search. 360 // 361 362 ReturnNextEntry = TRUE; 363 364 // 365 // If we didn't receive a dirent, then we are at the end of the 366 // directory. If we have returned any files, we exit with 367 // success, otherwise we return STATUS_NO_MORE_FILES. 368 // 369 370 if (!Found) { 371 372 if (NextEntry == 0) { 373 374 Status = STATUS_NO_MORE_FILES; 375 376 if (InitialQuery) { 377 378 Status = STATUS_NO_SUCH_FILE; 379 } 380 } 381 382 DoCcbUpdate = TRUE; 383 try_leave( Status ); 384 } 385 386 // 387 // Remember the dirent for the file we just found. 388 // 389 390 ThisDirent = &FileContext.InitialDirent->Dirent; 391 392 // 393 // Here are the rules concerning filling up the buffer: 394 // 395 // 1. The Io system garentees that there will always be 396 // enough room for at least one base record. 397 // 398 // 2. If the full first record (including file name) cannot 399 // fit, as much of the name as possible is copied and 400 // STATUS_BUFFER_OVERFLOW is returned. 401 // 402 // 3. If a subsequent record cannot completely fit into the 403 // buffer, none of it (as in 0 bytes) is copied, and 404 // STATUS_SUCCESS is returned. A subsequent query will 405 // pick up with this record. 406 // 407 408 // 409 // Let's compute the number of bytes we need to transfer the current entry. 410 // 411 412 SeparatorBytes = 413 VersionStringBytes = 0; 414 415 // 416 // We can look directly at the dirent that we found. 417 // 418 419 FileNameBytes = ThisDirent->CdFileName.FileName.Length; 420 421 // 422 // Compute the number of bytes for the version string if 423 // we will return this. Allow directories with illegal ";". 424 // 425 426 if (((Ccb->SearchExpression.VersionString.Length != 0) || 427 (FlagOn(ThisDirent->DirentFlags, CD_ATTRIBUTE_DIRECTORY))) && 428 (ThisDirent->CdFileName.VersionString.Length != 0)) { 429 430 SeparatorBytes = 2; 431 432 VersionStringBytes = ThisDirent->CdFileName.VersionString.Length; 433 } 434 435 // 436 // If the slot for the next entry would be beyond the length of the 437 // user's buffer just exit (we know we've returned at least one entry 438 // already). This will happen when we align the pointer past the end. 439 // 440 441 if (NextEntry > IrpSp->Parameters.QueryDirectory.Length) { 442 443 ReturnNextEntry = FALSE; 444 DoCcbUpdate = TRUE; 445 try_leave( Status = STATUS_SUCCESS ); 446 } 447 448 // 449 // Compute the number of bytes remaining in the buffer. Round this 450 // down to a WCHAR boundary so we can copy full characters. 451 // 452 453 BytesRemainingInBuffer = IrpSp->Parameters.QueryDirectory.Length - NextEntry; 454 ClearFlag( BytesRemainingInBuffer, 1 ); 455 456 // 457 // If this won't fit and we have returned a previous entry then just 458 // return STATUS_SUCCESS. 459 // 460 461 if ((BaseLength + FileNameBytes + SeparatorBytes + VersionStringBytes) > BytesRemainingInBuffer) { 462 463 // 464 // If we already found an entry then just exit. 465 // 466 467 if (NextEntry != 0) { 468 469 ReturnNextEntry = FALSE; 470 DoCcbUpdate = TRUE; 471 try_leave( Status = STATUS_SUCCESS ); 472 } 473 474 // 475 // Don't even try to return the version string if it doesn't all fit. 476 // Reduce the FileNameBytes to just fit in the buffer. 477 // 478 479 if ((BaseLength + FileNameBytes) > BytesRemainingInBuffer) { 480 481 FileNameBytes = BytesRemainingInBuffer - BaseLength; 482 } 483 484 // 485 // Don't return any version string bytes. 486 // 487 488 VersionStringBytes = 489 SeparatorBytes = 0; 490 491 // 492 // Use a status code of STATUS_BUFFER_OVERFLOW. Also set 493 // ReturnSingleEntry so that we will exit the loop at the top. 494 // 495 496 Status = STATUS_BUFFER_OVERFLOW; 497 ReturnSingleEntry = TRUE; 498 } 499 500 // 501 // Protect access to the user buffer with an exception handler. 502 // Since (at our request) IO doesn't buffer these requests, we have 503 // to guard against a user messing with the page protection and other 504 // such trickery. 505 // 506 507 _SEH2_TRY { 508 509 // 510 // Zero and initialize the base part of the current entry. 511 // 512 513 RtlZeroMemory( Add2Ptr( UserBuffer, NextEntry, PVOID ), 514 BaseLength ); 515 516 // 517 // Now we have an entry to return to our caller. 518 // We'll case on the type of information requested and fill up 519 // the user buffer if everything fits. 520 // 521 522 switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) { 523 524 case FileBothDirectoryInformation: 525 case FileFullDirectoryInformation: 526 case FileIdBothDirectoryInformation: 527 case FileIdFullDirectoryInformation: 528 case FileDirectoryInformation: 529 530 DirInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_BOTH_DIR_INFORMATION ); 531 532 // 533 // Use the create time for all the time stamps. 534 // 535 536 CdConvertCdTimeToNtTime( IrpContext, 537 FileContext.InitialDirent->Dirent.CdTime, 538 &DirInfo->CreationTime ); 539 540 DirInfo->LastWriteTime = DirInfo->ChangeTime = DirInfo->CreationTime; 541 542 // 543 // Set the attributes and sizes separately for directories and 544 // files. 545 // 546 547 if (FlagOn( ThisDirent->DirentFlags, CD_ATTRIBUTE_DIRECTORY )) { 548 549 DirInfo->EndOfFile.QuadPart = DirInfo->AllocationSize.QuadPart = 0; 550 551 SetFlag( DirInfo->FileAttributes, FILE_ATTRIBUTE_DIRECTORY); 552 553 } else { 554 555 DirInfo->EndOfFile.QuadPart = FileContext.FileSize; 556 DirInfo->AllocationSize.QuadPart = LlSectorAlign( FileContext.FileSize ); 557 558 SetFlag( DirInfo->FileAttributes, FILE_ATTRIBUTE_READONLY); 559 } 560 561 if (FlagOn( ThisDirent->DirentFlags, 562 CD_ATTRIBUTE_HIDDEN )) { 563 564 SetFlag( DirInfo->FileAttributes, FILE_ATTRIBUTE_HIDDEN ); 565 } 566 567 DirInfo->FileIndex = ThisDirent->DirentOffset; 568 569 DirInfo->FileNameLength = FileNameBytes + SeparatorBytes + VersionStringBytes; 570 571 break; 572 573 case FileNamesInformation: 574 575 NamesInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_NAMES_INFORMATION ); 576 577 NamesInfo->FileIndex = ThisDirent->DirentOffset; 578 579 NamesInfo->FileNameLength = FileNameBytes + SeparatorBytes + VersionStringBytes; 580 581 break; 582 583 /* ReactOS Change: GCC "enumeration value not handled in switch" */ 584 default: break; 585 } 586 587 // 588 // Fill in the FileId 589 // 590 591 switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) { 592 593 case FileIdBothDirectoryInformation: 594 595 IdBothDirInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_ID_BOTH_DIR_INFORMATION ); 596 CdSetFidFromParentAndDirent( IdBothDirInfo->FileId, Fcb, ThisDirent ); 597 break; 598 599 case FileIdFullDirectoryInformation: 600 601 IdFullDirInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_ID_FULL_DIR_INFORMATION ); 602 CdSetFidFromParentAndDirent( IdFullDirInfo->FileId, Fcb, ThisDirent ); 603 break; 604 605 default: 606 break; 607 } 608 609 // 610 // Now copy as much of the name as possible. We also may have a version 611 // string to copy. 612 // 613 614 if (FileNameBytes != 0) { 615 616 // 617 // This is a Unicode name, we can copy the bytes directly. 618 // 619 620 RtlCopyMemory( Add2Ptr( UserBuffer, NextEntry + BaseLength, PVOID ), 621 ThisDirent->CdFileName.FileName.Buffer, 622 FileNameBytes ); 623 624 if (SeparatorBytes != 0) { 625 626 *(Add2Ptr( UserBuffer, 627 NextEntry + BaseLength + FileNameBytes, 628 PWCHAR )) = L';'; 629 630 if (VersionStringBytes != 0) { 631 632 RtlCopyMemory( Add2Ptr( UserBuffer, 633 NextEntry + BaseLength + FileNameBytes + sizeof( WCHAR ), 634 PVOID ), 635 ThisDirent->CdFileName.VersionString.Buffer, 636 VersionStringBytes ); 637 } 638 } 639 } 640 641 // 642 // Fill in the short name if we got STATUS_SUCCESS. The short name 643 // may already be in the file context. Otherwise we will check 644 // whether the long name is 8.3. Special case the self and parent 645 // directory names. 646 // 647 648 if ((Status == STATUS_SUCCESS) && 649 (IrpSp->Parameters.QueryDirectory.FileInformationClass == FileBothDirectoryInformation || 650 IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdBothDirectoryInformation) && 651 (Ccb->SearchExpression.VersionString.Length == 0) && 652 !FlagOn( ThisDirent->Flags, DIRENT_FLAG_CONSTANT_ENTRY )) { 653 654 // 655 // If we already have the short name then copy into the user's buffer. 656 // 657 658 if (FileContext.ShortName.FileName.Length != 0) { 659 660 RtlCopyMemory( DirInfo->ShortName, 661 FileContext.ShortName.FileName.Buffer, 662 FileContext.ShortName.FileName.Length ); 663 664 DirInfo->ShortNameLength = (CCHAR) FileContext.ShortName.FileName.Length; 665 666 // 667 // If the short name length is currently zero then check if 668 // the long name is not 8.3. We can copy the short name in 669 // unicode form directly into the caller's buffer. 670 // 671 672 } else { 673 674 if (!CdIs8dot3Name( IrpContext, 675 ThisDirent->CdFileName.FileName )) { 676 677 CdGenerate8dot3Name( IrpContext, 678 &ThisDirent->CdCaseFileName.FileName, 679 ThisDirent->DirentOffset, 680 DirInfo->ShortName, 681 &FileContext.ShortName.FileName.Length ); 682 683 DirInfo->ShortNameLength = (CCHAR) FileContext.ShortName.FileName.Length; 684 } 685 } 686 687 } 688 689 // 690 // Sum the total number of bytes for the information field. 691 // 692 693 FileNameBytes += SeparatorBytes + VersionStringBytes; 694 695 // 696 // Update the information with the number of bytes stored in the 697 // buffer. We quad-align the existing buffer to add any necessary 698 // pad bytes. 699 // 700 701 Information = NextEntry + BaseLength + FileNameBytes; 702 703 // 704 // Go back to the previous entry and fill in the update to this entry. 705 // 706 707 *(Add2Ptr( UserBuffer, LastEntry, PULONG )) = NextEntry - LastEntry; 708 709 // 710 // Set up our variables for the next dirent. 711 // 712 713 InitialQuery = FALSE; 714 715 LastEntry = NextEntry; 716 NextEntry = QuadAlign( Information ); 717 718 #ifdef _MSC_VER 719 #pragma warning(suppress: 6320) 720 #endif 721 } _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER) { 722 723 // 724 // We had a problem filling in the user's buffer, so stop and 725 // fail this request. This is the only reason any exception 726 // would have occured at this level. 727 // 728 729 Information = 0; 730 try_leave( Status = _SEH2_GetExceptionCode()); 731 } _SEH2_END; 732 } 733 734 DoCcbUpdate = TRUE; 735 736 } _SEH2_FINALLY { 737 738 // 739 // Cleanup our search context - *before* aquiring the FCB mutex exclusive, 740 // else can block on threads in cdcreateinternalstream/purge which 741 // hold the FCB but are waiting for all maps in this stream to be released. 742 // 743 744 CdCleanupFileContext( IrpContext, &FileContext ); 745 746 // 747 // Now we can safely aqure the FCB mutex if we need to. 748 // 749 750 if (DoCcbUpdate && !NT_ERROR( Status )) { 751 752 // 753 // Update the Ccb to show the current state of the enumeration. 754 // 755 756 CdLockFcb( IrpContext, Fcb ); 757 758 Ccb->CurrentDirentOffset = ThisDirent->DirentOffset; 759 760 ClearFlag( Ccb->Flags, CCB_FLAG_ENUM_RETURN_NEXT ); 761 762 if (ReturnNextEntry) { 763 764 SetFlag( Ccb->Flags, CCB_FLAG_ENUM_RETURN_NEXT ); 765 } 766 767 CdUnlockFcb( IrpContext, Fcb ); 768 } 769 770 // 771 // Release the Fcb. 772 // 773 774 CdReleaseFile( IrpContext, Fcb ); 775 } _SEH2_END; 776 777 // 778 // Complete the request here. 779 // 780 781 Irp->IoStatus.Information = Information; 782 783 CdCompleteRequest( IrpContext, Irp, Status ); 784 return Status; 785 } 786 787 788 // 789 // Local support routines 790 // 791 792 _Requires_lock_held_(_Global_critical_region_) 793 NTSTATUS 794 CdNotifyChangeDirectory ( 795 _Inout_ PIRP_CONTEXT IrpContext, 796 _Inout_ PIRP Irp, 797 _In_ PIO_STACK_LOCATION IrpSp, 798 _In_ PCCB Ccb 799 ) 800 801 /*++ 802 803 Routine Description: 804 805 This routine performs the notify change directory operation. It is 806 responsible for either completing of enqueuing the input Irp. Although there 807 will never be a notify signalled on a CDROM disk we still support this call. 808 809 We have already checked that this is not an OpenById handle. 810 811 Arguments: 812 813 Irp - Supplies the Irp to process 814 815 IrpSp - Io stack location for this request. 816 817 Ccb - Handle to the directory being watched. 818 819 Return Value: 820 821 NTSTATUS - STATUS_PENDING, any other error will raise. 822 823 --*/ 824 825 { 826 PAGED_CODE(); 827 828 // 829 // Always set the wait bit in the IrpContext so the initial wait can't fail. 830 // 831 832 SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT ); 833 834 // 835 // Acquire the Vcb shared. 836 // 837 838 CdAcquireVcbShared( IrpContext, IrpContext->Vcb, FALSE ); 839 840 // 841 // Use a try-finally to facilitate cleanup. 842 // 843 844 _SEH2_TRY { 845 846 // 847 // Verify the Vcb. 848 // 849 850 CdVerifyVcb( IrpContext, IrpContext->Vcb ); 851 852 // 853 // Call the Fsrtl package to process the request. We cast the 854 // unicode strings to ansi strings as the dir notify package 855 // only deals with memory matching. 856 // 857 858 FsRtlNotifyFullChangeDirectory( IrpContext->Vcb->NotifySync, 859 &IrpContext->Vcb->DirNotifyList, 860 Ccb, 861 (PSTRING) &IrpSp->FileObject->FileName, 862 BooleanFlagOn( IrpSp->Flags, SL_WATCH_TREE ), 863 FALSE, 864 IrpSp->Parameters.NotifyDirectory.CompletionFilter, 865 Irp, 866 NULL, 867 NULL ); 868 869 } _SEH2_FINALLY { 870 871 // 872 // Release the Vcb. 873 // 874 875 CdReleaseVcb( IrpContext, IrpContext->Vcb ); 876 } _SEH2_END; 877 878 // 879 // Cleanup the IrpContext. 880 // 881 882 CdCompleteRequest( IrpContext, NULL, STATUS_SUCCESS ); 883 884 return STATUS_PENDING; 885 } 886 887 888 // 889 // Local support routine 890 // 891 892 VOID 893 CdInitializeEnumeration ( 894 _In_ PIRP_CONTEXT IrpContext, 895 _In_ PIO_STACK_LOCATION IrpSp, 896 _In_ PFCB Fcb, 897 _Inout_ PCCB Ccb, 898 _Inout_ PFILE_ENUM_CONTEXT FileContext, 899 _Out_ PBOOLEAN ReturnNextEntry, 900 _Out_ PBOOLEAN ReturnSingleEntry, 901 _Out_ PBOOLEAN InitialQuery 902 ) 903 904 /*++ 905 906 Routine Description: 907 908 This routine is called to initialize the enumeration variables and structures. 909 We look at the state of a previous enumeration from the Ccb as well as any 910 input values from the user. On exit we will position the FileContext at 911 a file in the directory and let the caller know whether this entry or the 912 next entry should be returned. 913 914 Arguments: 915 916 IrpSp - Irp stack location for this request. 917 918 Fcb - Fcb for this directory. 919 920 Ccb - Ccb for the directory handle. 921 922 FileContext - FileContext to use for this enumeration. 923 924 ReturnNextEntry - Address to store whether we should return the entry at 925 the FileContext position or the next entry. 926 927 ReturnSingleEntry - Address to store whether we should only return 928 a single entry. 929 930 InitialQuery - Address to store whether this is the first enumeration 931 query on this handle. 932 933 Return Value: 934 935 None. 936 937 --*/ 938 939 { 940 NTSTATUS Status; 941 942 PUNICODE_STRING FileName; 943 CD_NAME WildCardName; 944 CD_NAME SearchExpression; 945 946 ULONG CcbFlags; 947 948 ULONG DirentOffset; 949 ULONG LastDirentOffset; 950 BOOLEAN KnownOffset; 951 952 BOOLEAN Found; 953 954 PAGED_CODE(); 955 956 // 957 // If the user has specified that the scan be restarted, and has specicified 958 // a new query pattern, reinitialize the CCB. 959 // 960 961 if (FlagOn( IrpSp->Flags, SL_RESTART_SCAN )) { 962 963 CdLockFcb( IrpContext, Fcb ); 964 965 FileName = (PUNICODE_STRING) IrpSp->Parameters.QueryDirectory.FileName; 966 if (FileName && FileName->Length > 0) { 967 968 if (!FlagOn( Ccb->Flags, CCB_FLAG_ENUM_MATCH_ALL )) { 969 970 CdFreePool( &Ccb->SearchExpression.FileName.Buffer ); 971 } 972 973 ClearFlag(Ccb->Flags, CCB_FLAG_ENUM_MATCH_ALL); 974 ClearFlag(Ccb->Flags, CCB_FLAG_ENUM_INITIALIZED); 975 ClearFlag(Ccb->Flags, CCB_FLAG_ENUM_NAME_EXP_HAS_WILD); 976 } 977 978 CdUnlockFcb( IrpContext, Fcb ); 979 } 980 981 // 982 // If this is the initial query then build a search expression from the input 983 // file name. 984 // 985 986 if (!FlagOn( Ccb->Flags, CCB_FLAG_ENUM_INITIALIZED )) { 987 988 FileName = IrpSp->Parameters.QueryDirectory.FileName; 989 990 CcbFlags = 0; 991 992 // 993 // If the filename is not specified or is a single '*' then we will 994 // match all names. 995 // 996 997 if ((FileName == NULL) || 998 (FileName->Buffer == NULL) || 999 (FileName->Length == 0) || 1000 ((FileName->Length == sizeof( WCHAR )) && 1001 (FileName->Buffer[0] == L'*'))) { 1002 1003 SetFlag( CcbFlags, CCB_FLAG_ENUM_MATCH_ALL ); 1004 RtlZeroMemory( &SearchExpression, sizeof( SearchExpression )); 1005 1006 // 1007 // Otherwise build the CdName from the name in the stack location. 1008 // This involves building both the name and version portions and 1009 // checking for wild card characters. We also upcase the string if 1010 // this is a case-insensitive search. 1011 // 1012 1013 } else { 1014 1015 // 1016 // Create a CdName to check for wild cards. 1017 // 1018 1019 WildCardName.FileName = *FileName; 1020 1021 CdConvertNameToCdName( IrpContext, &WildCardName ); 1022 1023 // 1024 // The name better have at least one character. 1025 // 1026 1027 if (WildCardName.FileName.Length == 0) { 1028 1029 CdRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER ); 1030 } 1031 1032 // 1033 // Check for wildcards in the separate components. 1034 // 1035 1036 if (FsRtlDoesNameContainWildCards( &WildCardName.FileName)) { 1037 1038 SetFlag( CcbFlags, CCB_FLAG_ENUM_NAME_EXP_HAS_WILD ); 1039 } 1040 1041 if ((WildCardName.VersionString.Length != 0) && 1042 (FsRtlDoesNameContainWildCards( &WildCardName.VersionString ))) { 1043 1044 SetFlag( CcbFlags, CCB_FLAG_ENUM_VERSION_EXP_HAS_WILD ); 1045 1046 // 1047 // Check if this is a wild card only and match all version 1048 // strings. 1049 // 1050 1051 if ((WildCardName.VersionString.Length == sizeof( WCHAR )) && 1052 (WildCardName.VersionString.Buffer[0] == L'*')) { 1053 1054 SetFlag( CcbFlags, CCB_FLAG_ENUM_VERSION_MATCH_ALL ); 1055 } 1056 } 1057 1058 // 1059 // Now create the search expression to store in the Ccb. 1060 // 1061 1062 SearchExpression.FileName.Buffer = FsRtlAllocatePoolWithTag( CdPagedPool, 1063 FileName->Length, 1064 TAG_ENUM_EXPRESSION ); 1065 1066 SearchExpression.FileName.MaximumLength = FileName->Length; 1067 1068 // 1069 // Either copy the name directly or perform the upcase. 1070 // 1071 1072 if (FlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE )) { 1073 1074 Status = RtlUpcaseUnicodeString( (PUNICODE_STRING) &SearchExpression.FileName, 1075 FileName, 1076 FALSE ); 1077 1078 // 1079 // This should never fail. 1080 // 1081 __analysis_assert( Status == STATUS_SUCCESS ); 1082 NT_ASSERT( Status == STATUS_SUCCESS ); 1083 1084 } else { 1085 1086 RtlCopyMemory( SearchExpression.FileName.Buffer, 1087 FileName->Buffer, 1088 FileName->Length ); 1089 } 1090 1091 // 1092 // Now split into the separate name and version components. 1093 // 1094 1095 SearchExpression.FileName.Length = WildCardName.FileName.Length; 1096 SearchExpression.VersionString.Length = WildCardName.VersionString.Length; 1097 SearchExpression.VersionString.MaximumLength = WildCardName.VersionString.MaximumLength; 1098 1099 SearchExpression.VersionString.Buffer = Add2Ptr( SearchExpression.FileName.Buffer, 1100 SearchExpression.FileName.Length + sizeof( WCHAR ), 1101 PWCHAR ); 1102 } 1103 1104 // 1105 // But we do not want to return the constant "." and ".." entries for 1106 // the root directory, for consistency with the rest of Microsoft's 1107 // filesystems. 1108 // 1109 1110 if (Fcb == Fcb->Vcb->RootIndexFcb) { 1111 1112 SetFlag( CcbFlags, CCB_FLAG_ENUM_NOMATCH_CONSTANT_ENTRY ); 1113 } 1114 1115 // 1116 // Now lock the Fcb in order to update the Ccb with the inital 1117 // enumeration values. 1118 // 1119 1120 CdLockFcb( IrpContext, Fcb ); 1121 1122 // 1123 // Check again that this is the initial search. 1124 // 1125 1126 if (!FlagOn( Ccb->Flags, CCB_FLAG_ENUM_INITIALIZED )) { 1127 1128 // 1129 // Update the values in the Ccb. 1130 // 1131 1132 Ccb->CurrentDirentOffset = Fcb->StreamOffset; 1133 Ccb->SearchExpression = SearchExpression; 1134 1135 // 1136 // Set the appropriate flags in the Ccb. 1137 // 1138 1139 SetFlag( Ccb->Flags, CcbFlags | CCB_FLAG_ENUM_INITIALIZED ); 1140 1141 // 1142 // Otherwise cleanup any buffer allocated here. 1143 // 1144 1145 } else { 1146 1147 if (!FlagOn( CcbFlags, CCB_FLAG_ENUM_MATCH_ALL )) { 1148 1149 CdFreePool( &SearchExpression.FileName.Buffer ); 1150 } 1151 } 1152 1153 // 1154 // Otherwise lock the Fcb so we can read the current enumeration values. 1155 // 1156 1157 } else { 1158 1159 CdLockFcb( IrpContext, Fcb ); 1160 } 1161 1162 // 1163 // Capture the current state of the enumeration. 1164 // 1165 // If the user specified an index then use his offset. We always 1166 // return the next entry in this case. 1167 // 1168 1169 if (FlagOn( IrpSp->Flags, SL_INDEX_SPECIFIED )) { 1170 1171 KnownOffset = FALSE; 1172 DirentOffset = IrpSp->Parameters.QueryDirectory.FileIndex; 1173 *ReturnNextEntry = TRUE; 1174 1175 // 1176 // If we are restarting the scan then go from the self entry. 1177 // 1178 1179 } else if (FlagOn( IrpSp->Flags, SL_RESTART_SCAN )) { 1180 1181 KnownOffset = TRUE; 1182 DirentOffset = Fcb->StreamOffset; 1183 *ReturnNextEntry = FALSE; 1184 1185 // 1186 // Otherwise use the values from the Ccb. 1187 // 1188 1189 } else { 1190 1191 KnownOffset = TRUE; 1192 DirentOffset = Ccb->CurrentDirentOffset; 1193 *ReturnNextEntry = BooleanFlagOn( Ccb->Flags, CCB_FLAG_ENUM_RETURN_NEXT ); 1194 } 1195 1196 // 1197 // Unlock the Fcb. 1198 // 1199 1200 CdUnlockFcb( IrpContext, Fcb ); 1201 1202 // 1203 // We have the starting offset in the directory and whether to return 1204 // that entry or the next. If we are at the beginning of the directory 1205 // and are returning that entry, then tell our caller this is the 1206 // initial query. 1207 // 1208 1209 *InitialQuery = FALSE; 1210 1211 if ((DirentOffset == Fcb->StreamOffset) && 1212 !(*ReturnNextEntry)) { 1213 1214 *InitialQuery = TRUE; 1215 } 1216 1217 // 1218 // If there is no file object then create it now. 1219 // 1220 1221 CdVerifyOrCreateDirStreamFile( IrpContext, Fcb); 1222 1223 // 1224 // Determine the offset in the stream to position the FileContext and 1225 // whether this offset is known to be a file offset. 1226 // 1227 // If this offset is known to be safe then go ahead and position the 1228 // file context. This handles the cases where the offset is the beginning 1229 // of the stream, the offset is from a previous search or this is the 1230 // initial query. 1231 // 1232 1233 if (KnownOffset) { 1234 1235 CdLookupInitialFileDirent( IrpContext, Fcb, FileContext, DirentOffset ); 1236 1237 // 1238 // Otherwise we walk through the directory from the beginning until 1239 // we reach the entry which contains this offset. 1240 // 1241 1242 } else { 1243 1244 LastDirentOffset = Fcb->StreamOffset; 1245 Found = TRUE; 1246 1247 CdLookupInitialFileDirent( IrpContext, Fcb, FileContext, LastDirentOffset ); 1248 1249 // 1250 // If the requested offset is prior to the beginning offset in the stream 1251 // then don't return the next entry. 1252 // 1253 1254 if (DirentOffset < LastDirentOffset) { 1255 1256 *ReturnNextEntry = FALSE; 1257 1258 // 1259 // Else look for the last entry which ends past the desired index. 1260 // 1261 1262 } else { 1263 1264 // 1265 // Keep walking through the directory until we run out of 1266 // entries or we find an entry which ends beyond the input 1267 // index value. 1268 // 1269 1270 do { 1271 1272 // 1273 // If we have passed the index value then exit. 1274 // 1275 1276 if (FileContext->InitialDirent->Dirent.DirentOffset > DirentOffset) { 1277 1278 Found = FALSE; 1279 break; 1280 } 1281 1282 // 1283 // Remember the current position in case we need to go back. 1284 // 1285 1286 LastDirentOffset = FileContext->InitialDirent->Dirent.DirentOffset; 1287 1288 // 1289 // Exit if the next entry is beyond the desired index value. 1290 // 1291 1292 if (LastDirentOffset + FileContext->InitialDirent->Dirent.DirentLength > DirentOffset) { 1293 1294 break; 1295 } 1296 1297 Found = CdLookupNextInitialFileDirent( IrpContext, Fcb, FileContext ); 1298 1299 } while (Found); 1300 1301 // 1302 // If we didn't find the entry then go back to the last known entry. 1303 // This can happen if the index lies in the unused range at the 1304 // end of a sector. 1305 // 1306 1307 if (!Found) { 1308 1309 CdCleanupFileContext( IrpContext, FileContext ); 1310 CdInitializeFileContext( IrpContext, FileContext ); 1311 1312 CdLookupInitialFileDirent( IrpContext, Fcb, FileContext, LastDirentOffset ); 1313 } 1314 } 1315 } 1316 1317 // 1318 // Only update the dirent name if we will need it for some reason. 1319 // Don't update this name if we are returning the next entry and 1320 // the search string has a version component. 1321 // 1322 1323 FileContext->ShortName.FileName.Length = 0; 1324 1325 if (!(*ReturnNextEntry) || 1326 (Ccb->SearchExpression.VersionString.Length == 0)) { 1327 1328 // 1329 // Update the name in the dirent into filename and version components. 1330 // 1331 1332 CdUpdateDirentName( IrpContext, 1333 &FileContext->InitialDirent->Dirent, 1334 FlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE )); 1335 } 1336 1337 // 1338 // Look at the flag in the IrpSp indicating whether to return just 1339 // one entry. 1340 // 1341 1342 *ReturnSingleEntry = FALSE; 1343 1344 if (FlagOn( IrpSp->Flags, SL_RETURN_SINGLE_ENTRY )) { 1345 1346 *ReturnSingleEntry = TRUE; 1347 } 1348 1349 return; 1350 } 1351 1352 1353 // 1354 // Local support routine 1355 // 1356 1357 BOOLEAN 1358 CdEnumerateIndex ( 1359 _In_ PIRP_CONTEXT IrpContext, 1360 _In_ PCCB Ccb, 1361 _Inout_ PFILE_ENUM_CONTEXT FileContext, 1362 _In_ BOOLEAN ReturnNextEntry 1363 ) 1364 1365 /*++ 1366 1367 Routine Description: 1368 1369 This routine is the worker routine for index enumeration. We are positioned 1370 at some dirent in the directory and will either return the first match 1371 at that point or look to the next entry. The Ccb contains details about 1372 the type of matching to do. If the user didn't specify a version in 1373 his search string then we only return the first version of a sequence 1374 of files with versions. We also don't return any associated files. 1375 1376 Arguments: 1377 1378 Ccb - Ccb for this directory handle. 1379 1380 FileContext - File context already positioned at some entry in the directory. 1381 1382 ReturnNextEntry - Indicates if we are returning this entry or should start 1383 with the next entry. 1384 1385 Return Value: 1386 1387 BOOLEAN - TRUE if next entry is found, FALSE otherwise. 1388 1389 --*/ 1390 1391 { 1392 PDIRENT PreviousDirent = NULL; 1393 PDIRENT ThisDirent = &FileContext->InitialDirent->Dirent; 1394 1395 BOOLEAN Found = FALSE; 1396 1397 PAGED_CODE(); 1398 1399 // 1400 // Loop until we find a match or exaust the directory. 1401 // 1402 1403 while (TRUE) { 1404 1405 // 1406 // Move to the next entry unless we want to consider the current 1407 // entry. 1408 // 1409 1410 if (ReturnNextEntry) { 1411 1412 if (!CdLookupNextInitialFileDirent( IrpContext, Ccb->Fcb, FileContext )) { 1413 1414 break; 1415 } 1416 1417 PreviousDirent = ThisDirent; 1418 ThisDirent = &FileContext->InitialDirent->Dirent; 1419 1420 CdUpdateDirentName( IrpContext, ThisDirent, FlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE )); 1421 1422 } else { 1423 1424 ReturnNextEntry = TRUE; 1425 } 1426 1427 // 1428 // Don't bother if we have a constant entry and are ignoring them. 1429 // 1430 1431 if (FlagOn( ThisDirent->Flags, DIRENT_FLAG_CONSTANT_ENTRY ) && 1432 FlagOn( Ccb->Flags, CCB_FLAG_ENUM_NOMATCH_CONSTANT_ENTRY )) { 1433 1434 continue; 1435 } 1436 1437 // 1438 // Look at the current entry if it is not an associated file 1439 // and the name doesn't match the previous file if the version 1440 // name is not part of the search. 1441 // 1442 1443 if (!FlagOn( ThisDirent->DirentFlags, CD_ATTRIBUTE_ASSOC )) { 1444 1445 // 1446 // Check if this entry matches the previous entry except 1447 // for version number and whether we should return the 1448 // entry in that case. Go directly to the name comparison 1449 // if: 1450 // 1451 // There is no previous entry. 1452 // The search expression has a version component. 1453 // The name length doesn't match the length of the previous entry. 1454 // The base name strings don't match. 1455 // 1456 1457 if ((PreviousDirent == NULL) || 1458 (Ccb->SearchExpression.VersionString.Length != 0) || 1459 (PreviousDirent->CdCaseFileName.FileName.Length != ThisDirent->CdCaseFileName.FileName.Length) || 1460 FlagOn( PreviousDirent->DirentFlags, CD_ATTRIBUTE_ASSOC ) || 1461 !RtlEqualMemory( PreviousDirent->CdCaseFileName.FileName.Buffer, 1462 ThisDirent->CdCaseFileName.FileName.Buffer, 1463 ThisDirent->CdCaseFileName.FileName.Length )) { 1464 1465 // 1466 // If we match all names then return to our caller. 1467 // 1468 1469 if (FlagOn( Ccb->Flags, CCB_FLAG_ENUM_MATCH_ALL )) { 1470 1471 FileContext->ShortName.FileName.Length = 0; 1472 Found = TRUE; 1473 break; 1474 } 1475 1476 // 1477 // Check if the long name matches the search expression. 1478 // 1479 1480 if (CdIsNameInExpression( IrpContext, 1481 &ThisDirent->CdCaseFileName, 1482 &Ccb->SearchExpression, 1483 Ccb->Flags, 1484 TRUE )) { 1485 1486 // 1487 // Let our caller know we found an entry. 1488 // 1489 1490 Found = TRUE; 1491 FileContext->ShortName.FileName.Length = 0; 1492 break; 1493 } 1494 1495 // 1496 // The long name didn't match so we need to check for a 1497 // possible short name match. There is no match if the 1498 // long name is 8dot3 or the search expression has a 1499 // version component. Special case the self and parent 1500 // entries. 1501 // 1502 1503 if ((Ccb->SearchExpression.VersionString.Length == 0) && 1504 !FlagOn( ThisDirent->Flags, DIRENT_FLAG_CONSTANT_ENTRY ) && 1505 !CdIs8dot3Name( IrpContext, 1506 ThisDirent->CdFileName.FileName )) { 1507 1508 CdGenerate8dot3Name( IrpContext, 1509 &ThisDirent->CdCaseFileName.FileName, 1510 ThisDirent->DirentOffset, 1511 FileContext->ShortName.FileName.Buffer, 1512 &FileContext->ShortName.FileName.Length ); 1513 1514 // 1515 // Check if this name matches. 1516 // 1517 1518 if (CdIsNameInExpression( IrpContext, 1519 &FileContext->ShortName, 1520 &Ccb->SearchExpression, 1521 Ccb->Flags, 1522 FALSE )) { 1523 1524 // 1525 // Let our caller know we found an entry. 1526 // 1527 1528 Found = TRUE; 1529 break; 1530 } 1531 } 1532 } 1533 } 1534 } 1535 1536 // 1537 // If we found the entry then make sure we walk through all of the 1538 // file dirents. 1539 // 1540 1541 if (Found) { 1542 1543 CdLookupLastFileDirent( IrpContext, Ccb->Fcb, FileContext ); 1544 } 1545 1546 return Found; 1547 } 1548 1549 1550