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