xref: /reactos/drivers/filesystems/cdfs/dirctrl.c (revision cc439606)
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