xref: /reactos/drivers/filesystems/cdfs/namesup.c (revision f4d29a74)
1 /*++
2 
3 Copyright (c) 1991-2000 Microsoft Corporation
4 
5 Module Name:
6 
7     NameSup.c
8 
9 Abstract:
10 
11     This module implements the Cdfs Name support routines
12 
13 
14 --*/
15 
16 #include "cdprocs.h"
17 
18 //
19 //  The Bug check file id for this module
20 //
21 
22 #define BugCheckFileId                   (CDFS_BUG_CHECK_NAMESUP)
23 
24 #ifdef ALLOC_PRAGMA
25 #pragma alloc_text(PAGE, CdConvertBigToLittleEndian)
26 #pragma alloc_text(PAGE, CdConvertNameToCdName)
27 #pragma alloc_text(PAGE, CdDissectName)
28 #pragma alloc_text(PAGE, CdGenerate8dot3Name)
29 #pragma alloc_text(PAGE, CdFullCompareNames)
30 #pragma alloc_text(PAGE, CdIsLegalName)
31 #pragma alloc_text(PAGE, CdIs8dot3Name)
32 #pragma alloc_text(PAGE, CdIsNameInExpression)
33 #pragma alloc_text(PAGE, CdShortNameDirentOffset)
34 #pragma alloc_text(PAGE, CdUpcaseName)
35 #endif
36 
37 
38 _Post_satisfies_(_Old_(CdName->FileName.Length) >=
39                  CdName->FileName.Length + CdName->VersionString.Length)
40 VOID
41 CdConvertNameToCdName (
42     _In_ PIRP_CONTEXT IrpContext,
43     _Inout_ PCD_NAME CdName
44     )
45 
46 /*++
47 
48 Routine Description:
49 
50     This routine is called to convert a string of bytes into a CdName.
51 
52     The full name is already in the CdName structure in the FileName field.
53     We split this into the filename and version strings.
54 
55 Arguments:
56 
57     CdName - Pointer to CdName structure to update.
58 
59 Return Value:
60 
61     None.
62 
63 --*/
64 
65 {
66     ULONG NameLength = 0;
67     PWCHAR CurrentCharacter = CdName->FileName.Buffer;
68 
69     PAGED_CODE();
70 
71     UNREFERENCED_PARAMETER( IrpContext );
72 
73     //
74     //  Look for a separator character.
75     //
76 
77     while ((NameLength < CdName->FileName.Length) &&
78            (*CurrentCharacter != L';')) {
79 
80         CurrentCharacter += 1;
81         NameLength += 2;
82     }
83 
84     //
85     //  If there is at least one more character after a possible separator then it
86     //  and all following characters are part of the version string.
87     //
88 
89     CdName->VersionString.Length = 0;
90     if (NameLength + sizeof( WCHAR ) < CdName->FileName.Length) {
91 
92         CdName->VersionString.MaximumLength =
93         CdName->VersionString.Length = (USHORT) (CdName->FileName.Length - NameLength - sizeof( WCHAR ));
94         CdName->VersionString.Buffer = Add2Ptr( CdName->FileName.Buffer,
95                                                 NameLength + sizeof( WCHAR ),
96                                                 PWCHAR );
97     }
98 
99     //
100     //  Now update the filename portion of the name.
101     //
102 
103     CdName->FileName.Length = (USHORT) NameLength;
104 
105     return;
106 }
107 
108 
109 VOID
110 CdConvertBigToLittleEndian (
111     _In_ PIRP_CONTEXT IrpContext,
112     _In_reads_bytes_(ByteCount) PCHAR BigEndian,
113     _In_ ULONG ByteCount,
114     _Out_writes_bytes_(ByteCount) PCHAR LittleEndian
115     )
116 
117 /*++
118 
119 Routine Description:
120 
121     This routine is called to convert a unicode string in big endian to
122     little endian.  We start by copying all of the source bytes except
123     the first.  This will put the low order bytes in the correct position.
124     We then copy each high order byte in its correct position.
125 
126 Arguments:
127 
128     BigEndian - Pointer to the string of big endian characters.
129 
130     ByteCount - Number of unicode characters in this string.
131 
132     LittleEndian - Pointer to array to store the little endian characters.
133 
134 Return Value:
135 
136     None.
137 
138 --*/
139 
140 {
141     ULONG RemainingByteCount = ByteCount;
142 
143     PCHAR Source = BigEndian;
144     PCHAR Destination = LittleEndian;
145 
146     PAGED_CODE();
147 
148     //
149     //  If the byte count isn't an even number then the disk is corrupt.
150     //
151 
152     if (FlagOn( ByteCount, 1 )) {
153 
154         CdRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR );
155     }
156 
157     //
158     //  Start by copy the low-order bytes into the correct position.  Do
159     //  this by skipping the first byte in the BigEndian string.
160     //
161 
162     RtlCopyMemory( Destination,
163                    Source + 1,
164                    RemainingByteCount - 1 );
165 
166     //
167     //  Now move the high-order bytes into position.
168     //
169 
170     Destination += 1;
171 
172     while (RemainingByteCount != 0) {
173 
174 #ifdef _MSC_VER
175 #pragma prefast(push)
176 #pragma prefast(suppress:26014, "RemainingByteCount is even")
177 #endif
178         *Destination = *Source;
179 #ifdef _MSC_VER
180 #pragma prefast(pop)
181 #endif
182 
183         Source += 2;
184         Destination += 2;
185 
186         RemainingByteCount -= 2;
187     }
188 
189     return;
190 }
191 
192 
193 VOID
194 CdUpcaseName (
195     _In_ PIRP_CONTEXT IrpContext,
196     _In_ PCD_NAME Name,
197     _Inout_ PCD_NAME UpcaseName
198     )
199 
200 /*++
201 
202 Routine Description:
203 
204     This routine is called to upcase a CdName structure.  We will do both
205     the filename and version strings.
206 
207 Arguments:
208 
209     Name - This is the mixed case version of the name.
210 
211     UpcaseName - This is the destination for the upcase operation.
212 
213 Return Value:
214 
215     None.  This routine will raise all errors.
216 
217 --*/
218 
219 {
220     NTSTATUS Status;
221 
222     PAGED_CODE();
223 
224     UNREFERENCED_PARAMETER( IrpContext );
225 
226     //
227     //  If the name structures are different then initialize the different components.
228     //
229 
230     if (Name != UpcaseName) {
231 
232         //
233         //  Initialize the version string portion of the name.
234         //
235 
236         UpcaseName->VersionString.Length = 0;
237 
238         if (Name->VersionString.Length != 0) {
239 
240             UpcaseName->VersionString.MaximumLength =
241             UpcaseName->VersionString.Length = Name->VersionString.Length;
242 
243             //
244             //  Initially set the buffer to point to where we need to insert
245             //  the separator.
246             //
247 
248             UpcaseName->VersionString.Buffer = Add2Ptr( UpcaseName->FileName.Buffer,
249                                                         Name->FileName.Length,
250                                                         PWCHAR );
251 
252             //
253             //  We are currently pointing to the location to store the separator.
254             //  Store the ';' and then move to the next character to
255             //  copy the data.
256             //
257 
258 #ifdef _MSC_VER
259 #pragma prefast( suppress:26015, "CD_NAME structures have two UNICODE_STRING structures pointing to the same allocation. there is no way to tell prefast this is the case and that the allocation is always big enough.");
260 #endif
261             *(UpcaseName->VersionString.Buffer) = L';';
262 
263             UpcaseName->VersionString.Buffer += 1;
264         }
265     }
266 
267     //
268     //  Upcase the string using the correct upcase routine.
269     //
270 
271     Status = RtlUpcaseUnicodeString( &UpcaseName->FileName,
272                                      &Name->FileName,
273                                      FALSE );
274 
275     //
276     //  This should never fail.
277     //
278 
279     NT_ASSERT( Status == STATUS_SUCCESS );
280     __analysis_assert( Status == STATUS_SUCCESS );
281 
282     if (Name->VersionString.Length != 0) {
283 
284         Status = RtlUpcaseUnicodeString( &UpcaseName->VersionString,
285                                          &Name->VersionString,
286                                          FALSE );
287 
288         //
289         //  This should never fail.
290         //
291 
292         NT_ASSERT( Status == STATUS_SUCCESS );
293         __analysis_assert( Status == STATUS_SUCCESS );
294     }
295 
296     return;
297 }
298 
299 
300 VOID
301 CdDissectName (
302     _In_ PIRP_CONTEXT IrpContext,
303     _Inout_ PUNICODE_STRING RemainingName,
304     _Out_ PUNICODE_STRING FinalName
305     )
306 
307 /*++
308 
309 Routine Description:
310 
311     This routine is called to strip off leading components of the name strings.  We search
312     for either the end of the string or separating characters.  The input remaining
313     name strings should have neither a trailing or leading backslash.
314 
315 Arguments:
316 
317     RemainingName - Remaining name.
318 
319     FinalName - Location to store next component of name.
320 
321 Return Value:
322 
323     None.
324 
325 --*/
326 
327 {
328     ULONG NameLength;
329     PWCHAR NextWchar;
330 
331     PAGED_CODE();
332 
333     UNREFERENCED_PARAMETER( IrpContext );
334 
335     //
336     //  Find the offset of the next component separators.
337     //
338 
339     for (NameLength = 0, NextWchar = RemainingName->Buffer;
340          (NameLength < RemainingName->Length) && (*NextWchar != L'\\');
341          NameLength += sizeof( WCHAR) , NextWchar += 1);
342 
343     //
344     //  Adjust all the strings by this amount.
345     //
346 
347     FinalName->Buffer = RemainingName->Buffer;
348 
349     FinalName->MaximumLength = FinalName->Length = (USHORT) NameLength;
350 
351     //
352     //  If this is the last component then set the RemainingName lengths to zero.
353     //
354 
355     if (NameLength == RemainingName->Length) {
356 
357         RemainingName->Length = 0;
358 
359     //
360     //  Otherwise we adjust the string by this amount plus the separating character.
361     //
362 
363     } else {
364 
365         RemainingName->MaximumLength -= (USHORT) (NameLength + sizeof( WCHAR ));
366         RemainingName->Length -= (USHORT) (NameLength + sizeof( WCHAR ));
367         RemainingName->Buffer = Add2Ptr( RemainingName->Buffer,
368                                          NameLength + sizeof( WCHAR ),
369                                          PWCHAR );
370     }
371 
372     return;
373 }
374 
375 
376 BOOLEAN
377 CdIsLegalName (
378     _In_ PIRP_CONTEXT IrpContext,
379     _In_ PUNICODE_STRING FileName
380     )
381 
382 /*++
383 
384 Routine Description:
385 
386     This routine checks if the name is a legal ISO 9660 name.
387 
388 Arguments:
389 
390     FileName - String of bytes containing the name.
391 
392 Return Value:
393 
394     BOOLEAN - TRUE if this name is a legal, FALSE otherwise.
395 
396 --*/
397 
398 {
399     PWCHAR Wchar;
400 
401     PAGED_CODE();
402 
403     UNREFERENCED_PARAMETER( IrpContext );
404 
405     //
406     //  Check if name corresponds to a legal file name.
407     //
408 
409     for (Wchar = FileName->Buffer;
410          Wchar < Add2Ptr( FileName->Buffer, FileName->Length, PWCHAR );
411          Wchar++) {
412 
413         if ((*Wchar < 0xff) &&
414             !FsRtlIsAnsiCharacterLegalHpfs( *Wchar, FALSE ) &&
415             (*Wchar != L'"') &&
416             (*Wchar != L'<') &&
417             (*Wchar != L'>') &&
418             (*Wchar != L'|')) {
419 
420             return FALSE;
421         }
422     }
423 
424     return TRUE;
425 }
426 
427 
428 BOOLEAN
429 CdIs8dot3Name (
430     _In_ PIRP_CONTEXT IrpContext,
431     _In_ UNICODE_STRING FileName
432     )
433 
434 /*++
435 
436 Routine Description:
437 
438     This routine checks if the name follows the 8.3 name conventions.  We check for
439     the name length and whether the characters are valid.
440 
441 Arguments:
442 
443     FileName - String of bytes containing the name.
444 
445 Return Value:
446 
447     BOOLEAN - TRUE if this name is a legal 8.3 name, FALSE otherwise.
448 
449 --*/
450 
451 {
452     CHAR DbcsNameBuffer[ BYTE_COUNT_8_DOT_3 ];
453     STRING DbcsName = {0};
454 
455     PWCHAR NextWchar;
456     ULONG Count;
457 
458     ULONG DotCount = 0;
459     BOOLEAN LastCharDot = FALSE;
460 
461     PAGED_CODE();
462 
463     UNREFERENCED_PARAMETER( IrpContext );
464 
465     //
466     //  The length must be less than 24 bytes.
467     //
468 
469     NT_ASSERT( FileName.Length != 0 );
470     if (FileName.Length > BYTE_COUNT_8_DOT_3) {
471 
472         return FALSE;
473     }
474 
475     //
476     //  Walk though and check for a space character.
477     //
478 
479     NextWchar = FileName.Buffer;
480     Count = 0;
481 
482     do {
483 
484         //
485         //  No spaces allowed.
486         //
487 
488         if (*NextWchar == L' ') { return FALSE; }
489 
490         if (*NextWchar == L'.') {
491 
492             //
493             //  Not an 8.3 name if more than 1 dot or more than 8 characters
494             //  remaining.  (It is legal for the dot to be in the ninth
495             //  position)
496             //
497 
498             if ((DotCount > 0) ||
499                 (Count > 8 * sizeof( WCHAR ))) {
500 
501                 return FALSE;
502             }
503 
504             DotCount += 1;
505             LastCharDot = TRUE;
506 
507         } else {
508 
509             LastCharDot = FALSE;
510         }
511 
512         Count += 2;
513         NextWchar += 1;
514 
515     } while (Count < FileName.Length);
516 
517     //
518     //  Go ahead and truncate the dot if at the end.
519     //
520 
521     if (LastCharDot) {
522 
523         FileName.Length -= sizeof( WCHAR );
524     }
525 
526     //
527     //  Create an Oem name to use to check for a valid short name.
528     //
529 
530     DbcsName.MaximumLength = BYTE_COUNT_8_DOT_3;
531     DbcsName.Buffer = DbcsNameBuffer;
532 
533     if (!NT_SUCCESS( RtlUnicodeStringToCountedOemString( &DbcsName,
534                                                          &FileName,
535                                                          FALSE ))) {
536 
537         return FALSE;
538     }
539 
540     //
541     //  We have now initialized the Oem string.  Call the FsRtl package to check for a
542     //  valid FAT name.
543     //
544 
545     return FsRtlIsFatDbcsLegal( DbcsName, FALSE, FALSE, FALSE );
546 }
547 
548 
549 VOID
550 CdGenerate8dot3Name (
551     _In_ PIRP_CONTEXT IrpContext,
552     _In_ PUNICODE_STRING FileName,
553     _In_ ULONG DirentOffset,
554     _Out_writes_bytes_to_(BYTE_COUNT_8_DOT_3, *ShortByteCount) PWCHAR ShortFileName,
555     _Out_ PUSHORT ShortByteCount
556     )
557 
558 /*++
559 
560 Routine Description:
561 
562     This routine is called to generate a short name from the given long name.  We will
563     generate a short name from the given long name.
564 
565     We go through the following steps to make this conversion.
566 
567         1 - Generate the generic short name.  This will also be in unicode format.
568 
569         2 - Build the string representation of the dirent offset.
570 
571         3 - Build the biased short name string by combining the generic short name with
572             the dirent offset string.
573 
574         4 - Copy the final unicode string back to our caller's buffer.
575 
576 Arguments:
577 
578     FileName - String of bytes containing the name.
579 
580     DirentOffset - Offset in the directory for this filename.  We incorporate the offset into
581         the short name by dividing this by 32 and prepending a tilde character to the
582         digit character.  We then append this to the base of the generated short name.
583 
584     ShortFileName - Pointer to the buffer to store the short name into.
585 
586     ShortByteCount - Address to store the number of bytes in the short name.
587 
588 Return Value:
589 
590     None.
591 
592 --*/
593 
594 {
595     NTSTATUS Status;
596 
597     UNICODE_STRING ShortName;
598     UNICODE_STRING BiasedShortName;
599     WCHAR ShortNameBuffer[ BYTE_COUNT_8_DOT_3 / sizeof( WCHAR ) ] = {0};
600     WCHAR BiasedShortNameBuffer[ BYTE_COUNT_8_DOT_3 / sizeof( WCHAR ) ];
601 
602     GENERATE_NAME_CONTEXT NameContext;
603 
604     ULONG BiasedDirentOffset;
605 
606     ULONG MaximumBaseBytes;
607     ULONG BaseNameOffset;
608 
609     PWCHAR NextWchar;
610     WCHAR ThisWchar;
611     USHORT Length;
612 
613     BOOLEAN FoundTilde = FALSE;
614 
615     OEM_STRING OemName = {0};
616     USHORT OemNameOffset = 0;
617     BOOLEAN OverflowBuffer = FALSE;
618 
619     PAGED_CODE();
620 
621     //
622     //  Initialize the short string to use the input buffer.
623     //
624 
625     ShortName.Buffer = ShortNameBuffer;
626     ShortName.MaximumLength = BYTE_COUNT_8_DOT_3;
627 
628     //
629     //  Initialize the name context.
630     //
631 
632     RtlZeroMemory( &NameContext, sizeof( GENERATE_NAME_CONTEXT ));
633 
634     //
635     //  We now have the unicode name for the input string.  Go ahead and generate
636     //  the short name.
637     //
638 
639     RtlGenerate8dot3Name( FileName, TRUE, &NameContext, &ShortName );
640 
641     //
642     //  We now have the generic short name.  We want incorporate the dirent offset
643     //  into the name in order to reduce the chance of name conflicts.  We will use
644     //  a tilde character followed by a character representation of the dirent offset.
645     //  This will be the hexadecimal representation of the dirent offset in the directory.
646     //  It is actuall this offset divided by 32 since we don't need the full
647     //  granularity.
648     //
649 
650     BiasedDirentOffset = DirentOffset >> SHORT_NAME_SHIFT;
651 
652     //
653     //  Point to a local buffer to store the offset string.  We start
654     //  at the end of the buffer and work backwards.
655     //
656 
657     NextWchar = Add2Ptr( BiasedShortNameBuffer,
658                          BYTE_COUNT_8_DOT_3,
659                          PWCHAR );
660 
661     BiasedShortName.MaximumLength = BYTE_COUNT_8_DOT_3;
662 
663     //
664     //  Generate an OEM version of the string so that we can check for double
665     //  byte characters.
666     //
667 
668     Status = RtlUnicodeStringToOemString(&OemName, &ShortName, TRUE);
669 
670     //
671     //  If this failed, bail out. Don't expect any problems other than no mem.
672     //
673 
674     if (!NT_SUCCESS( Status)) {
675 
676         NT_ASSERT( STATUS_INSUFFICIENT_RESOURCES == Status);
677         CdRaiseStatus( IrpContext, Status);
678     }
679 
680     Length = 0;
681 
682     //
683     //  Now add the characters for the dirent offset.  We need to start
684     //  from the least significant digit and work backwards.
685     //
686 
687     do {
688 
689         NextWchar -= 1;
690 
691         ThisWchar = (WCHAR) (BiasedDirentOffset & 0x0000000f);
692 
693         //
694         //  Store in the next character.  Bias against either '0' or 'A'
695         //
696 
697         if (ThisWchar <= 9) {
698 
699             *NextWchar = ThisWchar + L'0';
700 
701         } else {
702 
703             *NextWchar = ThisWchar + L'A' - 0xA;
704         }
705 
706         Length += sizeof( WCHAR );
707 
708         //
709         //  Shift out the low 4 bits of the offset.
710         //
711 
712         BiasedDirentOffset >>= 4;
713 
714     } while (BiasedDirentOffset != 0);
715 
716     //
717     //  Now store in the tilde character.
718     //
719 
720     NextWchar -= 1;
721     *NextWchar = L'~';
722     Length += sizeof( WCHAR );
723 
724     //
725     //  Set the length of this string.
726     //
727 
728     BiasedShortName.Length = Length;
729     BiasedShortName.Buffer = NextWchar;
730 
731     //
732     //  Figure out the maximum number of characters we can copy of the base
733     //  name.  We subract the number of characters in the dirent string from 8.
734     //  We will copy this many characters or stop when we reach a '.' character
735     //  or a '~' character in the name.
736     //
737 
738     MaximumBaseBytes = 16 - Length;
739 
740     BaseNameOffset = 0;
741 
742     //
743     //  Keep copying from the base name until we hit a '.', '~'  or the end of
744     //  the short name.
745     //
746 
747     NextWchar = ShortFileName;
748     Length = 0;
749 
750     while ((BaseNameOffset < ShortName.Length) &&
751            (ShortName.Buffer[BaseNameOffset / 2] != L'.')) {
752 
753         //
754         //  Remember if we found a tilde character in the short name,
755         //  so we don't copy it or anything following it.
756         //
757 
758         if (ShortName.Buffer[BaseNameOffset / 2] == L'~') {
759 
760             FoundTilde = TRUE;
761         }
762 
763         //
764         // We need to consider the DBCS code page,  because Unicode characters
765         // may use 2 bytes as DBCS characters.
766         //
767 
768 #ifdef _MSC_VER
769 #pragma prefast(push)
770 #pragma prefast(suppress:26014, "OemNameOffset <= BaseNameOffset throughout this loop; OemName buffer previously allocated based on ShortName's length.")
771 #endif
772         if (FsRtlIsLeadDbcsCharacter(OemName.Buffer[OemNameOffset])) {
773 #ifdef _MSC_VER
774 #pragma prefast(pop)
775 #endif
776 
777             OemNameOffset += 2;
778 
779             if ((OemNameOffset + (BiasedShortName.Length / sizeof(WCHAR))) > 8)  {
780 
781                 OverflowBuffer = TRUE;
782             }
783         }
784         else  {
785 
786             OemNameOffset++;
787         }
788 
789         //
790         //  Only copy the bytes if we still have space for the dirent string.
791         //
792 
793         if (!FoundTilde && !OverflowBuffer && (BaseNameOffset < MaximumBaseBytes)) {
794 
795             *NextWchar = ShortName.Buffer[BaseNameOffset / 2];
796             Length += sizeof( WCHAR );
797             NextWchar += 1;
798         }
799 
800         BaseNameOffset += 2;
801     }
802 
803     RtlFreeOemString(&OemName);
804 
805     //
806     //  Now copy the dirent string into the biased name buffer.
807     //
808 
809 #ifdef _MSC_VER
810 #pragma prefast(push)
811 #endif
812     RtlCopyMemory( NextWchar,
813                    BiasedShortName.Buffer,
814                    BiasedShortName.Length );
815 #ifdef _MSC_VER
816 #pragma prefast(pop)
817 #endif
818 
819     Length += BiasedShortName.Length;
820     NextWchar += (BiasedShortName.Length / sizeof( WCHAR ));
821 
822     //
823     //  Now copy any remaining bytes over to the biased short name.
824     //
825 
826     if (BaseNameOffset != ShortName.Length) {
827 
828         RtlCopyMemory( NextWchar,
829                        &ShortName.Buffer[BaseNameOffset / 2],
830                        ShortName.Length - BaseNameOffset );
831 
832         Length += (ShortName.Length - (USHORT) BaseNameOffset);
833     }
834 
835     //
836     //  The final short name is stored in the user's buffer.
837     //
838 
839     *ShortByteCount = Length;
840 }
841 
842 
843 BOOLEAN
844 CdIsNameInExpression (
845     _In_ PIRP_CONTEXT IrpContext,
846     _In_ PCD_NAME CurrentName,
847     _In_ PCD_NAME SearchExpression,
848     _In_ ULONG  WildcardFlags,
849     _In_ BOOLEAN CheckVersion
850     )
851 
852 /*++
853 
854 Routine Description:
855 
856     This routine will compare two CdName strings.  We assume that if this
857     is to be a case-insensitive search then they are already upcased.
858 
859     We compare the filename portions of the name and if they match we
860     compare the version strings if requested.
861 
862 Arguments:
863 
864     CurrentName - Filename from the disk.
865 
866     SearchExpression - Filename expression to use for match.
867 
868     WildcardFlags - Flags field which indicates which parts of the
869         search expression might have wildcards.  These flags are the
870         same as in the Ccb flags field.
871 
872     CheckVersion - Indicates whether we should check both the name and the
873         version strings or just the name.
874 
875 Return Value:
876 
877     BOOLEAN - TRUE if the expressions match, FALSE otherwise.
878 
879 --*/
880 
881 {
882     BOOLEAN Match = TRUE;
883     PAGED_CODE();
884 
885     UNREFERENCED_PARAMETER( IrpContext );
886 
887     //
888     //  If there are wildcards in the expression then we call the
889     //  appropriate FsRtlRoutine.
890     //
891 
892     if (FlagOn( WildcardFlags, CCB_FLAG_ENUM_NAME_EXP_HAS_WILD )) {
893 
894         Match = FsRtlIsNameInExpression( &SearchExpression->FileName,
895                                          &CurrentName->FileName,
896                                          FALSE,
897                                          NULL );
898 
899     //
900     //  Otherwise do a direct memory comparison for the name string.
901     //
902 
903     } else {
904 
905         if ((CurrentName->FileName.Length != SearchExpression->FileName.Length) ||
906             (!RtlEqualMemory( CurrentName->FileName.Buffer,
907                               SearchExpression->FileName.Buffer,
908                               CurrentName->FileName.Length ))) {
909 
910             Match = FALSE;
911         }
912     }
913 
914     //
915     //  Check the version numbers if requested by the user and we have a
916     //  match on the name and the version number is present.
917     //
918 
919     if (Match && CheckVersion && SearchExpression->VersionString.Length &&
920         !FlagOn( WildcardFlags, CCB_FLAG_ENUM_VERSION_MATCH_ALL )) {
921 
922         //
923         //  If there are wildcards in the expression then call the
924         //  appropriate search expression.
925         //
926 
927         if (FlagOn( WildcardFlags, CCB_FLAG_ENUM_VERSION_EXP_HAS_WILD )) {
928 
929             Match = FsRtlIsNameInExpression( &SearchExpression->VersionString,
930                                              &CurrentName->VersionString,
931                                              FALSE,
932                                              NULL );
933 
934         //
935         //  Otherwise do a direct memory comparison for the name string.
936         //
937 
938         } else {
939 
940             if ((CurrentName->VersionString.Length != SearchExpression->VersionString.Length) ||
941                 (!RtlEqualMemory( CurrentName->VersionString.Buffer,
942                                   SearchExpression->VersionString.Buffer,
943                                   CurrentName->VersionString.Length ))) {
944 
945                 Match = FALSE;
946             }
947         }
948     }
949 
950     return Match;
951 }
952 
953 
954 ULONG
955 CdShortNameDirentOffset (
956     _In_ PIRP_CONTEXT IrpContext,
957     _In_ PUNICODE_STRING Name
958     )
959 
960 /*++
961 
962 Routine Description:
963 
964     This routine is called to examine a name to see if the dirent offset string is contained.
965     This consists of a tilde character followed by the offset represented as a hexadecimal
966     characters.  We don't do any other checks to see if this is a short name.  We
967     catch that later outside this routine.
968 
969 Arguments:
970 
971     Name - This is the CdName to examine.
972 
973 Return Value:
974 
975     ULONG - MAXULONG if there is no valid dirent offset string embedded, otherwise the
976         convert the value to numeric form.
977 
978 --*/
979 
980 {
981     ULONG ResultOffset = MAXULONG;
982     ULONG RemainingByteCount = Name->Length;
983 
984     BOOLEAN FoundTilde = FALSE;
985 
986     PWCHAR NextWchar;
987 
988     PAGED_CODE();
989 
990     UNREFERENCED_PARAMETER( IrpContext );
991 
992     //
993     //  Walk through the name until we either reach the end of the name
994     //  or find a tilde character.
995     //
996 
997     for (NextWchar = Name->Buffer;
998          RemainingByteCount != 0;
999          NextWchar += 1, RemainingByteCount -= sizeof( WCHAR )) {
1000 
1001         //
1002         //  Check if this is a dot.  Stop constructing any string if
1003         //  we found a dot.
1004         //
1005 
1006         if (*NextWchar == L'.') {
1007 
1008             break;
1009         }
1010 
1011         //
1012         //  If we already found a tilde then check this character as a
1013         //  valid character.  It must be a digit or A to F.
1014         //
1015 
1016         if (FoundTilde) {
1017 
1018             if ((*NextWchar < L'0') ||
1019                 (*NextWchar > L'F') ||
1020                 ((*NextWchar > L'9') && (*NextWchar < 'A'))) {
1021 
1022                 ResultOffset = MAXULONG;
1023                 break;
1024             }
1025 
1026             //
1027             //  Shift the result by 4 bits and add in this new character.
1028             //
1029 
1030             ResultOffset <<= 4;
1031 
1032             if (*NextWchar < L'A') {
1033 
1034                 ResultOffset += *NextWchar - L'0';
1035 
1036             } else {
1037 
1038                 ResultOffset += (*NextWchar - L'A') + 10;
1039             }
1040 
1041             continue;
1042         }
1043 
1044         //
1045         //  If this is a tilde then start building the dirent string.
1046         //
1047 
1048         if (*NextWchar == L'~') {
1049 
1050             FoundTilde = TRUE;
1051             ResultOffset = 0;
1052         }
1053     }
1054 
1055     return ResultOffset;
1056 }
1057 
1058 
1059 //
1060 //  Local support routine
1061 //
1062 
1063 FSRTL_COMPARISON_RESULT
1064 CdFullCompareNames (
1065     _In_ PIRP_CONTEXT IrpContext,
1066     _In_ PUNICODE_STRING NameA,
1067     _In_ PUNICODE_STRING NameB
1068     )
1069 
1070 /*++
1071 
1072 Routine Description:
1073 
1074     This function compares two names as fast as possible.  Note that since
1075     this comparison is case sensitive we can do a direct memory comparison.
1076 
1077 Arguments:
1078 
1079     NameA & NameB - The names to compare.
1080 
1081 Return Value:
1082 
1083     COMPARISON - returns
1084 
1085         LessThan    if NameA < NameB lexicalgraphically,
1086         GreaterThan if NameA > NameB lexicalgraphically,
1087         EqualTo     if NameA is equal to NameB
1088 
1089 --*/
1090 
1091 {
1092     SIZE_T i;
1093     ULONG MinLength = NameA->Length;
1094     FSRTL_COMPARISON_RESULT Result = LessThan;
1095 
1096     PAGED_CODE();
1097 
1098     UNREFERENCED_PARAMETER( IrpContext );
1099 
1100     //
1101     //  Figure out the minimum of the two lengths
1102     //
1103 
1104     if (NameA->Length > NameB->Length) {
1105 
1106         MinLength = NameB->Length;
1107         Result = GreaterThan;
1108 
1109     } else if (NameA->Length == NameB->Length) {
1110 
1111         Result = EqualTo;
1112     }
1113 
1114     //
1115     //  Loop through looking at all of the characters in both strings
1116     //  testing for equalilty, less than, and greater than
1117     //
1118 
1119     i = RtlCompareMemory( NameA->Buffer, NameB->Buffer, MinLength );
1120 
1121     if (i < MinLength) {
1122 
1123         //
1124         //  We know the offset of the first character which is different.
1125         //
1126 
1127         return ((NameA->Buffer[ i / 2 ] < NameB->Buffer[ i / 2 ]) ?
1128                  LessThan :
1129                  GreaterThan);
1130     }
1131 
1132     //
1133     //  The names match up to the length of the shorter string.
1134     //  The shorter string lexically appears first.
1135     //
1136 
1137     return Result;
1138 }
1139 
1140 
1141 
1142