3 Copyright (c) 1991-2000 Microsoft Corporation
11 This module implements the Cdfs Name support routines
19 // The Bug check file id for this module
22 #define BugCheckFileId (CDFS_BUG_CHECK_NAMESUP)
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)
38 _Post_satisfies_(_Old_(CdName
->FileName
.Length
) >=
39 CdName
->FileName
.Length
+ CdName
->VersionString
.Length
)
41 CdConvertNameToCdName (
42 _In_ PIRP_CONTEXT IrpContext
,
43 _Inout_ PCD_NAME CdName
50 This routine is called to convert a string of bytes into a CdName.
52 The full name is already in the CdName structure in the FileName field.
53 We split this into the filename and version strings.
57 CdName - Pointer to CdName structure to update.
67 PWCHAR CurrentCharacter
= CdName
->FileName
.Buffer
;
71 UNREFERENCED_PARAMETER( IrpContext
);
74 // Look for a separator character.
77 while ((NameLength
< CdName
->FileName
.Length
) &&
78 (*CurrentCharacter
!= L
';')) {
80 CurrentCharacter
+= 1;
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.
89 CdName
->VersionString
.Length
= 0;
90 if (NameLength
+ sizeof( WCHAR
) < CdName
->FileName
.Length
) {
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
),
100 // Now update the filename portion of the name.
103 CdName
->FileName
.Length
= (USHORT
) NameLength
;
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
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.
128 BigEndian - Pointer to the string of big endian characters.
130 ByteCount - Number of unicode characters in this string.
132 LittleEndian - Pointer to array to store the little endian characters.
141 ULONG RemainingByteCount
= ByteCount
;
143 PCHAR Source
= BigEndian
;
144 PCHAR Destination
= LittleEndian
;
149 // If the byte count isn't an even number then the disk is corrupt.
152 if (FlagOn( ByteCount
, 1 )) {
154 CdRaiseStatus( IrpContext
, STATUS_DISK_CORRUPT_ERROR
);
158 // Start by copy the low-order bytes into the correct position. Do
159 // this by skipping the first byte in the BigEndian string.
162 RtlCopyMemory( Destination
,
164 RemainingByteCount
- 1 );
167 // Now move the high-order bytes into position.
172 while (RemainingByteCount
!= 0) {
174 #pragma prefast(push)
175 #pragma prefast(suppress:26014, "RemainingByteCount is even")
176 *Destination
= *Source
;
182 RemainingByteCount
-= 2;
191 _In_ PIRP_CONTEXT IrpContext
,
193 _Inout_ PCD_NAME UpcaseName
200 This routine is called to upcase a CdName structure. We will do both
201 the filename and version strings.
205 Name - This is the mixed case version of the name.
207 UpcaseName - This is the destination for the upcase operation.
211 None. This routine will raise all errors.
220 UNREFERENCED_PARAMETER( IrpContext
);
223 // If the name structures are different then initialize the different components.
226 if (Name
!= UpcaseName
) {
229 // Initialize the version string portion of the name.
232 UpcaseName
->VersionString
.Length
= 0;
234 if (Name
->VersionString
.Length
!= 0) {
236 UpcaseName
->VersionString
.MaximumLength
=
237 UpcaseName
->VersionString
.Length
= Name
->VersionString
.Length
;
240 // Initially set the buffer to point to where we need to insert
244 UpcaseName
->VersionString
.Buffer
= Add2Ptr( UpcaseName
->FileName
.Buffer
,
245 Name
->FileName
.Length
,
249 // We are currently pointing to the location to store the separator.
250 // Store the ';' and then move to the next character to
254 #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.");
255 *(UpcaseName
->VersionString
.Buffer
) = L
';';
257 UpcaseName
->VersionString
.Buffer
+= 1;
262 // Upcase the string using the correct upcase routine.
265 Status
= RtlUpcaseUnicodeString( &UpcaseName
->FileName
,
270 // This should never fail.
273 NT_ASSERT( Status
== STATUS_SUCCESS
);
274 __analysis_assert( Status
== STATUS_SUCCESS
);
276 if (Name
->VersionString
.Length
!= 0) {
278 Status
= RtlUpcaseUnicodeString( &UpcaseName
->VersionString
,
279 &Name
->VersionString
,
283 // This should never fail.
286 NT_ASSERT( Status
== STATUS_SUCCESS
);
287 __analysis_assert( Status
== STATUS_SUCCESS
);
296 _In_ PIRP_CONTEXT IrpContext
,
297 _Inout_ PUNICODE_STRING RemainingName
,
298 _Out_ PUNICODE_STRING FinalName
305 This routine is called to strip off leading components of the name strings. We search
306 for either the end of the string or separating characters. The input remaining
307 name strings should have neither a trailing or leading backslash.
311 RemainingName - Remaining name.
313 FinalName - Location to store next component of name.
327 UNREFERENCED_PARAMETER( IrpContext
);
330 // Find the offset of the next component separators.
333 for (NameLength
= 0, NextWchar
= RemainingName
->Buffer
;
334 (NameLength
< RemainingName
->Length
) && (*NextWchar
!= L
'\\');
335 NameLength
+= sizeof( WCHAR
) , NextWchar
+= 1);
338 // Adjust all the strings by this amount.
341 FinalName
->Buffer
= RemainingName
->Buffer
;
343 FinalName
->MaximumLength
= FinalName
->Length
= (USHORT
) NameLength
;
346 // If this is the last component then set the RemainingName lengths to zero.
349 if (NameLength
== RemainingName
->Length
) {
351 RemainingName
->Length
= 0;
354 // Otherwise we adjust the string by this amount plus the separating character.
359 RemainingName
->MaximumLength
-= (USHORT
) (NameLength
+ sizeof( WCHAR
));
360 RemainingName
->Length
-= (USHORT
) (NameLength
+ sizeof( WCHAR
));
361 RemainingName
->Buffer
= Add2Ptr( RemainingName
->Buffer
,
362 NameLength
+ sizeof( WCHAR
),
372 _In_ PIRP_CONTEXT IrpContext
,
373 _In_ PUNICODE_STRING FileName
380 This routine checks if the name is a legal ISO 9660 name.
384 FileName - String of bytes containing the name.
388 BOOLEAN - TRUE if this name is a legal, FALSE otherwise.
397 UNREFERENCED_PARAMETER( IrpContext
);
400 // Check if name corresponds to a legal file name.
403 for (Wchar
= FileName
->Buffer
;
404 Wchar
< Add2Ptr( FileName
->Buffer
, FileName
->Length
, PWCHAR
);
407 if ((*Wchar
< 0xff) &&
408 !FsRtlIsAnsiCharacterLegalHpfs( *Wchar
, FALSE
) &&
424 _In_ PIRP_CONTEXT IrpContext
,
425 _In_ UNICODE_STRING FileName
432 This routine checks if the name follows the 8.3 name conventions. We check for
433 the name length and whether the characters are valid.
437 FileName - String of bytes containing the name.
441 BOOLEAN - TRUE if this name is a legal 8.3 name, FALSE otherwise.
446 CHAR DbcsNameBuffer
[ BYTE_COUNT_8_DOT_3
];
447 STRING DbcsName
= {0};
453 BOOLEAN LastCharDot
= FALSE
;
457 UNREFERENCED_PARAMETER( IrpContext
);
460 // The length must be less than 24 bytes.
463 NT_ASSERT( FileName
.Length
!= 0 );
464 if (FileName
.Length
> BYTE_COUNT_8_DOT_3
) {
470 // Walk though and check for a space character.
473 NextWchar
= FileName
.Buffer
;
479 // No spaces allowed.
482 if (*NextWchar
== L
' ') { return FALSE
; }
484 if (*NextWchar
== L
'.') {
487 // Not an 8.3 name if more than 1 dot or more than 8 characters
488 // remaining. (It is legal for the dot to be in the ninth
492 if ((DotCount
> 0) ||
493 (Count
> 8 * sizeof( WCHAR
))) {
509 } while (Count
< FileName
.Length
);
512 // Go ahead and truncate the dot if at the end.
517 FileName
.Length
-= sizeof( WCHAR
);
521 // Create an Oem name to use to check for a valid short name.
524 DbcsName
.MaximumLength
= BYTE_COUNT_8_DOT_3
;
525 DbcsName
.Buffer
= DbcsNameBuffer
;
527 if (!NT_SUCCESS( RtlUnicodeStringToCountedOemString( &DbcsName
,
535 // We have now initialized the Oem string. Call the FsRtl package to check for a
539 return FsRtlIsFatDbcsLegal( DbcsName
, FALSE
, FALSE
, FALSE
);
544 CdGenerate8dot3Name (
545 _In_ PIRP_CONTEXT IrpContext
,
546 _In_ PUNICODE_STRING FileName
,
547 _In_ ULONG DirentOffset
,
548 _Out_writes_bytes_to_(BYTE_COUNT_8_DOT_3
, *ShortByteCount
) PWCHAR ShortFileName
,
549 _Out_ PUSHORT ShortByteCount
556 This routine is called to generate a short name from the given long name. We will
557 generate a short name from the given long name.
559 We go through the following steps to make this conversion.
561 1 - Generate the generic short name. This will also be in unicode format.
563 2 - Build the string representation of the dirent offset.
565 3 - Build the biased short name string by combining the generic short name with
566 the dirent offset string.
568 4 - Copy the final unicode string back to our caller's buffer.
572 FileName - String of bytes containing the name.
574 DirentOffset - Offset in the directory for this filename. We incorporate the offset into
575 the short name by dividing this by 32 and prepending a tilde character to the
576 digit character. We then append this to the base of the generated short name.
578 ShortFileName - Pointer to the buffer to store the short name into.
580 ShortByteCount - Address to store the number of bytes in the short name.
591 UNICODE_STRING ShortName
;
592 UNICODE_STRING BiasedShortName
;
593 WCHAR ShortNameBuffer
[ BYTE_COUNT_8_DOT_3
/ sizeof( WCHAR
) ] = {0};
594 WCHAR BiasedShortNameBuffer
[ BYTE_COUNT_8_DOT_3
/ sizeof( WCHAR
) ];
596 GENERATE_NAME_CONTEXT NameContext
;
598 ULONG BiasedDirentOffset
;
600 ULONG MaximumBaseBytes
;
601 ULONG BaseNameOffset
;
607 BOOLEAN FoundTilde
= FALSE
;
609 OEM_STRING OemName
= {0};
610 USHORT OemNameOffset
= 0;
611 BOOLEAN OverflowBuffer
= FALSE
;
616 // Initialize the short string to use the input buffer.
619 ShortName
.Buffer
= ShortNameBuffer
;
620 ShortName
.MaximumLength
= BYTE_COUNT_8_DOT_3
;
623 // Initialize the name context.
626 RtlZeroMemory( &NameContext
, sizeof( GENERATE_NAME_CONTEXT
));
629 // We now have the unicode name for the input string. Go ahead and generate
633 RtlGenerate8dot3Name( FileName
, TRUE
, &NameContext
, &ShortName
);
636 // We now have the generic short name. We want incorporate the dirent offset
637 // into the name in order to reduce the chance of name conflicts. We will use
638 // a tilde character followed by a character representation of the dirent offset.
639 // This will be the hexadecimal representation of the dirent offset in the directory.
640 // It is actuall this offset divided by 32 since we don't need the full
644 BiasedDirentOffset
= DirentOffset
>> SHORT_NAME_SHIFT
;
647 // Point to a local buffer to store the offset string. We start
648 // at the end of the buffer and work backwards.
651 NextWchar
= Add2Ptr( BiasedShortNameBuffer
,
655 BiasedShortName
.MaximumLength
= BYTE_COUNT_8_DOT_3
;
658 // Generate an OEM version of the string so that we can check for double
662 Status
= RtlUnicodeStringToOemString(&OemName
, &ShortName
, TRUE
);
665 // If this failed, bail out. Don't expect any problems other than no mem.
668 if (!NT_SUCCESS( Status
)) {
670 NT_ASSERT( STATUS_INSUFFICIENT_RESOURCES
== Status
);
671 CdRaiseStatus( IrpContext
, Status
);
677 // Now add the characters for the dirent offset. We need to start
678 // from the least significant digit and work backwards.
685 ThisWchar
= (WCHAR
) (BiasedDirentOffset
& 0x0000000f);
688 // Store in the next character. Bias against either '0' or 'A'
691 if (ThisWchar
<= 9) {
693 *NextWchar
= ThisWchar
+ L
'0';
697 *NextWchar
= ThisWchar
+ L
'A' - 0xA;
700 Length
+= sizeof( WCHAR
);
703 // Shift out the low 4 bits of the offset.
706 BiasedDirentOffset
>>= 4;
708 } while (BiasedDirentOffset
!= 0);
711 // Now store in the tilde character.
716 Length
+= sizeof( WCHAR
);
719 // Set the length of this string.
722 BiasedShortName
.Length
= Length
;
723 BiasedShortName
.Buffer
= NextWchar
;
726 // Figure out the maximum number of characters we can copy of the base
727 // name. We subract the number of characters in the dirent string from 8.
728 // We will copy this many characters or stop when we reach a '.' character
729 // or a '~' character in the name.
732 MaximumBaseBytes
= 16 - Length
;
737 // Keep copying from the base name until we hit a '.', '~' or the end of
741 NextWchar
= ShortFileName
;
744 while ((BaseNameOffset
< ShortName
.Length
) &&
745 (ShortName
.Buffer
[BaseNameOffset
/ 2] != L
'.')) {
748 // Remember if we found a tilde character in the short name,
749 // so we don't copy it or anything following it.
752 if (ShortName
.Buffer
[BaseNameOffset
/ 2] == L
'~') {
758 // We need to consider the DBCS code page, because Unicode characters
759 // may use 2 bytes as DBCS characters.
762 #pragma prefast(push)
763 #pragma prefast(suppress:26014, "OemNameOffset <= BaseNameOffset throughout this loop; OemName buffer previously allocated based on ShortName's length.")
764 if (FsRtlIsLeadDbcsCharacter(OemName
.Buffer
[OemNameOffset
])) {
769 if ((OemNameOffset
+ (BiasedShortName
.Length
/ sizeof(WCHAR
))) > 8) {
771 OverflowBuffer
= TRUE
;
780 // Only copy the bytes if we still have space for the dirent string.
783 if (!FoundTilde
&& !OverflowBuffer
&& (BaseNameOffset
< MaximumBaseBytes
)) {
785 *NextWchar
= ShortName
.Buffer
[BaseNameOffset
/ 2];
786 Length
+= sizeof( WCHAR
);
793 RtlFreeOemString(&OemName
);
796 // Now copy the dirent string into the biased name buffer.
799 #pragma prefast(push)
800 RtlCopyMemory( NextWchar
,
801 BiasedShortName
.Buffer
,
802 BiasedShortName
.Length
);
805 Length
+= BiasedShortName
.Length
;
806 NextWchar
+= (BiasedShortName
.Length
/ sizeof( WCHAR
));
809 // Now copy any remaining bytes over to the biased short name.
812 if (BaseNameOffset
!= ShortName
.Length
) {
814 RtlCopyMemory( NextWchar
,
815 &ShortName
.Buffer
[BaseNameOffset
/ 2],
816 ShortName
.Length
- BaseNameOffset
);
818 Length
+= (ShortName
.Length
- (USHORT
) BaseNameOffset
);
822 // The final short name is stored in the user's buffer.
825 *ShortByteCount
= Length
;
830 CdIsNameInExpression (
831 _In_ PIRP_CONTEXT IrpContext
,
832 _In_ PCD_NAME CurrentName
,
833 _In_ PCD_NAME SearchExpression
,
834 _In_ ULONG WildcardFlags
,
835 _In_ BOOLEAN CheckVersion
842 This routine will compare two CdName strings. We assume that if this
843 is to be a case-insensitive search then they are already upcased.
845 We compare the filename portions of the name and if they match we
846 compare the version strings if requested.
850 CurrentName - Filename from the disk.
852 SearchExpression - Filename expression to use for match.
854 WildcardFlags - Flags field which indicates which parts of the
855 search expression might have wildcards. These flags are the
856 same as in the Ccb flags field.
858 CheckVersion - Indicates whether we should check both the name and the
859 version strings or just the name.
863 BOOLEAN - TRUE if the expressions match, FALSE otherwise.
868 BOOLEAN Match
= TRUE
;
871 UNREFERENCED_PARAMETER( IrpContext
);
874 // If there are wildcards in the expression then we call the
875 // appropriate FsRtlRoutine.
878 if (FlagOn( WildcardFlags
, CCB_FLAG_ENUM_NAME_EXP_HAS_WILD
)) {
880 Match
= FsRtlIsNameInExpression( &SearchExpression
->FileName
,
881 &CurrentName
->FileName
,
886 // Otherwise do a direct memory comparison for the name string.
891 if ((CurrentName
->FileName
.Length
!= SearchExpression
->FileName
.Length
) ||
892 (!RtlEqualMemory( CurrentName
->FileName
.Buffer
,
893 SearchExpression
->FileName
.Buffer
,
894 CurrentName
->FileName
.Length
))) {
901 // Check the version numbers if requested by the user and we have a
902 // match on the name and the version number is present.
905 if (Match
&& CheckVersion
&& SearchExpression
->VersionString
.Length
&&
906 !FlagOn( WildcardFlags
, CCB_FLAG_ENUM_VERSION_MATCH_ALL
)) {
909 // If there are wildcards in the expression then call the
910 // appropriate search expression.
913 if (FlagOn( WildcardFlags
, CCB_FLAG_ENUM_VERSION_EXP_HAS_WILD
)) {
915 Match
= FsRtlIsNameInExpression( &SearchExpression
->VersionString
,
916 &CurrentName
->VersionString
,
921 // Otherwise do a direct memory comparison for the name string.
926 if ((CurrentName
->VersionString
.Length
!= SearchExpression
->VersionString
.Length
) ||
927 (!RtlEqualMemory( CurrentName
->VersionString
.Buffer
,
928 SearchExpression
->VersionString
.Buffer
,
929 CurrentName
->VersionString
.Length
))) {
941 CdShortNameDirentOffset (
942 _In_ PIRP_CONTEXT IrpContext
,
943 _In_ PUNICODE_STRING Name
950 This routine is called to examine a name to see if the dirent offset string is contained.
951 This consists of a tilde character followed by the offset represented as a hexadecimal
952 characters. We don't do any other checks to see if this is a short name. We
953 catch that later outside this routine.
957 Name - This is the CdName to examine.
961 ULONG - MAXULONG if there is no valid dirent offset string embedded, otherwise the
962 convert the value to numeric form.
967 ULONG ResultOffset
= MAXULONG
;
968 ULONG RemainingByteCount
= Name
->Length
;
970 BOOLEAN FoundTilde
= FALSE
;
976 UNREFERENCED_PARAMETER( IrpContext
);
979 // Walk through the name until we either reach the end of the name
980 // or find a tilde character.
983 for (NextWchar
= Name
->Buffer
;
984 RemainingByteCount
!= 0;
985 NextWchar
+= 1, RemainingByteCount
-= sizeof( WCHAR
)) {
988 // Check if this is a dot. Stop constructing any string if
992 if (*NextWchar
== L
'.') {
998 // If we already found a tilde then check this character as a
999 // valid character. It must be a digit or A to F.
1004 if ((*NextWchar
< L
'0') ||
1005 (*NextWchar
> L
'F') ||
1006 ((*NextWchar
> L
'9') && (*NextWchar
< 'A'))) {
1008 ResultOffset
= MAXULONG
;
1013 // Shift the result by 4 bits and add in this new character.
1018 if (*NextWchar
< L
'A') {
1020 ResultOffset
+= *NextWchar
- L
'0';
1024 ResultOffset
+= (*NextWchar
- L
'A') + 10;
1031 // If this is a tilde then start building the dirent string.
1034 if (*NextWchar
== L
'~') {
1041 return ResultOffset
;
1046 // Local support routine
1049 FSRTL_COMPARISON_RESULT
1050 CdFullCompareNames (
1051 _In_ PIRP_CONTEXT IrpContext
,
1052 _In_ PUNICODE_STRING NameA
,
1053 _In_ PUNICODE_STRING NameB
1058 Routine Description:
1060 This function compares two names as fast as possible. Note that since
1061 this comparison is case sensitive we can do a direct memory comparison.
1065 NameA & NameB - The names to compare.
1069 COMPARISON - returns
1071 LessThan if NameA < NameB lexicalgraphically,
1072 GreaterThan if NameA > NameB lexicalgraphically,
1073 EqualTo if NameA is equal to NameB
1079 ULONG MinLength
= NameA
->Length
;
1080 FSRTL_COMPARISON_RESULT Result
= LessThan
;
1084 UNREFERENCED_PARAMETER( IrpContext
);
1087 // Figure out the minimum of the two lengths
1090 if (NameA
->Length
> NameB
->Length
) {
1092 MinLength
= NameB
->Length
;
1093 Result
= GreaterThan
;
1095 } else if (NameA
->Length
== NameB
->Length
) {
1101 // Loop through looking at all of the characters in both strings
1102 // testing for equalilty, less than, and greater than
1105 i
= RtlCompareMemory( NameA
->Buffer
, NameB
->Buffer
, MinLength
);
1107 if (i
< MinLength
) {
1110 // We know the offset of the first character which is different.
1113 return ((NameA
->Buffer
[ i
/ 2 ] < NameB
->Buffer
[ i
/ 2 ]) ?
1119 // The names match up to the length of the shorter string.
1120 // The shorter string lexically appears first.