3 Copyright (c) 1989-2000 Microsoft Corporation
11 This module implements the Path Table support routines for Cdfs.
13 The path table on a CDROM is a condensed summary of the entire
14 directory structure. It is stored on a number of contiguous sectors
15 on the disk. Each directory on the disk has an entry in the path
16 table. The entries are aligned on USHORT boundaries and MAY span
17 sector boundaries. The entries are stored as a breadth-first search.
19 The first entry in the table contains the entry for the root. The
20 next entries will consist of the contents of the root directory. The
21 next entries will consist of the all the directories at the next level
22 of the tree. The children of a given directory will be grouped together.
24 The directories are assigned ordinal numbers based on their position in
25 the path table. The root dirctory is assigned ordinal value 1.
33 +----------------------------+ +------------------------+
35 DirName | \ | a | b |c| | c | d | e |
37 Parent #| 1 | 1 | 1 | | | 2 | 2 | 3 |
38 +----------------------------+ +------------------------+
54 - Position scan at known offset in the path table. Path Entry at
55 this offset must exist and is known to be valid. Used when
56 scanning for the children of a given directory.
58 - Position scan at known offset in the path table. Path Entry is
59 known to start at this location but the bounds must be checked
62 - Move to next path entry in the table.
64 - Update a common path entry structure with the details of the
65 on-disk structure. This is used to smooth out the differences
66 in the on-disk structures.
68 - Update the filename in the in-memory path entry with the bytes
69 off the disk. For Joliet disks we will have
70 to convert to little endian. We assume that directories
71 don't have version numbers.
79 // The Bug check file id for this module
82 #define BugCheckFileId (CDFS_BUG_CHECK_PATHSUP)
91 // _In_ PIRP_CONTEXT IrpContext,
92 // _In_ PPATH_ENUM_CONTEXT PathContext
96 #define CdRawPathEntry(IC, PC) \
97 Add2Ptr( (PC)->Data, (PC)->DataOffset, PRAW_PATH_ENTRY )
100 // Local support routines
104 CdMapPathTableBlock (
105 _In_ PIRP_CONTEXT IrpContext
,
107 _In_ LONGLONG BaseOffset
,
108 _Inout_ PPATH_ENUM_CONTEXT PathContext
111 _Success_(return != FALSE
)
113 CdUpdatePathEntryFromRawPathEntry (
114 _In_ PIRP_CONTEXT IrpContext
,
116 _In_ BOOLEAN VerifyBounds
,
117 _In_ PPATH_ENUM_CONTEXT PathContext
,
118 _Out_ PPATH_ENTRY PathEntry
122 #pragma alloc_text(PAGE, CdFindPathEntry)
123 #pragma alloc_text(PAGE, CdLookupPathEntry)
124 #pragma alloc_text(PAGE, CdLookupNextPathEntry)
125 #pragma alloc_text(PAGE, CdMapPathTableBlock)
126 #pragma alloc_text(PAGE, CdUpdatePathEntryFromRawPathEntry)
127 #pragma alloc_text(PAGE, CdUpdatePathEntryName)
133 _In_ PIRP_CONTEXT IrpContext
,
134 _In_ ULONG PathEntryOffset
,
136 _In_ BOOLEAN VerifyBounds
,
137 _Inout_ PCOMPOUND_PATH_ENTRY CompoundPathEntry
144 This routine is called to initiate a walk through a path table. We are
145 looking for a path table entry at location PathEntryOffset.
149 PathEntryOffset - This is our target point in the Path Table. We know that
150 a path entry must begin at this point although we may have to verify
153 Ordinal - Ordinal number for the directory at the PathEntryOffset above.
155 VerifyBounds - Indicates whether we need to check the validity of
158 CompoundPathEntry - PathEnumeration context and in-memory path entry. This
159 has been initialized outside of this call.
168 PPATH_ENUM_CONTEXT PathContext
= &CompoundPathEntry
->PathContext
;
169 LONGLONG CurrentBaseOffset
;
174 // Compute the starting base and starting path table offset.
177 CurrentBaseOffset
= SectorTruncate( PathEntryOffset
);
180 // Map the next block in the Path Table.
183 CdMapPathTableBlock( IrpContext
,
184 IrpContext
->Vcb
->PathTableFcb
,
189 // Set up our current offset into the Path Context.
192 PathContext
->DataOffset
= PathEntryOffset
- PathContext
->BaseOffset
;
195 // Update the in-memory structure for this path entry.
198 (VOID
) CdUpdatePathEntryFromRawPathEntry( IrpContext
,
201 &CompoundPathEntry
->PathContext
,
202 &CompoundPathEntry
->PathEntry
);
207 CdLookupNextPathEntry (
208 _In_ PIRP_CONTEXT IrpContext
,
209 _Inout_ PPATH_ENUM_CONTEXT PathContext
,
210 _Inout_ PPATH_ENTRY PathEntry
217 This routine is called to move to the next path table entry. We know
218 the offset and the length of the current entry. We start by computing
219 the offset of the next entry and determine if it is contained in the
220 table. Then we check to see if we need to move to the next sector in
221 the path table. We always map two sectors at a time so we don't
222 have to deal with any path entries which span sectors. We move to
223 the next sector if we are in the second sector of the current mapped
226 We look up the next entry and update the path entry structure with
227 the values out of the raw sector but don't update the CdName structure.
231 PathContext - Enumeration context for this scan of the path table.
233 PathEntry - In-memory representation of the on-disk path table entry.
237 BOOLEAN - TRUE if another entry is found, FALSE otherwise.
238 This routine may raise on error.
243 LONGLONG CurrentBaseOffset
;
248 // Get the offset of the next path entry within the current
252 PathContext
->DataOffset
+= PathEntry
->PathEntryLength
;
255 // If we are in the last data block then check if we are beyond the
259 if (PathContext
->LastDataBlock
) {
261 if (PathContext
->DataOffset
>= PathContext
->DataLength
) {
267 // If we are not in the last data block of the path table and
268 // this offset is in the second sector then move to the next
272 } else if (PathContext
->DataOffset
>= SECTOR_SIZE
) {
274 CurrentBaseOffset
= PathContext
->BaseOffset
+ SECTOR_SIZE
;
276 CdMapPathTableBlock( IrpContext
,
277 IrpContext
->Vcb
->PathTableFcb
,
282 // Set up our current offset into the Path Context.
285 PathContext
->DataOffset
-= SECTOR_SIZE
;
289 // Now update the path entry with the values from the on-disk
293 return CdUpdatePathEntryFromRawPathEntry( IrpContext
,
294 PathEntry
->Ordinal
+ 1,
300 _Success_(return != FALSE
)\f
303 _In_ PIRP_CONTEXT IrpContext
,
305 _In_ PCD_NAME DirName
,
306 _In_ BOOLEAN IgnoreCase
,
307 _Inout_ PCOMPOUND_PATH_ENTRY CompoundPathEntry
314 This routine will walk through the path table looking for a matching entry for DirName
315 among the child directories of the ParentFcb.
319 ParentFcb - This is the directory we are examining. We know the ordinal and path table
320 offset for this directory in the path table. If this is the first scan for this
321 Fcb we will update the first child offset for this directory in the path table.
323 DirName - This is the name we are searching for. This name will not contain wildcard
324 characters. The name will also not have a version string.
326 IgnoreCase - Indicates if this search is exact or ignore case.
328 CompoundPathEntry - Complete path table enumeration structure. We will have initialized
329 it for the search on entry. This will be positioned at the matching name if found.
333 BOOLEAN - TRUE if matching entry found, FALSE otherwise.
338 BOOLEAN Found
= FALSE
;
339 BOOLEAN UpdateChildOffset
= TRUE
;
341 ULONG StartingOffset
;
342 ULONG StartingOrdinal
;
347 // Position ourselves at either the first child or at the directory itself.
348 // Lock the Fcb to get this value and remember whether to update with the first
352 StartingOffset
= CdQueryFidPathTableOffset( ParentFcb
->FileId
);
353 StartingOrdinal
= ParentFcb
->Ordinal
;
356 // ISO 9660 9.4.4 restricts the backpointer from child to parent in a
357 // pathtable entry to 16bits. Although we internally store ordinals
358 // as 32bit values, it is impossible to search for the children of a
359 // directory whose ordinal value is greater than MAXUSHORT. Media that
360 // could induce such a search is illegal.
362 // Note that it is not illegal to have more than MAXUSHORT directories.
365 if (ParentFcb
->Ordinal
> MAXUSHORT
) {
367 CdRaiseStatus( IrpContext
, STATUS_DISK_CORRUPT_ERROR
);
370 CdLockFcb( IrpContext
, ParentFcb
);
372 if (ParentFcb
->ChildPathTableOffset
!= 0) {
374 StartingOffset
= ParentFcb
->ChildPathTableOffset
;
375 StartingOrdinal
= ParentFcb
->ChildOrdinal
;
376 UpdateChildOffset
= FALSE
;
378 } else if (ParentFcb
== ParentFcb
->Vcb
->RootIndexFcb
) {
380 UpdateChildOffset
= FALSE
;
383 CdUnlockFcb( IrpContext
, ParentFcb
);
385 CdLookupPathEntry( IrpContext
, StartingOffset
, StartingOrdinal
, FALSE
, CompoundPathEntry
);
388 // Loop until we find a match or are beyond the children for this directory.
394 // If we are beyond this directory then return FALSE.
397 if (CompoundPathEntry
->PathEntry
.ParentOrdinal
> ParentFcb
->Ordinal
) {
400 // Update the Fcb with the offsets for the children in the path table.
403 if (UpdateChildOffset
) {
405 CdLockFcb( IrpContext
, ParentFcb
);
407 ParentFcb
->ChildPathTableOffset
= StartingOffset
;
408 ParentFcb
->ChildOrdinal
= StartingOrdinal
;
410 CdUnlockFcb( IrpContext
, ParentFcb
);
417 // If we are within the children of this directory then check for a match.
420 if (CompoundPathEntry
->PathEntry
.ParentOrdinal
== ParentFcb
->Ordinal
) {
423 // Update the child offset if not yet done.
426 if (UpdateChildOffset
) {
428 CdLockFcb( IrpContext
, ParentFcb
);
430 ParentFcb
->ChildPathTableOffset
= CompoundPathEntry
->PathEntry
.PathTableOffset
;
431 ParentFcb
->ChildOrdinal
= CompoundPathEntry
->PathEntry
.Ordinal
;
433 CdUnlockFcb( IrpContext
, ParentFcb
);
435 UpdateChildOffset
= FALSE
;
439 // Update the name in the path entry.
442 CdUpdatePathEntryName( IrpContext
, &CompoundPathEntry
->PathEntry
, IgnoreCase
);
445 // Now compare the names for an exact match.
448 if (CdIsNameInExpression( IrpContext
,
449 &CompoundPathEntry
->PathEntry
.CdCaseDirName
,
455 // Let our caller know we have a match.
464 // Go to the next entry in the path table. Remember the current position
465 // in the event we update the Fcb.
468 StartingOffset
= CompoundPathEntry
->PathEntry
.PathTableOffset
;
469 StartingOrdinal
= CompoundPathEntry
->PathEntry
.Ordinal
;
471 } while (CdLookupNextPathEntry( IrpContext
,
472 &CompoundPathEntry
->PathContext
,
473 &CompoundPathEntry
->PathEntry
));
480 // Local support routine
484 CdMapPathTableBlock (
485 _In_ PIRP_CONTEXT IrpContext
,
487 _In_ LONGLONG BaseOffset
,
488 _Inout_ PPATH_ENUM_CONTEXT PathContext
495 This routine is called to map (or allocate and copy) the next
496 data block in the path table. We check if the next block will
497 span a view boundary and allocate an auxilary buffer in that case.
501 Fcb - This is the Fcb for the Path Table.
503 BaseOffset - Offset of the first sector to map. This will be on a
506 PathContext - Enumeration context to update in this routine.
523 UNREFERENCED_PARAMETER( IrpContext
);
526 // Map the new block and set the enumeration context to this
527 // point. Allocate an auxilary buffer if necessary.
530 CurrentLength
= 2 * SECTOR_SIZE
;
532 if (CurrentLength
>= (ULONG
) (Fcb
->FileSize
.QuadPart
- BaseOffset
)) {
534 CurrentLength
= (ULONG
) (Fcb
->FileSize
.QuadPart
- BaseOffset
);
537 // We know this is the last data block for this
541 PathContext
->LastDataBlock
= TRUE
;
545 // Set context values.
548 PathContext
->BaseOffset
= (ULONG
) BaseOffset
;
549 PathContext
->DataLength
= CurrentLength
;
552 // Drop the previous sector's mapping
555 CdUnpinData( IrpContext
, &PathContext
->Bcb
);
558 // Check if spanning a view section. The following must
559 // be true before we take this step.
561 // Data length is more than one sector.
562 // Starting offset must be one sector before the
563 // cache manager VACB boundary.
566 if ((CurrentLength
> SECTOR_SIZE
) &&
567 (FlagOn( ((ULONG
) BaseOffset
), VACB_MAPPING_MASK
) == LAST_VACB_SECTOR_OFFSET
)) {
570 // Map each sector individually and store into an auxilary
574 SectorSize
= SECTOR_SIZE
;
578 PathContext
->Data
= FsRtlAllocatePoolWithTag( CdPagedPool
,
580 TAG_SPANNING_PATH_TABLE
);
581 PathContext
->AllocatedData
= TRUE
;
583 while (PassCount
--) {
585 CcMapData( Fcb
->FileObject
,
586 (PLARGE_INTEGER
) &BaseOffset
,
592 RtlCopyMemory( Add2Ptr( PathContext
->Data
, DataOffset
, PVOID
),
596 CdUnpinData( IrpContext
, &PathContext
->Bcb
);
598 BaseOffset
+= SECTOR_SIZE
;
599 SectorSize
= CurrentLength
- SECTOR_SIZE
;
600 DataOffset
= SECTOR_SIZE
;
604 // Otherwise we can just map the data into the cache.
610 // There is a slight chance that we have allocated an
611 // auxilary buffer on the previous sector.
614 if (PathContext
->AllocatedData
) {
616 CdFreePool( &PathContext
->Data
);
617 PathContext
->AllocatedData
= FALSE
;
620 CcMapData( Fcb
->FileObject
,
621 (PLARGE_INTEGER
) &BaseOffset
,
625 &PathContext
->Data
);
633 // Local support routine
635 _Success_(return != FALSE
)
637 CdUpdatePathEntryFromRawPathEntry (
638 _In_ PIRP_CONTEXT IrpContext
,
640 _In_ BOOLEAN VerifyBounds
,
641 _In_ PPATH_ENUM_CONTEXT PathContext
,
642 _Out_ PPATH_ENTRY PathEntry
649 This routine is called to update the in-memory Path Entry from the on-disk
650 path entry. We also do a careful check of the bounds if requested and we
651 are in the last data block of the path table.
655 Ordinal - Ordinal number for this directory.
657 VerifyBounds - Check that the current raw Path Entry actually fits
658 within the data block.
660 PathContext - Current path table enumeration context.
662 PathEntry - Pointer to the in-memory path entry structure.
667 FALSE if we've hit the end of the pathtable - zero name length && PT size is a multiple
668 of blocksize. This is a workaround for some Video CDs. Win 9x works around this.
670 This routine may raise.
675 PRAW_PATH_ENTRY RawPathEntry
= CdRawPathEntry( IrpContext
, PathContext
);
676 ULONG RemainingDataLength
;
681 // Check for a name length of zero. This is the first byte of the record,
682 // and there must be at least one byte remaining in the buffer else we
683 // wouldn't be here (caller would have spotted buffer end).
686 PathEntry
->DirNameLen
= CdRawPathIdLen( IrpContext
, RawPathEntry
);
688 if (0 == PathEntry
->DirNameLen
) {
691 // If we are in the last block, and the path table size (ie last block) is a
692 // multiple of block size, then we will consider this the end of the path table
693 // rather than raising an error. Workaround for NTI Cd Maker video CDs which
694 // round path table length to blocksize multiple. In all other cases we consider
695 // a zero length name to be corruption.
698 if ( PathContext
->LastDataBlock
&&
699 (0 == BlockOffset( IrpContext
->Vcb
, PathContext
->DataLength
))) {
704 CdRaiseStatus( IrpContext
, STATUS_DISK_CORRUPT_ERROR
);
708 // Check if we should verify the path entry. If we are not in the last
709 // data block then there is nothing to check.
712 if (PathContext
->LastDataBlock
&& VerifyBounds
) {
715 // Quick check to see if the maximum size is still available. This
716 // will handle most cases and we don't need to access any of the
720 RemainingDataLength
= PathContext
->DataLength
- PathContext
->DataOffset
;
722 if (RemainingDataLength
< sizeof( RAW_PATH_ENTRY
)) {
725 // Make sure the remaining bytes hold the path table entries.
726 // Do the following checks.
728 // - A minimal path table entry will fit (and then check)
729 // - This path table entry (with dir name) will fit.
732 if ((RemainingDataLength
< MIN_RAW_PATH_ENTRY_LEN
) ||
733 (RemainingDataLength
< (ULONG
) (CdRawPathIdLen( IrpContext
, RawPathEntry
) + MIN_RAW_PATH_ENTRY_LEN
- 1))) {
735 CdRaiseStatus( IrpContext
, STATUS_DISK_CORRUPT_ERROR
);
741 // The ordinal number of this directory is passed in.
742 // Compute the path table offset of this entry.
745 PathEntry
->Ordinal
= Ordinal
;
746 PathEntry
->PathTableOffset
= PathContext
->BaseOffset
+ PathContext
->DataOffset
;
749 // We know we can safely access all of the fields of the raw path table at
753 // Bias the disk offset by the number of logical blocks
756 CopyUchar4( &PathEntry
->DiskOffset
, CdRawPathLoc( IrpContext
, RawPathEntry
));
758 PathEntry
->DiskOffset
+= CdRawPathXar( IrpContext
, RawPathEntry
);
760 CopyUchar2( &PathEntry
->ParentOrdinal
, &RawPathEntry
->ParentNum
);
762 PathEntry
->PathEntryLength
= PathEntry
->DirNameLen
+ MIN_RAW_PATH_ENTRY_LEN
- 1;
765 // Align the path entry length on a ushort boundary.
768 PathEntry
->PathEntryLength
= WordAlign( PathEntry
->PathEntryLength
);
770 PathEntry
->DirName
= (PCHAR
)RawPathEntry
->DirId
;
777 // Local support routine
781 CdUpdatePathEntryName (
782 _In_ PIRP_CONTEXT IrpContext
,
783 _Inout_ PPATH_ENTRY PathEntry
,
784 _In_ BOOLEAN IgnoreCase
791 This routine will store the directory name into the CdName in the
792 path entry. If this is a Joliet name then we will make sure we have
793 an allocated buffer and need to convert from big endian to little
794 endian. We also correctly update the case name. If this operation is ignore
795 case then we need an auxilary buffer for the name.
797 For an Ansi disk we can use the name from the disk for the exact case. We only
798 need to allocate a buffer for the ignore case name. The on-disk representation of
799 a Unicode name is useless for us. In this case we will need a name buffer for
800 both names. We store a buffer in the PathEntry which can hold two 8.3 unicode
801 names. This means we will almost never need to allocate a buffer in the Ansi case
802 (we only need one buffer and already have 48 characters).
806 PathEntry - Pointer to a path entry structure. We have already updated
807 this path entry with the values from the raw path entry.
822 // Check if this is a self entry. We use a fixed string for this.
824 // Self-Entry - Length is 1, value is 0.
827 if ((*PathEntry
->DirName
== 0) &&
828 (PathEntry
->DirNameLen
== 1)) {
831 // There should be no allocated buffers.
834 NT_ASSERT( !FlagOn( PathEntry
->Flags
, PATH_ENTRY_FLAG_ALLOC_BUFFER
));
837 // Now use one of the hard coded directory names.
840 PathEntry
->CdDirName
.FileName
= CdUnicodeDirectoryNames
[0];
843 // Show that there is no version number.
846 PathEntry
->CdDirName
.VersionString
.Length
= 0;
849 // The case name is identical.
852 PathEntry
->CdCaseDirName
= PathEntry
->CdDirName
;
862 // Compute how large a buffer we will need. If this is an ignore
863 // case operation then we will want a double size buffer. If the disk is not
864 // a Joliet disk then we might need two bytes for each byte in the name.
867 Length
= PathEntry
->DirNameLen
;
874 if (!FlagOn( IrpContext
->Vcb
->VcbState
, VCB_STATE_JOLIET
)) {
876 Length
*= sizeof( WCHAR
);
880 // Now decide if we need to allocate a new buffer. We will if
881 // this name won't fit in the embedded name buffer and it is
882 // larger than the current allocated buffer. We always use the
883 // allocated buffer if present.
885 // If we haven't allocated a buffer then use the embedded buffer if the data
886 // will fit. This is the typical case.
889 if (!FlagOn( PathEntry
->Flags
, PATH_ENTRY_FLAG_ALLOC_BUFFER
) &&
890 (Length
<= sizeof( PathEntry
->NameBuffer
))) {
892 PathEntry
->CdDirName
.FileName
.MaximumLength
= sizeof( PathEntry
->NameBuffer
);
893 PathEntry
->CdDirName
.FileName
.Buffer
= PathEntry
->NameBuffer
;
898 // We need to use an allocated buffer. Check if the current buffer
902 if (Length
> PathEntry
->CdDirName
.FileName
.MaximumLength
) {
905 // Free any allocated buffer.
908 if (FlagOn( PathEntry
->Flags
, PATH_ENTRY_FLAG_ALLOC_BUFFER
)) {
910 CdFreePool( &PathEntry
->CdDirName
.FileName
.Buffer
);
911 ClearFlag( PathEntry
->Flags
, PATH_ENTRY_FLAG_ALLOC_BUFFER
);
914 PathEntry
->CdDirName
.FileName
.Buffer
= FsRtlAllocatePoolWithTag( CdPagedPool
,
916 TAG_PATH_ENTRY_NAME
);
918 SetFlag( PathEntry
->Flags
, PATH_ENTRY_FLAG_ALLOC_BUFFER
);
920 PathEntry
->CdDirName
.FileName
.MaximumLength
= (USHORT
) Length
;
925 // We now have a buffer for the name. We need to either convert the on-disk bigendian
926 // to little endian or covert the name to Unicode.
929 if (!FlagOn( IrpContext
->Vcb
->VcbState
, VCB_STATE_JOLIET
)) {
931 Status
= RtlOemToUnicodeN( PathEntry
->CdDirName
.FileName
.Buffer
,
932 PathEntry
->CdDirName
.FileName
.MaximumLength
,
935 PathEntry
->DirNameLen
);
937 NT_ASSERT( Status
== STATUS_SUCCESS
);
938 __analysis_assert( Status
== STATUS_SUCCESS
);
939 PathEntry
->CdDirName
.FileName
.Length
= (USHORT
) Length
;
944 // Convert this string to little endian.
947 CdConvertBigToLittleEndian( IrpContext
,
949 PathEntry
->DirNameLen
,
950 (PCHAR
) PathEntry
->CdDirName
.FileName
.Buffer
);
952 PathEntry
->CdDirName
.FileName
.Length
= (USHORT
) PathEntry
->DirNameLen
;
956 // There is no version string.
959 PathEntry
->CdDirName
.VersionString
.Length
=
960 PathEntry
->CdCaseDirName
.VersionString
.Length
= 0;
963 // If the name string ends with a period then knock off the last
967 if (PathEntry
->CdDirName
.FileName
.Buffer
[(PathEntry
->CdDirName
.FileName
.Length
- sizeof( WCHAR
)) / 2] == L
'.') {
970 // Shrink the filename length.
973 PathEntry
->CdDirName
.FileName
.Length
-= sizeof( WCHAR
);
977 // Update the case name buffer if necessary. If this is an exact case
978 // operation then just copy the exact case string.
983 PathEntry
->CdCaseDirName
.FileName
.Buffer
= Add2Ptr( PathEntry
->CdDirName
.FileName
.Buffer
,
984 PathEntry
->CdDirName
.FileName
.MaximumLength
/ 2,
987 PathEntry
->CdCaseDirName
.FileName
.MaximumLength
= PathEntry
->CdDirName
.FileName
.MaximumLength
/ 2;
989 CdUpcaseName( IrpContext
,
990 &PathEntry
->CdDirName
,
991 &PathEntry
->CdCaseDirName
);
995 PathEntry
->CdCaseDirName
= PathEntry
->CdDirName
;