/*++ Copyright (c) 1993 Microsoft Corporation Module Name: dir.c Abstract: This module implements the file directory routines for the Netware Redirector. Author: Manny Weiser (mannyw) 4-Mar-1993 Revision History: --*/ #include "procs.h" // // Local debug trace level // #define Dbg (DEBUG_TRACE_DIRCTRL) NTSTATUS NwCommonDirectoryControl ( IN PIRP_CONTEXT pIrpContext ); NTSTATUS NwQueryDirectory ( IN PIRP_CONTEXT pIrpContext, IN PICB pIcb ); NTSTATUS GetNextFile( PIRP_CONTEXT pIrpContext, PICB Icb, PULONG fileIndexLow, PULONG fileIndexHigh, UCHAR SearchAttributes, PNW_DIRECTORY_INFO NwDirInfo ); NTSTATUS NtSearchMaskToNw( IN PUNICODE_STRING UcSearchMask, IN OUT POEM_STRING OemSearchMask, IN PICB Icb, IN BOOLEAN ShortNameSearch ); #if 0 VOID NwCancelFindNotify ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ); #endif #ifdef ALLOC_PRAGMA #pragma alloc_text( PAGE, NwFsdDirectoryControl ) #pragma alloc_text( PAGE, NwQueryDirectory ) #pragma alloc_text( PAGE, GetNextFile ) #pragma alloc_text( PAGE, NtSearchMaskToNw ) #ifndef QFE_BUILD #pragma alloc_text( PAGE1, NwCommonDirectoryControl ) #endif #endif #if 0 // Not pageable // see ifndef QFE_BUILD above #endif NTSTATUS NwFsdDirectoryControl ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine is the FSD routine that handles directory control functions (i.e., query and notify). Arguments: NwfsDeviceObject - Supplies the device object for the directory function. Irp - Supplies the IRP to process. Return Value: NTSTATUS - The result status. --*/ { PIRP_CONTEXT pIrpContext = NULL; NTSTATUS status; BOOLEAN TopLevel; PAGED_CODE(); DebugTrace(+1, Dbg, "NwFsdDirectoryControl\n", 0); // // Call the common directory control routine. // FsRtlEnterFileSystem(); TopLevel = NwIsIrpTopLevel( Irp ); try { pIrpContext = AllocateIrpContext( Irp ); status = NwCommonDirectoryControl( pIrpContext ); } except(NwExceptionFilter( Irp, GetExceptionInformation() )) { if ( pIrpContext == NULL ) { // // If we couldn't allocate an irp context, just complete // irp without any fanfare. // status = STATUS_INSUFFICIENT_RESOURCES; Irp->IoStatus.Status = status; Irp->IoStatus.Information = 0; IoCompleteRequest ( Irp, IO_NETWORK_INCREMENT ); } else { // // We had some trouble trying to perform the requested // operation, so we'll abort the I/O request with // the error status that we get back from the // execption code. // status = NwProcessException( pIrpContext, GetExceptionCode() ); } } if ( pIrpContext ) { NwCompleteRequest( pIrpContext, status ); } if ( TopLevel ) { NwSetTopLevelIrp( NULL ); } FsRtlExitFileSystem(); // // Return to the caller. // DebugTrace(-1, Dbg, "NwFsdDirectoryControl -> %08lx\n", status ); return status; } NTSTATUS NwCommonDirectoryControl ( IN PIRP_CONTEXT IrpContext ) /*++ Routine Description: This routine does the common code for directory control functions. Arguments: IrpContext - Supplies the request being processed. Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS status; PIRP Irp; PIO_STACK_LOCATION irpSp; NODE_TYPE_CODE nodeTypeCode; PICB icb; PDCB dcb; PVOID fsContext; // // Get the current stack location // Irp = IrpContext->pOriginalIrp; irpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "CommonDirectoryControl...\n", 0); DebugTrace( 0, Dbg, "Irp = %08lx\n", (ULONG_PTR)Irp); // // Decode the file object to figure out who we are. If the result // is not an ICB then its an illegal parameter. // if ((nodeTypeCode = NwDecodeFileObject( irpSp->FileObject, &fsContext, (PVOID *)&icb )) != NW_NTC_ICB) { DebugTrace(0, Dbg, "Not a directory\n", 0); status = STATUS_INVALID_PARAMETER; DebugTrace(-1, Dbg, "CommonDirectoryControl -> %08lx\n", status ); return status; } dcb = (PDCB)icb->SuperType.Fcb; nodeTypeCode = dcb->NodeTypeCode; if ( nodeTypeCode != NW_NTC_DCB ) { DebugTrace(0, Dbg, "Not a directory\n", 0); status = STATUS_INVALID_PARAMETER; DebugTrace(-1, Dbg, "CommonDirectoryControl -> %08lx\n", status ); return status; } IrpContext->pScb = icb->SuperType.Fcb->Scb; IrpContext->pNpScb = IrpContext->pScb->pNpScb; IrpContext->Icb = icb; // // Acquire exclusive access to the DCB. Get to front of queue // first to avoid deadlock potential. // NwAppendToQueueAndWait( IrpContext ); NwAcquireExclusiveFcb( dcb->NonPagedFcb, TRUE ); try { NwVerifyIcb( icb ); // // We know this is a directory control so we'll case on the // minor function, and call the appropriate work routines. // switch (irpSp->MinorFunction) { case IRP_MN_QUERY_DIRECTORY: status = NwQueryDirectory( IrpContext, icb ); break; case IRP_MN_NOTIFY_CHANGE_DIRECTORY: #if 0 if ( !icb->FailedFindNotify ) { icb->FailedFindNotify = TRUE; #endif status = STATUS_NOT_SUPPORTED; #if 0 } else { // // HACKHACK // Cover for process that keeps trying to use // find notify even though we don't support it. // NwAcquireExclusiveRcb( &NwRcb, TRUE ); IoAcquireCancelSpinLock( &Irp->CancelIrql ); if ( Irp->Cancel ) { status = STATUS_CANCELLED; } else { InsertTailList( &FnList, &IrpContext->NextRequest ); IoMarkIrpPending( Irp ); IoSetCancelRoutine( Irp, NwCancelFindNotify ); status = STATUS_PENDING; } IoReleaseCancelSpinLock( Irp->CancelIrql ); NwReleaseRcb( &NwRcb ); } #endif break; default: // // For all other minor function codes we say they're invalid // and complete the request. // DebugTrace(0, Dbg, "Invalid FS Control Minor Function Code %08lx\n", irpSp->MinorFunction); status = STATUS_INVALID_DEVICE_REQUEST; break; } } finally { NwDequeueIrpContext( IrpContext, FALSE ); NwReleaseFcb( dcb->NonPagedFcb ); DebugTrace(-1, Dbg, "CommonDirectoryControl -> %08lx\n", status); } return status; } NTSTATUS NwQueryDirectory ( IN PIRP_CONTEXT pIrpContext, IN PICB Icb ) /*++ Routine Description: This is the work routine for querying a directory. Arugments: IrpContext - Supplies the Irp context information. Icb - Pointer the ICB for the request. Return Value: NTSTATUS - The return status for the operation. --*/ { NTSTATUS status = STATUS_SUCCESS; PIRP Irp; PIO_STACK_LOCATION irpSp; PUCHAR buffer; CLONG systemBufferLength; UNICODE_STRING searchMask; ULONG fileIndexLow; ULONG fileIndexHigh; FILE_INFORMATION_CLASS fileInformationClass; BOOLEAN restartScan; BOOLEAN returnSingleEntry; BOOLEAN indexSpecified; PVCB vcb; BOOLEAN ansiStringAllocated = FALSE; UCHAR SearchAttributes; BOOLEAN searchRetry; static WCHAR star[] = L"*"; BOOLEAN caseInsensitive = TRUE; //*** Make searches case insensitive ULONG lastEntry; ULONG nextEntry; ULONG totalBufferLength = 0; PFILE_BOTH_DIR_INFORMATION dirInfo; PFILE_NAMES_INFORMATION namesInfo; BOOLEAN canContinue = FALSE; BOOLEAN useCache = FALSE; BOOLEAN lastIndexFromServer = FALSE; PNW_DIRECTORY_INFO dirCache; PLIST_ENTRY entry; ULONG i; PAGED_CODE(); // // Get the current stack location. // Irp = pIrpContext->pOriginalIrp; irpSp = IoGetCurrentIrpStackLocation( Irp ); vcb = Icb->SuperType.Fcb->Vcb; DebugTrace(+1, Dbg, "NwQueryDirectory\n", 0 ); DebugTrace( 0, Dbg, "Icb = %08lx\n", (ULONG_PTR)Icb); DebugTrace( 0, Dbg, "SystemBuffer = %08lx\n", (ULONG_PTR)Irp->AssociatedIrp.SystemBuffer); DebugTrace( 0, Dbg, "Length = %08lx\n", irpSp->Parameters.QueryDirectory.Length); DebugTrace( 0, Dbg, "Search Mask = %08lx\n", (ULONG_PTR)irpSp->Parameters.QueryDirectory.FileName); DebugTrace( 0, Dbg, "FileIndex = %08lx\n", irpSp->Parameters.QueryDirectory.FileIndex); DebugTrace( 0, Dbg, "FileInformationClass = %08lx\n", irpSp->Parameters.QueryDirectory.FileInformationClass); DebugTrace( 0, Dbg, "RestartScan = %08lx\n", BooleanFlagOn(irpSp->Flags, SL_RESTART_SCAN)); DebugTrace( 0, Dbg, "ReturnSingleEntry = %08lx\n", BooleanFlagOn(irpSp->Flags, SL_RETURN_SINGLE_ENTRY)); DebugTrace( 0, Dbg, "IndexSpecified = %08lx\n", BooleanFlagOn(irpSp->Flags, SL_INDEX_SPECIFIED)); // // Make local copies of the input parameters. // systemBufferLength = irpSp->Parameters.QueryDirectory.Length; restartScan = BooleanFlagOn(irpSp->Flags, SL_RESTART_SCAN); indexSpecified = BooleanFlagOn(irpSp->Flags, SL_INDEX_SPECIFIED); returnSingleEntry = BooleanFlagOn(irpSp->Flags, SL_RETURN_SINGLE_ENTRY); fileIndexLow = 0; fileIndexHigh = 0; fileInformationClass = irpSp->Parameters.QueryDirectory.FileInformationClass; if (irpSp->Parameters.QueryDirectory.FileName != NULL) { searchMask = *(PUNICODE_STRING)irpSp->Parameters.QueryDirectory.FileName; } else { searchMask.Length = 0; searchMask.Buffer = NULL; } buffer = Irp->UserBuffer; DebugTrace(0, Dbg, "Users Buffer -> %08lx\n", buffer); // // It is ok to attempt a reconnect if this request fails with a // connection error. // SetFlag( pIrpContext->Flags, IRP_FLAG_RECONNECTABLE ); // // Check if the ICB already has a query template attached. If it // does not already have one then we either use the string we are // given or we attach our own containing "*" // if ( Icb->NwQueryTemplate.Buffer == NULL ) { // // This is our first time calling query directory so we need // to either set the query template to the user specified string // or to "*.*". // if ( searchMask.Buffer == NULL ) { DebugTrace(0, Dbg, "Set template to *", 0); searchMask.Length = sizeof( star ) - sizeof(WCHAR); searchMask.Buffer = star; } DebugTrace(0, Dbg, "Set query template -> %wZ\n", (ULONG_PTR)&searchMask); // // Map the NT search names to NCP. Note that this must be // done after the Unicode to OEM translation. // searchRetry = FALSE; do { status = NtSearchMaskToNw( &searchMask, &Icb->NwQueryTemplate, Icb, searchRetry ); if ( !NT_SUCCESS( status ) ) { DebugTrace(-1, Dbg, "NwQueryDirectory -> %08lx\n", status); return( status ); } Icb->UQueryTemplate.Buffer = ALLOCATE_POOL( PagedPool, searchMask.Length ); if (Icb->UQueryTemplate.Buffer == NULL ) { DebugTrace(-1, Dbg, "NwQueryDirectory -> %08lx\n", STATUS_INSUFFICIENT_RESOURCES ); return( STATUS_INSUFFICIENT_RESOURCES ); } Icb->UQueryTemplate.MaximumLength = searchMask.Length; RtlCopyUnicodeString( &Icb->UQueryTemplate, &searchMask ); // // Now send a Search Initialize NCP. // // Do a short search if the server doesn't support long names, // or this is a short-name non-wild card search // if ( !Icb->ShortNameSearch ) { status = ExchangeWithWait( pIrpContext, SynchronousResponseCallback, "Lbb-DbC", NCP_LFN_SEARCH_INITIATE, vcb->Specific.Disk.LongNameSpace, vcb->Specific.Disk.VolumeNumber, vcb->Specific.Disk.Handle, LFN_FLAG_SHORT_DIRECTORY, &Icb->SuperType.Fcb->RelativeFileName ); if ( NT_SUCCESS( status ) ) { status = ParseResponse( pIrpContext, pIrpContext->rsp, pIrpContext->ResponseLength, "Nbee", &Icb->SearchVolume, &Icb->SearchIndexHigh, &Icb->SearchIndexLow ); } } else { status = ExchangeWithWait( pIrpContext, SynchronousResponseCallback, "FbJ", NCP_SEARCH_INITIATE, vcb->Specific.Disk.Handle, &Icb->SuperType.Fcb->RelativeFileName ); if ( NT_SUCCESS( status ) ) { status = ParseResponse( pIrpContext, pIrpContext->rsp, pIrpContext->ResponseLength, "Nbww-", &Icb->SearchVolume, &Icb->SearchHandle, &Icb->SearchIndexLow ); } } // // If we couldn't find the search path, and we did a long // name search initiate, try again with a short name. // if ( status == STATUS_OBJECT_PATH_NOT_FOUND && !Icb->ShortNameSearch ) { searchRetry = TRUE; if ( Icb->UQueryTemplate.Buffer != NULL ) { FREE_POOL( Icb->UQueryTemplate.Buffer ); } RtlFreeOemString ( &Icb->NwQueryTemplate ); } else { searchRetry = FALSE; } } while ( searchRetry ); if ( !NT_SUCCESS( status ) ) { if (status == STATUS_UNSUCCESSFUL) { DebugTrace(-1, Dbg, "NwQueryDirectory -> %08lx\n", STATUS_NO_SUCH_FILE); return( STATUS_NO_SUCH_FILE ); } DebugTrace(-1, Dbg, "NwQueryDirectory -> %08lx\n", status); return( status ); } // // Since we are doing a search we will need to send an End Of Job // for this PID. // NwSetEndOfJobRequired(pIrpContext->pNpScb, Icb->Pid ); fileIndexLow = Icb->SearchIndexLow; fileIndexHigh = Icb->SearchIndexHigh; // // We can't ask for both files and directories, so first ask for // files, then ask for directories. // SearchAttributes = NW_ATTRIBUTE_SYSTEM | NW_ATTRIBUTE_HIDDEN | NW_ATTRIBUTE_READ_ONLY; // // If there are no wildcards in the search mask, then setup to // not generate the . and .. entries. // if ( !FsRtlDoesNameContainWildCards( &Icb->UQueryTemplate ) ) { Icb->DotReturned = TRUE; Icb->DotDotReturned = TRUE; } else { Icb->DotReturned = FALSE; Icb->DotDotReturned = FALSE; } } else { // // Check if we were given an index to start with or if we need to // restart the scan or if we should use the index that was saved in // the ICB. // if (restartScan) { useCache = FALSE; fileIndexLow = (ULONG)-1; fileIndexHigh = Icb->SearchIndexHigh; // // Send a Search Initialize NCP. The server often times out search // handles and if this one has been sitting at the end of the // directory then its likely we would get no files at all! // // Do a short search if the server doesn't support long names, // or this is a short-name non-wild card search // if ( !Icb->ShortNameSearch ) { status = ExchangeWithWait( pIrpContext, SynchronousResponseCallback, "Lbb-DbC", NCP_LFN_SEARCH_INITIATE, vcb->Specific.Disk.LongNameSpace, vcb->Specific.Disk.VolumeNumber, vcb->Specific.Disk.Handle, LFN_FLAG_SHORT_DIRECTORY, &Icb->SuperType.Fcb->RelativeFileName ); if ( NT_SUCCESS( status ) ) { status = ParseResponse( pIrpContext, pIrpContext->rsp, pIrpContext->ResponseLength, "Nbee", &Icb->SearchVolume, &Icb->SearchIndexHigh, &Icb->SearchIndexLow ); } } else { status = ExchangeWithWait( pIrpContext, SynchronousResponseCallback, "FbJ", NCP_SEARCH_INITIATE, vcb->Specific.Disk.Handle, &Icb->SuperType.Fcb->RelativeFileName ); if ( NT_SUCCESS( status ) ) { status = ParseResponse( pIrpContext, pIrpContext->rsp, pIrpContext->ResponseLength, "Nbww-", &Icb->SearchVolume, &Icb->SearchHandle, &Icb->SearchIndexLow ); } } Icb->ReturnedSomething = FALSE; // // We can't ask for both files and directories, so first ask for // files, then ask for directories. // SearchAttributes = NW_ATTRIBUTE_SYSTEM | NW_ATTRIBUTE_HIDDEN | NW_ATTRIBUTE_READ_ONLY; Icb->SearchAttributes = SearchAttributes; Icb->DotReturned = FALSE; Icb->DotDotReturned = FALSE; } else if ((!indexSpecified) || (canContinue) ) { // // Continue from the one of the last filenames. // SearchAttributes = Icb->SearchAttributes; if( !indexSpecified ) { useCache = FALSE; fileIndexLow = Icb->SearchIndexLow; fileIndexHigh = Icb->SearchIndexHigh; } if ( SearchAttributes == 0xFF && fileIndexLow == Icb->SearchIndexLow ) { // // This is a completed search. // DebugTrace(-1, Dbg, "NwQueryDirectory -> %08lx\n", STATUS_NO_MORE_FILES); return( STATUS_NO_MORE_FILES ); } } else { // // Someone's trying to do a resume from key. The netware // server doesn't support this, so neither do we. // DebugTrace(-1, Dbg, "NwQueryDirectory -> %08lx\n", STATUS_NOT_IMPLEMENTED); return( STATUS_NOT_IMPLEMENTED ); } } // // Now we are committed to completing the Irp, we do that in // the finally clause of the following try. // try { ULONG baseLength; ULONG lengthAdded; PNW_DIRECTORY_INFO nwDirInfo; ULONG FileNameLength; ULONG entriesToCreate; lastEntry = 0; nextEntry = 0; switch (fileInformationClass) { case FileDirectoryInformation: baseLength = FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, FileName[0] ); break; case FileFullDirectoryInformation: baseLength = FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, FileName[0] ); break; case FileNamesInformation: baseLength = FIELD_OFFSET( FILE_NAMES_INFORMATION, FileName[0] ); break; case FileBothDirectoryInformation: baseLength = FIELD_OFFSET( FILE_BOTH_DIR_INFORMATION, FileName[0] ); break; default: try_return( status = STATUS_INVALID_INFO_CLASS ); } // // It is not ok to attempt a reconnect if this request fails with a // connection error, since our search handle would be invalid. // ClearFlag( pIrpContext->Flags, IRP_FLAG_RECONNECTABLE ); // // See if we have a dir cache. If not, create one. // if( !Icb->DirCacheBuffer ) { entriesToCreate = 1; Icb->DirCacheBuffer = ALLOCATE_POOL ( PagedPool, (sizeof(NW_DIRECTORY_INFO) * entriesToCreate) ); if( !Icb->DirCacheBuffer ) { try_return( status = STATUS_NO_MEMORY ); } RtlZeroMemory( Icb->DirCacheBuffer, sizeof(NW_DIRECTORY_INFO) * entriesToCreate ); dirCache = (PNW_DIRECTORY_INFO)Icb->DirCacheBuffer; for( i = 0; i < entriesToCreate; i++ ) { InsertTailList( &(Icb->DirCache), &(dirCache->ListEntry) ); dirCache++; } } while ( TRUE ) { ULONG bytesToCopy; ULONG bytesRemainingInBuffer; DebugTrace(0, Dbg, "Top of Loop\n", 0); DebugTrace(0, Dbg, "CurrentIndex = %08lx\n", fileIndexLow); DebugTrace(0, Dbg, "LastEntry = %08lx\n", lastEntry); DebugTrace(0, Dbg, "NextEntry = %08lx\n", nextEntry); if( useCache ) { // // We need to use the data out of the entry we found in the cache. // dirCache points to the entry that matches, and the request was not // for the last file we read, so the entry after dirCache is the one // we want. // DebugTrace(0, Dbg, "Using cache\n", 0); entry = dirCache->ListEntry.Flink; dirCache = CONTAINING_RECORD( entry, NW_DIRECTORY_INFO, ListEntry ); nwDirInfo = dirCache; fileIndexLow = nwDirInfo->FileIndexLow; fileIndexHigh = nwDirInfo->FileIndexHigh; status = nwDirInfo->Status; // // Check to see if we should still keep using the cache or not. // if( entry->Flink == &(Icb->DirCache) ) { // // This is the last entry. We need to stop using the cache. // useCache = FALSE; Icb->CacheHint = NULL; } else { Icb->CacheHint = entry; } } else { // // Pull an entry from the dir cache. // entry = RemoveHeadList( &(Icb->DirCache) ); nwDirInfo = CONTAINING_RECORD( entry, NW_DIRECTORY_INFO, ListEntry ); nwDirInfo->FileName.Buffer = nwDirInfo->FileNameBuffer; nwDirInfo->FileName.MaximumLength = NW_MAX_FILENAME_SIZE; status = GetNextFile( pIrpContext, Icb, &fileIndexLow, &fileIndexHigh, SearchAttributes, nwDirInfo ); // // Store the return and the file index number, // and then put this entry in the cache. // nwDirInfo->FileIndexLow = fileIndexLow; nwDirInfo->FileIndexHigh = fileIndexHigh; nwDirInfo->Status = status; InsertTailList( &(Icb->DirCache), &(nwDirInfo->ListEntry) ); lastIndexFromServer = TRUE; // SVR to avoid rescanning from end of dir all if (fileIndexLow != -1) { Icb->LastSearchIndexLow = fileIndexLow; } // SVR end } if ( NT_SUCCESS( status ) ) { DebugTrace(0, Dbg, "DirFileName = %wZ\n", &nwDirInfo->FileName); DebugTrace(0, Dbg, "FileIndexLow = %08lx\n", fileIndexLow); FileNameLength = nwDirInfo->FileName.Length; bytesRemainingInBuffer = systemBufferLength - nextEntry; ASSERT( bytesRemainingInBuffer >= baseLength ); if (IsTerminalServer() && (LONG)NW_MAX_FILENAME_SIZE < FileNameLength ) try_return( status = STATUS_BUFFER_OVERFLOW ); // // See how much of the name we will be able to copy into // the system buffer. This also dictates our return // value. // if ( baseLength + FileNameLength <= bytesRemainingInBuffer ) { bytesToCopy = FileNameLength; status = STATUS_SUCCESS; } else { if (IsTerminalServer()) { try_return( status = STATUS_BUFFER_OVERFLOW ); } bytesToCopy = bytesRemainingInBuffer - baseLength; status = STATUS_BUFFER_OVERFLOW; } // // Note how much of buffer we are consuming and zero // the base part of the structure. // lengthAdded = baseLength + bytesToCopy; RtlZeroMemory( &buffer[nextEntry], baseLength ); switch (fileInformationClass) { case FileBothDirectoryInformation: // // Fill in the short name, if this is a LFN volume. // DebugTrace(0, Dbg, "Getting directory both information\n", 0); if (!DisableAltFileName) { if ( nwDirInfo->DosDirectoryEntry != 0xFFFF && !IsFatNameValid( &nwDirInfo->FileName ) ) { UNICODE_STRING ShortName; status = ExchangeWithWait ( pIrpContext, SynchronousResponseCallback, "SbDb", NCP_DIR_FUNCTION, NCP_GET_SHORT_NAME, Icb->SearchVolume, nwDirInfo->DosDirectoryEntry, 0 ); if ( NT_SUCCESS( status ) ) { dirInfo = (PFILE_BOTH_DIR_INFORMATION)&buffer[nextEntry]; // // Short name is in form 8.3 plus nul terminator. // ShortName.MaximumLength = 13 * sizeof(WCHAR) ; ShortName.Buffer = dirInfo->ShortName; status = ParseResponse( pIrpContext, pIrpContext->rsp, pIrpContext->ResponseLength, "N_P", 15, &ShortName ); if ( NT_SUCCESS( status ) ) { dirInfo->ShortNameLength = (CCHAR)ShortName.Length; } } } } case FileFullDirectoryInformation: // // We don't use EaLength, so fill in nothing here. // DebugTrace(0, Dbg, "Getting directory full information\n", 0); case FileDirectoryInformation: DebugTrace(0, Dbg, "Getting directory information\n", 0); // // The eof indicates the number of instances and // allocation size is the maximum allowed // dirInfo = (PFILE_BOTH_DIR_INFORMATION)&buffer[nextEntry]; dirInfo->FileAttributes = nwDirInfo->Attributes; dirInfo->FileNameLength = bytesToCopy; dirInfo->EndOfFile.LowPart = nwDirInfo->FileSize; dirInfo->EndOfFile.HighPart = 0; dirInfo->AllocationSize = dirInfo->EndOfFile; dirInfo->CreationTime = NwDateTimeToNtTime( nwDirInfo->CreationDate, nwDirInfo->CreationTime ); dirInfo->LastAccessTime = NwDateTimeToNtTime( nwDirInfo->LastAccessDate, 0 ); dirInfo->LastWriteTime = NwDateTimeToNtTime( nwDirInfo->LastUpdateDate, nwDirInfo->LastUpdateTime ); dirInfo->ChangeTime = dirInfo->LastWriteTime; dirInfo->FileIndex = 0; break; case FileNamesInformation: DebugTrace(0, Dbg, "Getting names information\n", 0); namesInfo = (PFILE_NAMES_INFORMATION)&buffer[nextEntry]; namesInfo->FileNameLength = FileNameLength; namesInfo->FileIndex = 0; break; default: KeBugCheck( RDR_FILE_SYSTEM ); } // Mapping for Novell's handling of Euro char in file names { int index = 0; WCHAR * pCurrChar = nwDirInfo->FileName.Buffer; for (index = 0; index < (nwDirInfo->FileName.Length / 2); index++) { if (*(pCurrChar + index) == (WCHAR) 0x2560) // Its Novell's mapping of a Euro *(pCurrChar + index) = (WCHAR) 0x20AC; // set it to Euro } } RtlMoveMemory( &buffer[nextEntry + baseLength], nwDirInfo->FileName.Buffer, bytesToCopy ); dump( Dbg, &buffer[nextEntry], lengthAdded); // // Setup the previous next entry offset. // *((PULONG)(&buffer[lastEntry])) = nextEntry - lastEntry; totalBufferLength = nextEntry + lengthAdded; // // Set ourselves up for the next iteration // lastEntry = nextEntry; nextEntry += (ULONG)QuadAlign( lengthAdded ); // // Check if the last entry didn't completely fit // if ( status == STATUS_BUFFER_OVERFLOW ) { try_return( NOTHING ); } // // Check if we are only to return a single entry // if (returnSingleEntry) { try_return( status = STATUS_SUCCESS ); } } else { // // The search response contained an error. If we have // not yet enumerated directories, do them now. Otherwise, // we are done searching for files. // if ( status == STATUS_UNSUCCESSFUL && (!FlagOn(SearchAttributes, NW_ATTRIBUTE_DIRECTORY) || useCache) ) { SetFlag( SearchAttributes, NW_ATTRIBUTE_DIRECTORY ); fileIndexLow = (ULONG)-1; continue; } else { // // Remember that this is a completed search and // quit the loop. // SearchAttributes = 0xFF; break; } } // // Here are the rules concerning filling up the buffer: // // 1. The Io system garentees that there will always be // enough room for at least one base record. // // 2. If the full first record (including file name) cannot // fit, as much of the name as possible is copied and // STATUS_BUFFER_OVERFLOW is returned. // // 3. If a subsequent record cannot completely fit into the // buffer, none of it (as in 0 bytes) is copied, and // STATUS_SUCCESS is returned. A subsequent query will // pick up with this record. // // Since we cannot rewind a search, we'll guess that the // next entry is a full length name. If it mightn't fix, // just bail and re the files we've got. // bytesRemainingInBuffer = systemBufferLength - nextEntry; if ( baseLength + NW_MAX_FILENAME_SIZE > bytesRemainingInBuffer ) { DebugTrace(0, Dbg, "Next entry won't fit\n", 0); try_return( status = STATUS_SUCCESS ); } } // while ( TRUE ) try_exit: NOTHING; } finally { // // At this point we're finished searching for files. // If the NextEntry is zero then we haven't found anything so we // will return no more files or no such file. // if ( status == STATUS_NO_MORE_FILES || status == STATUS_UNSUCCESSFUL || status == STATUS_SUCCESS ) { if (nextEntry == 0) { if (Icb->ReturnedSomething) { status = STATUS_NO_MORE_FILES; } else { status = STATUS_NO_SUCH_FILE; } } else { Icb->ReturnedSomething = TRUE; status = STATUS_SUCCESS; } } // // Indicate how much of the system buffer we have used up. // Irp->IoStatus.Information = totalBufferLength; // // Remember the last file index, so that we can resume this // search. // // // Update the last search index read as long as it didn't come from cache. // if( lastIndexFromServer ) { Icb->SearchIndexLow = fileIndexLow; Icb->SearchIndexHigh = fileIndexHigh; } Icb->SearchAttributes = SearchAttributes; DebugTrace(-1, Dbg, "NwQueryDirectory -> %08lx\n", status); } return status; } NTSTATUS GetNextFile( PIRP_CONTEXT pIrpContext, PICB Icb, PULONG FileIndexLow, PULONG FileIndexHigh, UCHAR SearchAttributes, PNW_DIRECTORY_INFO DirInfo ) /*++ Routine Description: Get the next file in the directory being searched. Arguments: pIrpContext - Supplies the request being processed. Icb - A pointer to the ICB for the directory to query. FileIndexLow, FileIndexHigh - On entry, the the index of the previous directory entry. On exit, the index to the directory entry returned. SearchAttributes - Search attributes to use. DirInfo - Returns information for the directory entry found. Return Value: NTSTATUS - The result status. --*/ { NTSTATUS status; PVCB vcb; static UNICODE_STRING DotFile = { 2, 2, L"." }; static UNICODE_STRING DotDotFile = { 4, 4, L".." }; PAGED_CODE(); DirInfo->DosDirectoryEntry = 0xFFFF; if ( !Icb->DotReturned ) { Icb->DotReturned = TRUE; // // Return '.' only if it we are not searching in the root directory // and it matches the search pattern. // if ( Icb->SuperType.Fcb->RelativeFileName.Length != 0 && FsRtlIsNameInExpression( &Icb->UQueryTemplate, &DotFile, TRUE, NULL ) ) { RtlCopyUnicodeString( &DirInfo->FileName, &DotFile ); DirInfo->Attributes = FILE_ATTRIBUTE_DIRECTORY; DirInfo->FileSize = 0; DirInfo->CreationDate = DEFAULT_DATE; DirInfo->LastAccessDate = DEFAULT_DATE; DirInfo->LastUpdateDate = DEFAULT_DATE; DirInfo->LastUpdateTime = DEFAULT_TIME; DirInfo->CreationTime = DEFAULT_TIME; return( STATUS_SUCCESS ); } } if ( !Icb->DotDotReturned ) { Icb->DotDotReturned = TRUE; // // Return '..' only if it we are not searching in the root directory // and it matches the search pattern. // if ( Icb->SuperType.Fcb->RelativeFileName.Length != 0 && FsRtlIsNameInExpression( &Icb->UQueryTemplate, &DotDotFile, TRUE, NULL ) ) { RtlCopyUnicodeString( &DirInfo->FileName, &DotDotFile ); DirInfo->Attributes = FILE_ATTRIBUTE_DIRECTORY; DirInfo->FileSize = 0; DirInfo->CreationDate = DEFAULT_DATE; DirInfo->LastAccessDate = DEFAULT_DATE; DirInfo->LastUpdateDate = DEFAULT_DATE; DirInfo->LastUpdateTime = DEFAULT_TIME; DirInfo->CreationTime = DEFAULT_TIME; return( STATUS_SUCCESS ); } } vcb = Icb->SuperType.Fcb->Vcb; if ( Icb->ShortNameSearch ) { status = ExchangeWithWait( pIrpContext, SynchronousResponseCallback, "Fbwwbp", NCP_SEARCH_CONTINUE, Icb->SearchVolume, Icb->SearchHandle, *(PUSHORT)FileIndexLow, SearchAttributes, Icb->NwQueryTemplate.Buffer ); if ( !NT_SUCCESS( status )) { return status; } *FileIndexLow = 0; *FileIndexHigh = 0; if ( FlagOn(SearchAttributes, NW_ATTRIBUTE_DIRECTORY) ) { status = ParseResponse( pIrpContext, pIrpContext->rsp, pIrpContext->ResponseLength, "Nw=Rb-ww", FileIndexLow, &DirInfo->FileName, 14, &DirInfo->Attributes, &DirInfo->CreationDate, &DirInfo->CreationTime ); #if 0 if ( DirInfo->CreationDate == 0 && DirInfo->CreationTime == 0 ) { DirInfo->CreationDate = DEFAULT_DATE; DirInfo->CreationTime = DEFAULT_TIME; } #endif DirInfo->FileSize = 0; DirInfo->LastAccessDate = DirInfo->CreationDate; DirInfo->LastUpdateDate = DirInfo->CreationDate; DirInfo->LastUpdateTime = DirInfo->CreationTime; } else { status = ParseResponse( pIrpContext, pIrpContext->rsp, pIrpContext->ResponseLength, "Nw=Rb-dwwww", FileIndexLow, &DirInfo->FileName, 14, &DirInfo->Attributes, &DirInfo->FileSize, &DirInfo->CreationDate, &DirInfo->LastAccessDate, &DirInfo->LastUpdateDate, &DirInfo->LastUpdateTime ); DirInfo->CreationTime = DEFAULT_TIME; } } else { status = ExchangeWithWait ( pIrpContext, SynchronousResponseCallback, "LbbWDbDDp", NCP_LFN_SEARCH_CONTINUE, vcb->Specific.Disk.LongNameSpace, 0, // Data stream SearchAttributes & SEARCH_ALL_DIRECTORIES, LFN_FLAG_INFO_ATTRIBUTES | LFN_FLAG_INFO_FILE_SIZE | LFN_FLAG_INFO_MODIFY_TIME | LFN_FLAG_INFO_CREATION_TIME | LFN_FLAG_INFO_DIR_INFO | LFN_FLAG_INFO_NAME, vcb->Specific.Disk.VolumeNumber, *FileIndexHigh, *FileIndexLow, Icb->NwQueryTemplate.Buffer ); if ( NT_SUCCESS( status ) ) { status = ParseResponse( pIrpContext, pIrpContext->rsp, pIrpContext->ResponseLength, "N-ee_e_e_xx_xx_x_e_P", FileIndexHigh, FileIndexLow, 5, &DirInfo->Attributes, 2, &DirInfo->FileSize, 6, &DirInfo->CreationTime, &DirInfo->CreationDate, 4, &DirInfo->LastUpdateTime, &DirInfo->LastUpdateDate, 4, &DirInfo->LastAccessDate, 14, &DirInfo->DosDirectoryEntry, 20, &DirInfo->FileName ); } if ( FlagOn(SearchAttributes, NW_ATTRIBUTE_DIRECTORY) ) { DirInfo->FileSize = 0; } } if ( DirInfo->Attributes == 0 ) { DirInfo->Attributes = FILE_ATTRIBUTE_NORMAL; } return status; } NTSTATUS NtSearchMaskToNw( IN PUNICODE_STRING UcSearchMask, IN OUT POEM_STRING OemSearchMask, IN PICB Icb, IN BOOLEAN ShortNameSearch ) /*++ Routine Description: This routine maps a netware path name to the correct netware format. Arguments: UcSearchMask - The search mask in NT format. OemSearchMask - The search mask in Netware format. Icb - The ICB of the directory in which we are searching. ShortNameSearch - If TRUE, always do a short name search. Return Value: NTSTATUS - The result status. --*/ { USHORT i; NTSTATUS status; PAGED_CODE(); // // Use a short name search if the volume does not support long names. // or this is a short name ICB, and we are doing a short name, non // wild-card search. // if ( Icb->SuperType.Fcb->Vcb->Specific.Disk.LongNameSpace == LFN_NO_OS2_NAME_SPACE || ShortNameSearch || ( !BooleanFlagOn( Icb->SuperType.Fcb->Flags, FCB_FLAGS_LONG_NAME ) && !FsRtlDoesNameContainWildCards( UcSearchMask ) && IsFatNameValid( UcSearchMask ) ) ) { Icb->ShortNameSearch = TRUE; // Mapping for Novell's handling of Euro char in file names { int index = 0; WCHAR * pCurrChar = UcSearchMask->Buffer; for (index = 0; index < (UcSearchMask->Length / 2); index++) { if (*(pCurrChar + index) == (WCHAR) 0x20AC) // Its a Euro *(pCurrChar + index) = (WCHAR) 0x2560; // set it to Novell's mapping for Euro } } // // Allocate space for and initialize the query templates. // status = RtlUpcaseUnicodeStringToOemString( OemSearchMask, UcSearchMask, TRUE ); if ( !NT_SUCCESS( status ) ) { return( status ); } // // Special case. Map '*.*' to '*'. // if ( OemSearchMask->Length == 3 && RtlCompareMemory( OemSearchMask->Buffer, "*.*", 3 ) == 3 ) { OemSearchMask->Length = 1; OemSearchMask->Buffer[1] = '\0'; } else { for ( i = 0; i < OemSearchMask->Length ; i++ ) { // // In fact Novell server seems to convert all 0xBF, 0xAA, 0xAE // even if they are DBCS lead or trail byte. // We can't single out DBCS case in the conversion. // if( FsRtlIsLeadDbcsCharacter( OemSearchMask->Buffer[i] ) ) { if((UCHAR)(OemSearchMask->Buffer[i]) == 0xBF ) { OemSearchMask->Buffer[i] = (UCHAR)( 0x10 ); }else if((UCHAR)(OemSearchMask->Buffer[i]) == 0xAE ) { OemSearchMask->Buffer[i] = (UCHAR)( 0x11 ); }else if((UCHAR)(OemSearchMask->Buffer[i]) == 0xAA ) { OemSearchMask->Buffer[i] = (UCHAR)( 0x12 ); } i++; if((UCHAR)(OemSearchMask->Buffer[i]) == 0x5C ) { // // The trailbyte is 0x5C, replace it with 0x13 // OemSearchMask->Buffer[i] = (UCHAR)( 0x13 ); } // // Continue to check other conversions for trailbyte. // } // Single byte character that may need modification. switch ( (UCHAR)(OemSearchMask->Buffer[i]) ) { case ANSI_DOS_STAR: OemSearchMask->Buffer[i] = (UCHAR)( 0x80 | '*' ); break; case ANSI_DOS_QM: OemSearchMask->Buffer[i] = (UCHAR)( 0x80 | '?' ); break; case ANSI_DOS_DOT: OemSearchMask->Buffer[i] = (UCHAR)( 0x80 | '.' ); break; // // Netware Japanese version The following character is // replaced with another one if the string is for File // Name only when sendding from Client to Server. // // SO U+0xFF7F SJIS+0xBF -> 0x10 // SMALL_YO U+0xFF6E SJIS+0xAE -> 0x11 // SMALL_E U+0xFF64 SJIS+0xAA -> 0x12 // // The reason is unknown, Should ask Novell Japan. // // See Also exchange.c case 0xBF: // ANSI_DOS_KATAKANA_SO: if (Japan) { OemSearchMask->Buffer[i] = (UCHAR)( 0x10 ); } break; case 0xAE: // ANSI_DOS_KATAKANA_SMALL_YO: if (Japan) { OemSearchMask->Buffer[i] = (UCHAR)( 0x11 ); } break; case 0xAA: // ANSI_DOS_KATAKANA_SMALL_E: if (Japan) { OemSearchMask->Buffer[i] = (UCHAR)( 0x12 ); } break; } } } } else { USHORT size; PCHAR buffer; UNICODE_STRING src; OEM_STRING dest; Icb->ShortNameSearch = FALSE; // // Allocate space for and initialize the query templates. // We allocate an extra byte to account for the null terminator. // #ifndef QFE_BUILD buffer = ExAllocatePoolWithTag( PagedPool, (UcSearchMask->Length) + 1, 'scwn' ); #else buffer = ExAllocatePool( PagedPool, (UcSearchMask->Length) + 1 ); #endif if ( buffer == NULL ) { return( STATUS_INSUFFICIENT_RESOURCES ); } OemSearchMask->Buffer = buffer; // // Special case. Map '????????.???' to '*'. // if ( UcSearchMask->Length == 24 && RtlCompareMemory( UcSearchMask->Buffer, L">>>>>>>>\">>>", 24 ) == 24 ) { OemSearchMask->Length = 3; OemSearchMask->Buffer[0] = (UCHAR)0xFF; OemSearchMask->Buffer[1] = '*'; OemSearchMask->Buffer[2] = '\0'; return STATUS_SUCCESS; } // // Now convert the string, character by character // src.Buffer = UcSearchMask->Buffer; src.Length = 2; dest.Buffer = buffer; dest.MaximumLength = UcSearchMask->Length; size = UcSearchMask->Length / 2; for ( i = 0; i < size ; i++ ) { switch ( *src.Buffer ) { case L'*': case L'?': *dest.Buffer++ = LFN_META_CHARACTER; *dest.Buffer++ = (UCHAR)*src.Buffer++; break; case L'.': *dest.Buffer++ = (UCHAR)*src.Buffer++; break; case DOS_DOT: *dest.Buffer++ = LFN_META_CHARACTER; *dest.Buffer++ = (UCHAR)( 0x80 | '.' ); src.Buffer++; break; case DOS_STAR: *dest.Buffer++ = LFN_META_CHARACTER; *dest.Buffer++ = (UCHAR)( 0x80 | '*' ); src.Buffer++; break; case DOS_QM: *dest.Buffer++ = LFN_META_CHARACTER; *dest.Buffer++ = (UCHAR)( 0x80 | '?' ); src.Buffer++; break; case 0x20AC: // Euro *src.Buffer = (WCHAR)0x2560; // change it to Novell's mapping // intentional fall-through to get it mapped to OEM default: RtlUnicodeStringToCountedOemString( &dest, &src, FALSE ); if( FsRtlIsLeadDbcsCharacter( dest.Buffer[0] ) ) { dest.Buffer++; } dest.Buffer++; src.Buffer++; } } *dest.Buffer = '\0'; OemSearchMask->Length = (USHORT)( dest.Buffer - buffer ); } return STATUS_SUCCESS; } #if 0 VOID NwCancelFindNotify ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine implements the cancel function for an find notify IRP. Arguments: DeviceObject - ignored Irp - Supplies the Irp being cancelled. Return Value: None. --*/ { PLIST_ENTRY listEntry; UNREFERENCED_PARAMETER( DeviceObject ); // // We now need to void the cancel routine and release the io cancel // spin-lock. // IoSetCancelRoutine( Irp, NULL ); IoReleaseCancelSpinLock( Irp->CancelIrql ); NwAcquireExclusiveRcb( &NwRcb, TRUE ); for ( listEntry = FnList.Flink; listEntry != &FnList ; listEntry = listEntry->Flink ) { PIRP_CONTEXT IrpContext; IrpContext = CONTAINING_RECORD( listEntry, IRP_CONTEXT, NextRequest ); if ( IrpContext->pOriginalIrp == Irp ) { RemoveEntryList( &IrpContext->NextRequest ); NwCompleteRequest( IrpContext, STATUS_CANCELLED ); break; } } NwReleaseRcb( &NwRcb ); } #endif