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) {
175 #pragma prefast(push)
176 #pragma prefast(suppress:26014, "RemainingByteCount is even")
178 *Destination
= *Source
;
186 RemainingByteCount
-= 2;
195 _In_ PIRP_CONTEXT IrpContext
,
197 _Inout_ PCD_NAME UpcaseName
204 This routine is called to upcase a CdName structure. We will do both
205 the filename and version strings.
209 Name - This is the mixed case version of the name.
211 UpcaseName - This is the destination for the upcase operation.
215 None. This routine will raise all errors.
224 UNREFERENCED_PARAMETER( IrpContext
);
227 // If the name structures are different then initialize the different components.
230 if (Name
!= UpcaseName
) {
233 // Initialize the version string portion of the name.
236 UpcaseName
->VersionString
.Length
= 0;
238 if (Name
->VersionString
.Length
!= 0) {
240 UpcaseName
->VersionString
.MaximumLength
=
241 UpcaseName
->VersionString
.Length
= Name
->VersionString
.Length
;
244 // Initially set the buffer to point to where we need to insert
248 UpcaseName
->VersionString
.Buffer
= Add2Ptr( UpcaseName
->FileName
.Buffer
,
249 Name
->FileName
.Length
,
253 // We are currently pointing to the location to store the separator.
254 // Store the ';' and then move to the next character to
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.");
261 *(UpcaseName
->VersionString
.Buffer
) = L
';';
263 UpcaseName
->VersionString
.Buffer
+= 1;
268 // Upcase the string using the correct upcase routine.
271 Status
= RtlUpcaseUnicodeString( &UpcaseName
->FileName
,
276 // This should never fail.
279 NT_ASSERT( Status
== STATUS_SUCCESS
);
280 __analysis_assert( Status
== STATUS_SUCCESS
);
282 if (Name
->VersionString
.Length
!= 0) {
284 Status
= RtlUpcaseUnicodeString( &UpcaseName
->VersionString
,
285 &Name
->VersionString
,
289 // This should never fail.
292 NT_ASSERT( Status
== STATUS_SUCCESS
);
293 __analysis_assert( Status
== STATUS_SUCCESS
);
302 _In_ PIRP_CONTEXT IrpContext
,
303 _Inout_ PUNICODE_STRING RemainingName
,
304 _Out_ PUNICODE_STRING FinalName
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.
317 RemainingName - Remaining name.
319 FinalName - Location to store next component of name.
333 UNREFERENCED_PARAMETER( IrpContext
);
336 // Find the offset of the next component separators.
339 for (NameLength
= 0, NextWchar
= RemainingName
->Buffer
;
340 (NameLength
< RemainingName
->Length
) && (*NextWchar
!= L
'\\');
341 NameLength
+= sizeof( WCHAR
) , NextWchar
+= 1);
344 // Adjust all the strings by this amount.
347 FinalName
->Buffer
= RemainingName
->Buffer
;
349 FinalName
->MaximumLength
= FinalName
->Length
= (USHORT
) NameLength
;
352 // If this is the last component then set the RemainingName lengths to zero.
355 if (NameLength
== RemainingName
->Length
) {
357 RemainingName
->Length
= 0;
360 // Otherwise we adjust the string by this amount plus the separating character.
365 RemainingName
->MaximumLength
-= (USHORT
) (NameLength
+ sizeof( WCHAR
));
366 RemainingName
->Length
-= (USHORT
) (NameLength
+ sizeof( WCHAR
));
367 RemainingName
->Buffer
= Add2Ptr( RemainingName
->Buffer
,
368 NameLength
+ sizeof( WCHAR
),
378 _In_ PIRP_CONTEXT IrpContext
,
379 _In_ PUNICODE_STRING FileName
386 This routine checks if the name is a legal ISO 9660 name.
390 FileName - String of bytes containing the name.
394 BOOLEAN - TRUE if this name is a legal, FALSE otherwise.
403 UNREFERENCED_PARAMETER( IrpContext
);
406 // Check if name corresponds to a legal file name.
409 for (Wchar
= FileName
->Buffer
;
410 Wchar
< Add2Ptr( FileName
->Buffer
, FileName
->Length
, PWCHAR
);
414 if ((*Wchar
< 0xff) &&
417 // Check whether ASCII characters are legal.
418 // We will consider the rest of the characters
419 // (extended ASCII and unicode) as legal.
422 if ((*Wchar
< 0x80) &&
424 !FsRtlIsAnsiCharacterLegalHpfs( *Wchar
, FALSE
) &&
440 _In_ PIRP_CONTEXT IrpContext
,
441 _In_ UNICODE_STRING FileName
448 This routine checks if the name follows the 8.3 name conventions. We check for
449 the name length and whether the characters are valid.
453 FileName - String of bytes containing the name.
457 BOOLEAN - TRUE if this name is a legal 8.3 name, FALSE otherwise.
462 CHAR DbcsNameBuffer
[ BYTE_COUNT_8_DOT_3
];
463 STRING DbcsName
= {0};
469 BOOLEAN LastCharDot
= FALSE
;
473 UNREFERENCED_PARAMETER( IrpContext
);
476 // The length must be less than 24 bytes.
479 NT_ASSERT( FileName
.Length
!= 0 );
480 if (FileName
.Length
> BYTE_COUNT_8_DOT_3
) {
486 // Walk though and check for a space character.
489 NextWchar
= FileName
.Buffer
;
495 // No spaces allowed.
498 if (*NextWchar
== L
' ') { return FALSE
; }
500 if (*NextWchar
== L
'.') {
503 // Not an 8.3 name if more than 1 dot or more than 8 characters
504 // remaining. (It is legal for the dot to be in the ninth
508 if ((DotCount
> 0) ||
509 (Count
> 8 * sizeof( WCHAR
))) {
525 } while (Count
< FileName
.Length
);
528 // Go ahead and truncate the dot if at the end.
533 FileName
.Length
-= sizeof( WCHAR
);
537 // Create an Oem name to use to check for a valid short name.
540 DbcsName
.MaximumLength
= BYTE_COUNT_8_DOT_3
;
541 DbcsName
.Buffer
= DbcsNameBuffer
;
543 if (!NT_SUCCESS( RtlUnicodeStringToCountedOemString( &DbcsName
,
551 // We have now initialized the Oem string. Call the FsRtl package to check for a
555 return FsRtlIsFatDbcsLegal( DbcsName
, FALSE
, FALSE
, FALSE
);
560 CdGenerate8dot3Name (
561 _In_ PIRP_CONTEXT IrpContext
,
562 _In_ PUNICODE_STRING FileName
,
563 _In_ ULONG DirentOffset
,
564 _Out_writes_bytes_to_(BYTE_COUNT_8_DOT_3
, *ShortByteCount
) PWCHAR ShortFileName
,
565 _Out_ PUSHORT ShortByteCount
572 This routine is called to generate a short name from the given long name. We will
573 generate a short name from the given long name.
575 We go through the following steps to make this conversion.
577 1 - Generate the generic short name. This will also be in unicode format.
579 2 - Build the string representation of the dirent offset.
581 3 - Build the biased short name string by combining the generic short name with
582 the dirent offset string.
584 4 - Copy the final unicode string back to our caller's buffer.
588 FileName - String of bytes containing the name.
590 DirentOffset - Offset in the directory for this filename. We incorporate the offset into
591 the short name by dividing this by 32 and prepending a tilde character to the
592 digit character. We then append this to the base of the generated short name.
594 ShortFileName - Pointer to the buffer to store the short name into.
596 ShortByteCount - Address to store the number of bytes in the short name.
607 UNICODE_STRING ShortName
;
608 UNICODE_STRING BiasedShortName
;
609 WCHAR ShortNameBuffer
[ BYTE_COUNT_8_DOT_3
/ sizeof( WCHAR
) ] = {0};
610 WCHAR BiasedShortNameBuffer
[ BYTE_COUNT_8_DOT_3
/ sizeof( WCHAR
) ];
612 GENERATE_NAME_CONTEXT NameContext
;
614 ULONG BiasedDirentOffset
;
616 ULONG MaximumBaseBytes
;
617 ULONG BaseNameOffset
;
623 BOOLEAN FoundTilde
= FALSE
;
625 OEM_STRING OemName
= {0};
626 USHORT OemNameOffset
= 0;
627 BOOLEAN OverflowBuffer
= FALSE
;
632 // Initialize the short string to use the input buffer.
635 ShortName
.Buffer
= ShortNameBuffer
;
636 ShortName
.MaximumLength
= BYTE_COUNT_8_DOT_3
;
639 // Initialize the name context.
642 RtlZeroMemory( &NameContext
, sizeof( GENERATE_NAME_CONTEXT
));
645 // We now have the unicode name for the input string. Go ahead and generate
649 RtlGenerate8dot3Name( FileName
, TRUE
, &NameContext
, &ShortName
);
652 // We now have the generic short name. We want incorporate the dirent offset
653 // into the name in order to reduce the chance of name conflicts. We will use
654 // a tilde character followed by a character representation of the dirent offset.
655 // This will be the hexadecimal representation of the dirent offset in the directory.
656 // It is actuall this offset divided by 32 since we don't need the full
660 BiasedDirentOffset
= DirentOffset
>> SHORT_NAME_SHIFT
;
663 // Point to a local buffer to store the offset string. We start
664 // at the end of the buffer and work backwards.
667 NextWchar
= Add2Ptr( BiasedShortNameBuffer
,
671 BiasedShortName
.MaximumLength
= BYTE_COUNT_8_DOT_3
;
674 // Generate an OEM version of the string so that we can check for double
678 Status
= RtlUnicodeStringToOemString(&OemName
, &ShortName
, TRUE
);
681 // If this failed, bail out. Don't expect any problems other than no mem.
684 if (!NT_SUCCESS( Status
)) {
686 NT_ASSERT( STATUS_INSUFFICIENT_RESOURCES
== Status
);
687 CdRaiseStatus( IrpContext
, Status
);
693 // Now add the characters for the dirent offset. We need to start
694 // from the least significant digit and work backwards.
701 ThisWchar
= (WCHAR
) (BiasedDirentOffset
& 0x0000000f);
704 // Store in the next character. Bias against either '0' or 'A'
707 if (ThisWchar
<= 9) {
709 *NextWchar
= ThisWchar
+ L
'0';
713 *NextWchar
= ThisWchar
+ L
'A' - 0xA;
716 Length
+= sizeof( WCHAR
);
719 // Shift out the low 4 bits of the offset.
722 BiasedDirentOffset
>>= 4;
724 } while (BiasedDirentOffset
!= 0);
727 // Now store in the tilde character.
732 Length
+= sizeof( WCHAR
);
735 // Set the length of this string.
738 BiasedShortName
.Length
= Length
;
739 BiasedShortName
.Buffer
= NextWchar
;
742 // Figure out the maximum number of characters we can copy of the base
743 // name. We subract the number of characters in the dirent string from 8.
744 // We will copy this many characters or stop when we reach a '.' character
745 // or a '~' character in the name.
748 MaximumBaseBytes
= 16 - Length
;
753 // Keep copying from the base name until we hit a '.', '~' or the end of
757 NextWchar
= ShortFileName
;
760 while ((BaseNameOffset
< ShortName
.Length
) &&
761 (ShortName
.Buffer
[BaseNameOffset
/ 2] != L
'.')) {
764 // Remember if we found a tilde character in the short name,
765 // so we don't copy it or anything following it.
768 if (ShortName
.Buffer
[BaseNameOffset
/ 2] == L
'~') {
774 // We need to consider the DBCS code page, because Unicode characters
775 // may use 2 bytes as DBCS characters.
779 #pragma prefast(push)
780 #pragma prefast(suppress:26014, "OemNameOffset <= BaseNameOffset throughout this loop; OemName buffer previously allocated based on ShortName's length.")
782 if (FsRtlIsLeadDbcsCharacter(OemName
.Buffer
[OemNameOffset
])) {
789 if ((OemNameOffset
+ (BiasedShortName
.Length
/ sizeof(WCHAR
))) > 8) {
791 OverflowBuffer
= TRUE
;
800 // Only copy the bytes if we still have space for the dirent string.
803 if (!FoundTilde
&& !OverflowBuffer
&& (BaseNameOffset
< MaximumBaseBytes
)) {
805 *NextWchar
= ShortName
.Buffer
[BaseNameOffset
/ 2];
806 Length
+= sizeof( WCHAR
);
813 RtlFreeOemString(&OemName
);
816 // Now copy the dirent string into the biased name buffer.
820 #pragma prefast(push)
822 RtlCopyMemory( NextWchar
,
823 BiasedShortName
.Buffer
,
824 BiasedShortName
.Length
);
829 Length
+= BiasedShortName
.Length
;
830 NextWchar
+= (BiasedShortName
.Length
/ sizeof( WCHAR
));
833 // Now copy any remaining bytes over to the biased short name.
836 if (BaseNameOffset
!= ShortName
.Length
) {
838 RtlCopyMemory( NextWchar
,
839 &ShortName
.Buffer
[BaseNameOffset
/ 2],
840 ShortName
.Length
- BaseNameOffset
);
842 Length
+= (ShortName
.Length
- (USHORT
) BaseNameOffset
);
846 // The final short name is stored in the user's buffer.
849 *ShortByteCount
= Length
;
854 CdIsNameInExpression (
855 _In_ PIRP_CONTEXT IrpContext
,
856 _In_ PCD_NAME CurrentName
,
857 _In_ PCD_NAME SearchExpression
,
858 _In_ ULONG WildcardFlags
,
859 _In_ BOOLEAN CheckVersion
866 This routine will compare two CdName strings. We assume that if this
867 is to be a case-insensitive search then they are already upcased.
869 We compare the filename portions of the name and if they match we
870 compare the version strings if requested.
874 CurrentName - Filename from the disk.
876 SearchExpression - Filename expression to use for match.
878 WildcardFlags - Flags field which indicates which parts of the
879 search expression might have wildcards. These flags are the
880 same as in the Ccb flags field.
882 CheckVersion - Indicates whether we should check both the name and the
883 version strings or just the name.
887 BOOLEAN - TRUE if the expressions match, FALSE otherwise.
892 BOOLEAN Match
= TRUE
;
895 UNREFERENCED_PARAMETER( IrpContext
);
898 // If there are wildcards in the expression then we call the
899 // appropriate FsRtlRoutine.
902 if (FlagOn( WildcardFlags
, CCB_FLAG_ENUM_NAME_EXP_HAS_WILD
)) {
904 Match
= FsRtlIsNameInExpression( &SearchExpression
->FileName
,
905 &CurrentName
->FileName
,
910 // Otherwise do a direct memory comparison for the name string.
915 if ((CurrentName
->FileName
.Length
!= SearchExpression
->FileName
.Length
) ||
916 (!RtlEqualMemory( CurrentName
->FileName
.Buffer
,
917 SearchExpression
->FileName
.Buffer
,
918 CurrentName
->FileName
.Length
))) {
925 // Check the version numbers if requested by the user and we have a
926 // match on the name and the version number is present.
929 if (Match
&& CheckVersion
&& SearchExpression
->VersionString
.Length
&&
930 !FlagOn( WildcardFlags
, CCB_FLAG_ENUM_VERSION_MATCH_ALL
)) {
933 // If there are wildcards in the expression then call the
934 // appropriate search expression.
937 if (FlagOn( WildcardFlags
, CCB_FLAG_ENUM_VERSION_EXP_HAS_WILD
)) {
939 Match
= FsRtlIsNameInExpression( &SearchExpression
->VersionString
,
940 &CurrentName
->VersionString
,
945 // Otherwise do a direct memory comparison for the name string.
950 if ((CurrentName
->VersionString
.Length
!= SearchExpression
->VersionString
.Length
) ||
951 (!RtlEqualMemory( CurrentName
->VersionString
.Buffer
,
952 SearchExpression
->VersionString
.Buffer
,
953 CurrentName
->VersionString
.Length
))) {
965 CdShortNameDirentOffset (
966 _In_ PIRP_CONTEXT IrpContext
,
967 _In_ PUNICODE_STRING Name
974 This routine is called to examine a name to see if the dirent offset string is contained.
975 This consists of a tilde character followed by the offset represented as a hexadecimal
976 characters. We don't do any other checks to see if this is a short name. We
977 catch that later outside this routine.
981 Name - This is the CdName to examine.
985 ULONG - MAXULONG if there is no valid dirent offset string embedded, otherwise the
986 convert the value to numeric form.
991 ULONG ResultOffset
= MAXULONG
;
992 ULONG RemainingByteCount
= Name
->Length
;
994 BOOLEAN FoundTilde
= FALSE
;
1000 UNREFERENCED_PARAMETER( IrpContext
);
1003 // Walk through the name until we either reach the end of the name
1004 // or find a tilde character.
1007 for (NextWchar
= Name
->Buffer
;
1008 RemainingByteCount
!= 0;
1009 NextWchar
+= 1, RemainingByteCount
-= sizeof( WCHAR
)) {
1012 // Check if this is a dot. Stop constructing any string if
1016 if (*NextWchar
== L
'.') {
1022 // If we already found a tilde then check this character as a
1023 // valid character. It must be a digit or A to F.
1028 if ((*NextWchar
< L
'0') ||
1029 (*NextWchar
> L
'F') ||
1030 ((*NextWchar
> L
'9') && (*NextWchar
< 'A'))) {
1032 ResultOffset
= MAXULONG
;
1037 // Shift the result by 4 bits and add in this new character.
1042 if (*NextWchar
< L
'A') {
1044 ResultOffset
+= *NextWchar
- L
'0';
1048 ResultOffset
+= (*NextWchar
- L
'A') + 10;
1055 // If this is a tilde then start building the dirent string.
1058 if (*NextWchar
== L
'~') {
1065 return ResultOffset
;
1070 // Local support routine
1073 FSRTL_COMPARISON_RESULT
1074 CdFullCompareNames (
1075 _In_ PIRP_CONTEXT IrpContext
,
1076 _In_ PUNICODE_STRING NameA
,
1077 _In_ PUNICODE_STRING NameB
1082 Routine Description:
1084 This function compares two names as fast as possible. Note that since
1085 this comparison is case sensitive we can do a direct memory comparison.
1089 NameA & NameB - The names to compare.
1093 COMPARISON - returns
1095 LessThan if NameA < NameB lexicalgraphically,
1096 GreaterThan if NameA > NameB lexicalgraphically,
1097 EqualTo if NameA is equal to NameB
1103 ULONG MinLength
= NameA
->Length
;
1104 FSRTL_COMPARISON_RESULT Result
= LessThan
;
1108 UNREFERENCED_PARAMETER( IrpContext
);
1111 // Figure out the minimum of the two lengths
1114 if (NameA
->Length
> NameB
->Length
) {
1116 MinLength
= NameB
->Length
;
1117 Result
= GreaterThan
;
1119 } else if (NameA
->Length
== NameB
->Length
) {
1125 // Loop through looking at all of the characters in both strings
1126 // testing for equalilty, less than, and greater than
1129 i
= RtlCompareMemory( NameA
->Buffer
, NameB
->Buffer
, MinLength
);
1131 if (i
< MinLength
) {
1134 // We know the offset of the first character which is different.
1137 return ((NameA
->Buffer
[ i
/ 2 ] < NameB
->Buffer
[ i
/ 2 ]) ?
1143 // The names match up to the length of the shorter string.
1144 // The shorter string lexically appears first.