/*++ Copyright (c) 1989 Microsoft Corporation Module Name: smbfile.c Abstract: This module implements file-control SMB processors: Flush Delete Rename Move Copy Author: David Treadwell (davidtr) 15-Dec-1989 Revision History: --*/ #include "precomp.h" #include "smbfile.tmh" #pragma hdrstop #define BugCheckFileId SRV_FILE_SMBFILE // // Forward declarations // VOID SRVFASTCALL BlockingDelete ( IN OUT PWORK_CONTEXT WorkContext ); VOID SRVFASTCALL BlockingMove ( IN OUT PWORK_CONTEXT WorkContext ); VOID SRVFASTCALL BlockingRename ( IN OUT PWORK_CONTEXT WorkContext ); NTSTATUS DoDelete ( IN PUNICODE_STRING FullFileName, IN PUNICODE_STRING RelativeFileName, IN PWORK_CONTEXT WorkContext, IN USHORT SmbSearchAttributes, IN PSHARE Share ); NTSTATUS FindAndFlushFile ( IN PWORK_CONTEXT WorkContext ); VOID SRVFASTCALL RestartFlush ( IN OUT PWORK_CONTEXT WorkContext ); NTSTATUS StartFlush ( IN PWORK_CONTEXT WorkContext, IN PRFCB Rfcb ); #ifdef ALLOC_PRAGMA #pragma alloc_text( PAGE, SrvSmbFlush ) #pragma alloc_text( PAGE, RestartFlush ) #pragma alloc_text( PAGE, StartFlush ) #pragma alloc_text( PAGE, SrvSmbDelete ) #pragma alloc_text( PAGE, BlockingDelete ) #pragma alloc_text( PAGE, DoDelete ) #pragma alloc_text( PAGE, SrvSmbRename ) #pragma alloc_text( PAGE, BlockingRename ) #pragma alloc_text( PAGE, SrvSmbMove ) #pragma alloc_text( PAGE, BlockingMove ) #pragma alloc_text( PAGE, SrvSmbNtRename ) #endif #if 0 #pragma alloc_text( PAGECONN, FindAndFlushFile ) #endif SMB_PROCESSOR_RETURN_TYPE SrvSmbFlush ( SMB_PROCESSOR_PARAMETERS ) /*++ Routine Description: This routine processes the Flush SMB. It ensures that all data and allocation information for the specified file has been written out before the response is sent. Arguments: SMB_PROCESSOR_PARAMETERS - See smbtypes.h for a description of the parameters to SMB processor routines. Return Value: SMB_PROCESSOR_RETURN_TYPE - See smbtypes.h --*/ { PREQ_FLUSH request; PRESP_FLUSH response; NTSTATUS status = STATUS_SUCCESS; SMB_STATUS SmbStatus = SmbStatusInProgress; PRFCB rfcb; PAGED_CODE( ); if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT) WorkContext->PreviousSMB = EVENT_TYPE_SMB_FLUSH; SrvWmiStartContext(WorkContext); request = (PREQ_FLUSH)WorkContext->RequestParameters; response = (PRESP_FLUSH)WorkContext->ResponseParameters; IF_SMB_DEBUG(FILE_CONTROL1) { KdPrint(( "Flush request; FID 0x%lx\n", SmbGetUshort( &request->Fid ) )); } // // If a FID was specified, flush just that file. If FID == -1, // then flush all files corresponding to the PID passed in the // SMB header. // if ( SmbGetUshort( &request->Fid ) == (USHORT)0xFFFF ) { // // Find a single file to flush and flush it. We'll start one // flush here, then RestartFlush will handle flushing the rest // of the files. // WorkContext->Parameters.CurrentTableIndex = 0; status = FindAndFlushFile( WorkContext ); if ( status == STATUS_NO_MORE_FILES ) { // // There were no files that needed to be flushed. Build and // send a response SMB. // response->WordCount = 0; SmbPutUshort( &response->ByteCount, 0 ); WorkContext->ResponseParameters = NEXT_LOCATION( response, RESP_FLUSH, 0 ); SmbStatus = SmbStatusSendResponse; goto Cleanup; } SmbStatus = SmbStatusInProgress; goto Cleanup; } // // Flush of a specific file. Verify the FID. If verified, the // RFCB block is referenced and its address is stored in the // WorkContext block, and the RFCB address is returned. // rfcb = SrvVerifyFid( WorkContext, SmbGetUshort( &request->Fid ), TRUE, SrvRestartSmbReceived, // serialize with raw write &status ); if ( rfcb == SRV_INVALID_RFCB_POINTER ) { if ( !NT_SUCCESS( status ) ) { // // Invalid file ID or write behind error. Reject the request. // IF_DEBUG(ERRORS) { KdPrint(( "SrvSmbFlush: Status %X on FID: 0x%lx\n", status, SmbGetUshort( &request->Fid ) )); } SrvSetSmbError( WorkContext, status ); SmbStatus = SmbStatusSendResponse; goto Cleanup; } // // The work item has been queued because a raw write is in // progress. // SmbStatus = SmbStatusInProgress; goto Cleanup; } // // Set the CurrentTableIndex field of the work context block to // NULL so that the restart routine will know that only a single // file is to be flushed. // WorkContext->Parameters.CurrentTableIndex = -1; IF_SMB_DEBUG(FILE_CONTROL2) { KdPrint(( "Flushing buffers for FID %lx, RFCB %p\n", rfcb->Fid, rfcb )); } // // Start the flush operation on the file corresponding to the RFCB. // status = StartFlush( WorkContext, rfcb ); if ( !NT_SUCCESS(status) ) { // // Unable to start the I/O. Clean up the I/O request. Return // an error to the client. // SrvSetSmbError( WorkContext, status ); SmbStatus = SmbStatusSendResponse; goto Cleanup; } // // The flush request was successfully started. Return the InProgress // status to the caller, indicating that the caller should do // nothing further with the SMB/WorkContext at the present time. // SmbStatus = SmbStatusInProgress; IF_DEBUG(TRACE2) KdPrint(( "SrvSmbFlush complete\n" )); Cleanup: SrvWmiEndContext(WorkContext); return SmbStatus; } // SrvSmbFlush NTSTATUS FindAndFlushFile ( IN PWORK_CONTEXT WorkContext ) { NTSTATUS status; LONG currentTableIndex; PRFCB rfcb; USHORT pid = SmbGetAlignedUshort( &WorkContext->RequestHeader->Pid ); PCONNECTION connection = WorkContext->Connection; PTABLE_HEADER tableHeader; KIRQL oldIrql; //UNLOCKABLE_CODE( CONN ); IF_SMB_DEBUG(FILE_CONTROL1) { KdPrint(( "Flush FID == -1; flush all files for PID %lx\n", pid )); } // // Walk the connection's file table, looking an RFCB with a PID // equal to the PID passed in the SMB header. // // Acquire the lock that protects the connection's file table. // This prevents an RFCB from going away between when we find a // pointer to it and when we reference it. // tableHeader = &connection->FileTable; ACQUIRE_SPIN_LOCK( &connection->SpinLock, &oldIrql ); for ( currentTableIndex = WorkContext->Parameters.CurrentTableIndex; currentTableIndex < (LONG)tableHeader->TableSize; currentTableIndex++ ) { rfcb = tableHeader->Table[currentTableIndex].Owner; IF_SMB_DEBUG(FILE_CONTROL1) { KdPrint(( "Looking at RFCB %p, PID %lx, FID %lx\n", rfcb, rfcb != NULL ? rfcb->Pid : 0, rfcb != NULL ? rfcb->Fid : 0 )); } if ( rfcb == NULL || rfcb->Pid != pid ) { continue; } // // Reference the rfcb if it is active. // if ( GET_BLOCK_STATE(rfcb) != BlockStateActive ) { continue; } rfcb->BlockHeader.ReferenceCount++; // // Now that the RFCB has been referenced, we can safely // release the lock that protects the connection's file // table. // RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql ); WorkContext->Rfcb = rfcb; // // Mark the rfcb as active // rfcb->IsActive = TRUE; // // Set the CurrentTableIndex field of the work context // block so that the restart routine knows where to // continue looking for RFCBs to flush. // WorkContext->Parameters.CurrentTableIndex = currentTableIndex; IF_SMB_DEBUG(FILE_CONTROL2) { KdPrint(( "Flushing buffers for FID %lx, RFCB %p\n", rfcb->Fid, rfcb )); } // // Start the I/O to flush the file. // status = StartFlush( WorkContext, rfcb ); // // If there was an access violation or some other error, // simply continue walking through the file table. // We ignore these errors for flush with FID=-1. // // Note that StartFlush only returns an error if the IO // operation *was*not* started. If the operation was // started, then errors will be processed in this routine // when it is called later by IoCompleteRequest. // if ( status != STATUS_PENDING ) { SrvDereferenceRfcb( rfcb ); WorkContext->Rfcb = NULL; ACQUIRE_SPIN_LOCK( &connection->SpinLock, &oldIrql ); continue; } // // The flush request has been started. // IF_DEBUG(TRACE2) KdPrint(( "RestartFlush complete\n" )); return STATUS_SUCCESS; } // for ( ; ; ) (walk file table) RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql ); return STATUS_NO_MORE_FILES; } // FindAndFlushFile VOID SRVFASTCALL RestartFlush ( IN OUT PWORK_CONTEXT WorkContext ) /*++ Routine Description: Processes flush completion. Arguments: WorkContext - Supplies a pointer to the work context block describing server-specific context for the request. Return Value: None. --*/ { NTSTATUS status = STATUS_SUCCESS; PRESP_FLUSH response; PAGED_CODE( ); if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT) WorkContext->PreviousSMB = EVENT_TYPE_SMB_FLUSH; SrvWmiStartContext(WorkContext); IF_DEBUG(WORKER1) KdPrint(( " - RestartFlush\n" )); response = (PRESP_FLUSH)WorkContext->ResponseParameters; // // If the flush request failed, set an error status in the response // header. // status = WorkContext->Irp->IoStatus.Status; // // If an error occurred during processing of the flush, return the // error to the client. No more further files will be flushed. // // *** This should be very rare. STATUS_DISK_FULL is probably the // main culprit. if ( !NT_SUCCESS(status) ) { IF_DEBUG(ERRORS) KdPrint(( "Flush failed: %X\n", status )); SrvSetSmbError( WorkContext, status ); SrvEndSmbProcessing( WorkContext, SmbStatusSendResponse ); IF_DEBUG(TRACE2) KdPrint(( "RestartFlush complete\n" )); return; } IF_SMB_DEBUG(FILE_CONTROL1) { KdPrint(( "Flush operation for RFCB %p was successful.\n", WorkContext->Rfcb )); } // // If the FID in the original request was -1, look for more files // to flush. // if ( WorkContext->Parameters.CurrentTableIndex != -1 ) { // // Dereference the RFCB that was stored in the work context block, // and set the pointer to NULL so that it isn't accidentally // dereferenced again later. // SrvDereferenceRfcb( WorkContext->Rfcb ); WorkContext->Rfcb = NULL; // // Find a file to flush and flush it. // WorkContext->Parameters.CurrentTableIndex++; status = FindAndFlushFile( WorkContext ); // // If a file was found and IO operation started, then return. If // all the appropriate files have been flushed, send a response SMB. // if ( status != STATUS_NO_MORE_FILES ) { return; } } // if ( WorkContext->Parameters.CurrentTableIndex != -1 ) // // All files have been flushed. Build the response SMB. // response->WordCount = 0; SmbPutUshort( &response->ByteCount, 0 ); WorkContext->ResponseParameters = NEXT_LOCATION( response, RESP_FLUSH, 0 ); // // Processing of the SMB is complete. Call SrvEndSmbProcessing to // send the response. // SrvEndSmbProcessing( WorkContext, SmbStatusSendResponse ); IF_DEBUG(TRACE2) KdPrint(( "SrvSmbFlush complete.\n" )); SrvWmiEndContext(WorkContext); return; } // RestartFlush NTSTATUS StartFlush ( IN PWORK_CONTEXT WorkContext, IN PRFCB Rfcb ) /*++ Routine Description: Processes the actual file flush. Arguments: WorkContext - Supplies a pointer to the work context block describing server-specific context for the request. Rfcb - a pointer to the RFCB corresponding to the file to flush. Return Value: STATUS_PENDING if the IO operation was started, or an error from CHECK_FUNCTION_ACCESS (STATUS_ACCESS_DENIED, for example). --*/ { NTSTATUS status; PAGED_CODE( ); // // Verify that the client has write access to the file via the // specified handle. // CHECK_FUNCTION_ACCESS( Rfcb->GrantedAccess, IRP_MJ_FLUSH_BUFFERS, 0, 0, &status ); if ( !NT_SUCCESS(status) ) { IF_DEBUG(ERRORS) { KdPrint(( "StartFlush: IoCheckFunctionAccess failed: " "0x%X, GrantedAccess: %lx. Access granted anyway.\n", status, Rfcb->GrantedAccess )); } // // Some dumb apps flush files opened for r/o. If this happens, // assume the flush worked. OS/2 let's the // flush through and we should do the same. // WorkContext->Irp->IoStatus.Status = STATUS_SUCCESS; RestartFlush( WorkContext ); return(STATUS_PENDING); } // // Flush the file's buffers. // SrvBuildFlushRequest( WorkContext->Irp, // input IRP address Rfcb->Lfcb->FileObject, // target file object address WorkContext // context ); // // Pass the request to the file system. // WorkContext->FsdRestartRoutine = SrvQueueWorkToFspAtDpcLevel; WorkContext->FspRestartRoutine = RestartFlush; (VOID)IoCallDriver( Rfcb->Lfcb->DeviceObject, WorkContext->Irp ); return STATUS_PENDING; } // StartFlush SMB_PROCESSOR_RETURN_TYPE SrvSmbDelete ( SMB_PROCESSOR_PARAMETERS ) /*++ Routine Description: Processes the Delete SMB. Arguments: SMB_PROCESSOR_PARAMETERS - See smbprocs.h for a description of the parameters to SMB processor routines. Return Value: SMB_PROCESSOR_RETURN_TYPE - See smbprocs.h --*/ { PAGED_CODE(); // // This SMB must be processed in a blocking thread. // if( !WorkContext->UsingBlockingThread ) { WorkContext->FspRestartRoutine = BlockingDelete; SrvQueueWorkToBlockingThread( WorkContext ); } else { BlockingDelete( WorkContext ); } return SmbStatusInProgress; } // SrvSmbDelete VOID SRVFASTCALL BlockingDelete ( IN OUT PWORK_CONTEXT WorkContext ) /*++ Routine Description: This routine processes the Delete SMB. Arguments: SMB_PROCESSOR_PARAMETERS - See smbtypes.h for a description of the parameters to SMB processor routines. Return Value: SMB_PROCESSOR_RETURN_TYPE - See smbtypes.h --*/ { PREQ_DELETE request; PRESP_DELETE response; NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING filePathName; UNICODE_STRING fullPathName; PTREE_CONNECT treeConnect; PSESSION session; PSHARE share; BOOLEAN isUnicode; ULONG deleteRetries; PSRV_DIRECTORY_INFORMATION directoryInformation; PAGED_CODE( ); if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT) WorkContext->PreviousSMB = EVENT_TYPE_SMB_DELETE; SrvWmiStartContext(WorkContext); IF_SMB_DEBUG(FILE_CONTROL1) { KdPrint(( "Delete file request header at 0x%p, response header at 0x%p\n", WorkContext->RequestHeader, WorkContext->ResponseHeader )); KdPrint(( "Delete file request parameters at 0x%p, response parameters at 0x%p\n", WorkContext->RequestParameters, WorkContext->ResponseParameters )); } request = (PREQ_DELETE)WorkContext->RequestParameters; response = (PRESP_DELETE)WorkContext->ResponseParameters; // // If a session block has not already been assigned to the current // work context , verify the UID. If verified, the address of the // session block corresponding to this user is stored in the // WorkContext block and the session block is referenced. // // Find tree connect corresponding to given TID if a tree connect // pointer has not already been put in the WorkContext block by an // AndX command. // status = SrvVerifyUidAndTid( WorkContext, &session, &treeConnect, ShareTypeDisk ); if ( !NT_SUCCESS(status) ) { IF_DEBUG(SMB_ERRORS) { KdPrint(( "SrvSmbDelete: Invalid UID or TID\n" )); } goto error_exit; } // // If the session has expired, return that info // if( session->IsSessionExpired ) { status = SESSION_EXPIRED_STATUS_CODE; goto error_exit; } // // Get the share block from the tree connect block. This doesn't need // to be a referenced pointer becsue the tree connect has it referenced, // and we just referenced the tree connect. // share = treeConnect->Share; // // Initialize the string containing the path name. The +1 is to account // for the ASCII token in the Buffer field of the request SMB. // isUnicode = SMB_IS_UNICODE( WorkContext ); status = SrvCanonicalizePathName( WorkContext, share, NULL, (PVOID)(request->Buffer + 1), END_OF_REQUEST_SMB( WorkContext ), TRUE, isUnicode, &filePathName ); if( !NT_SUCCESS( status ) ) { IF_DEBUG(SMB_ERRORS) { KdPrint(( "SrvSmbDelete: illegal path name: %s\n", (PSZ)request->Buffer + 1 )); } goto error_exit; } // // Find out whether there are wildcards in the file name. If so, // then call SrvQueryDirectoryFile to expand the wildcards; if not, // just delete the file directly. // if ( !FsRtlDoesNameContainWildCards( &filePathName ) ) { // // Build a full pathname to the file. // SrvAllocateAndBuildPathName( &treeConnect->Share->DosPathName, &filePathName, NULL, &fullPathName ); if ( fullPathName.Buffer == NULL ) { IF_DEBUG(ERRORS) { KdPrint(( "SrvSmbDelete: SrvAllocateAndBuildPathName failed\n" )); } if ( !isUnicode ) { RtlFreeUnicodeString( &filePathName ); } status = STATUS_INSUFF_SERVER_RESOURCES; goto error_exit; } IF_SMB_DEBUG(FILE_CONTROL2) { KdPrint(( "Full path name to file is %wZ\n", &fullPathName )); } // // Perform the actual delete operation on this filename. // deleteRetries = SrvSharingViolationRetryCount; start_retry1: status = DoDelete( &fullPathName, &filePathName, WorkContext, SmbGetUshort( &request->SearchAttributes ), treeConnect->Share ); if ( (status == STATUS_SHARING_VIOLATION) && (deleteRetries-- > 0) ) { (VOID) KeDelayExecutionThread( KernelMode, FALSE, &SrvSharingViolationDelay ); goto start_retry1; } FREE_HEAP( fullPathName.Buffer ); if ( !isUnicode ) { RtlFreeUnicodeString( &filePathName ); } if ( !NT_SUCCESS(status) ) { goto error_exit; } } else { BOOLEAN firstCall = TRUE; CLONG bufferLength; UNICODE_STRING subdirInfo; BOOLEAN filterLongNames; // // A buffer of non-paged pool is required for // SrvQueryDirectoryFile. Since this routine does not use any // of the SMB buffer after the pathname of the file to delete, // we can use this. The buffer should be quadword-aligned. // directoryInformation = (PSRV_DIRECTORY_INFORMATION)( (ULONG_PTR)((PCHAR)request->Buffer + SmbGetUshort( &request->ByteCount ) + 7) & ~7 ); bufferLength = WorkContext->RequestBuffer->BufferLength - PTR_DIFF(directoryInformation, WorkContext->RequestBuffer->Buffer); // // We need the full path name of each file that is returned by // SrvQueryDirectoryFile, so we need to find the part of the // passed filename that contains subdirectory information (e.g. // for a\b\c\*.*, we want a string that indicates a\b\c). // subdirInfo.Buffer = filePathName.Buffer; subdirInfo.Length = SrvGetSubdirectoryLength( &filePathName ); subdirInfo.MaximumLength = subdirInfo.Length; IF_SMB_DEBUG(FILE_CONTROL2) { KdPrint(( "Subdirectory info is %wZ\n", &subdirInfo )); } // // Determine whether long filenames (non-8.3) should be filtered out // or processed. // if ( (SmbGetAlignedUshort( &WorkContext->RequestHeader->Flags2 ) & SMB_FLAGS2_KNOWS_LONG_NAMES) != 0 ) { filterLongNames = FALSE; } else { filterLongNames = TRUE; } // // When we call SrvQueryDirectoryFile, it will open the file for // us, so all we have to do is delete it with // NtSetInformationFile. // // *** We ask for FileBothDirectoryInformation so that we will // pick up long names on NTFS that have short name // equivalents. Without this, DOS clients will not be able // to delete long names on NTFS volumes. // while ( ( status = SrvQueryDirectoryFile( WorkContext, firstCall, filterLongNames, FALSE, FileBothDirectoryInformation, 0, &filePathName, NULL, SmbGetUshort( &request->SearchAttributes ), directoryInformation, bufferLength ) ) != STATUS_NO_MORE_FILES ) { PFILE_BOTH_DIR_INFORMATION bothDirInfo; UNICODE_STRING name; UNICODE_STRING relativeName; if ( !NT_SUCCESS(status) ) { IF_DEBUG(ERRORS) { KdPrint(( "SrvSmbDelete: SrvQueryDirectoryFile failed: " "%X\n", status )); } if ( !isUnicode ) { RtlFreeUnicodeString( &filePathName ); } goto error_exit1; } bothDirInfo = (PFILE_BOTH_DIR_INFORMATION)directoryInformation->CurrentEntry; // // Note that we use the standard name to do the delete, even // though we may have matched on the NTFS short name. The // client doesn't care which name we use to do the delete. // name.Length = (SHORT)bothDirInfo->FileNameLength; name.MaximumLength = name.Length; name.Buffer = bothDirInfo->FileName; IF_SMB_DEBUG(FILE_CONTROL2) { KdPrint(( "SrvQueryDirectoryFile--name %wZ, length = %ld, " "status = %X\n", &name, directoryInformation->CurrentEntry->FileNameLength, status )); } firstCall = FALSE; // // Build a full pathname to the file. // SrvAllocateAndBuildPathName( &treeConnect->Share->DosPathName, &subdirInfo, &name, &fullPathName ); if ( fullPathName.Buffer == NULL ) { IF_DEBUG(ERRORS) { KdPrint(( "SrvSmbDelete: SrvAllocateAndBuildPathName " "failed\n" )); } if ( !isUnicode ) { RtlFreeUnicodeString( &filePathName ); } status = STATUS_INSUFFICIENT_RESOURCES; goto error_exit1; } IF_SMB_DEBUG(FILE_CONTROL2) { KdPrint(( "Full path name to file is %wZ\n", &fullPathName )); } // // Build the relative path name to the file. // SrvAllocateAndBuildPathName( &subdirInfo, &name, NULL, &relativeName ); if ( relativeName.Buffer == NULL ) { IF_DEBUG(ERRORS) { KdPrint(( "SrvSmbDelete: SrvAllocateAndBuildPathName failed\n" )); } FREE_HEAP( fullPathName.Buffer ); if ( !isUnicode ) { RtlFreeUnicodeString( &filePathName ); } status = STATUS_INSUFF_SERVER_RESOURCES; goto error_exit1; } IF_SMB_DEBUG(FILE_CONTROL2) { KdPrint(( "Full path name to file is %wZ\n", &fullPathName )); } // // Perform the actual delete operation on this filename. // // *** SrvQueryDirectoryFile has already filtered based on // the search attributes, so tell DoDelete that files // with the system and hidden bits are OK. This will // prevent the call to NtQueryDirectoryFile performed // in SrvCheckSearchAttributesForHandle. deleteRetries = SrvSharingViolationRetryCount; start_retry2: status = DoDelete( &fullPathName, &relativeName, WorkContext, (USHORT)(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN), treeConnect->Share ); if ( (status == STATUS_SHARING_VIOLATION) && (deleteRetries-- > 0) ) { (VOID) KeDelayExecutionThread( KernelMode, FALSE, &SrvSharingViolationDelay ); goto start_retry2; } FREE_HEAP( relativeName.Buffer ); FREE_HEAP( fullPathName.Buffer ); if ( !NT_SUCCESS(status) ) { if ( !isUnicode ) { RtlFreeUnicodeString( &filePathName ); } goto error_exit1; } } // // Close the directory search. // if ( !isUnicode ) { RtlFreeUnicodeString( &filePathName ); } SrvCloseQueryDirectory( directoryInformation ); // // If no files were found, return an error to the client. // if ( firstCall ) { status = STATUS_NO_SUCH_FILE; goto error_exit; } } // // Build the response SMB. // response->WordCount = 0; SmbPutUshort( &response->ByteCount, 0 ); WorkContext->ResponseParameters = NEXT_LOCATION( response, RESP_DELETE, 0 ); IF_DEBUG(TRACE2) KdPrint(( "SrvSmbDelete complete.\n" )); goto normal_exit; error_exit1: SrvCloseQueryDirectory( directoryInformation ); error_exit: SrvSetSmbError( WorkContext, status ); normal_exit: SrvEndSmbProcessing( WorkContext, SmbStatusSendResponse ); SrvWmiEndContext(WorkContext); return; } // BlockingDelete NTSTATUS DoDelete ( IN PUNICODE_STRING FullFileName, IN PUNICODE_STRING RelativeFileName, IN PWORK_CONTEXT WorkContext, IN USHORT SmbSearchAttributes, IN PSHARE Share ) /*++ Routine Description: This routine performs the core of a file delete. Arguments: FileName - a full path name, from the system name space root, to the file to delete. RelativeFileName - the name of the file relative to the share root. WorkContext - context block for the operation. The RequestHeader and Session fields are used. SmbSearchAttributes - the search attributes passed in the request SMB. The actual file attributes are verified against these to make sure that the operation is legitimate. Return Value: NTSTATUS - indicates result of operation. --*/ { NTSTATUS status; PMFCB mfcb; PNONPAGED_MFCB nonpagedMfcb; FILE_DISPOSITION_INFORMATION fileDispositionInformation; HANDLE fileHandle = NULL; ULONG caseInsensitive; IO_STATUS_BLOCK ioStatusBlock; PSRV_LOCK mfcbLock; ULONG hashValue; PAGED_CODE( ); // // See if that file is already open. If it is open in // compatibility mode or is an FCB open, we have to close all of // that client's opens. // // *** SrvFindMfcb references the MFCB--remember to dereference it. // if ( (WorkContext->RequestHeader->Flags & SMB_FLAGS_CASE_INSENSITIVE) || WorkContext->Session->UsingUppercasePaths ) { caseInsensitive = OBJ_CASE_INSENSITIVE; mfcb = SrvFindMfcb( FullFileName, TRUE, &mfcbLock, &hashValue, WorkContext ); } else { caseInsensitive = 0; mfcb = SrvFindMfcb( FullFileName, FALSE, &mfcbLock, &hashValue, WorkContext ); } if ( mfcb != NULL ) { nonpagedMfcb = mfcb->NonpagedMfcb; ACQUIRE_LOCK( &nonpagedMfcb->Lock ); } if( mfcbLock ) { RELEASE_LOCK( mfcbLock ); } if ( mfcb == NULL || !mfcb->CompatibilityOpen ) { ACCESS_MASK deleteAccess = DELETE; OBJECT_ATTRIBUTES objectAttributes; // // Either the file wasn't opened by the server or it was not // a compatibility/FCB open, so open it here for the delete. // del_no_file_handle: // // If there was an MFCB for this file, we now hold its lock and a // referenced pointer. Undo both. // if ( mfcb != NULL ) { RELEASE_LOCK( &nonpagedMfcb->Lock ); SrvDereferenceMfcb( mfcb ); } SrvInitializeObjectAttributes_U( &objectAttributes, RelativeFileName, caseInsensitive, NULL, NULL ); INCREMENT_DEBUG_STAT( SrvDbgStatistics.TotalOpenAttempts ); INCREMENT_DEBUG_STAT( SrvDbgStatistics.TotalOpensForPathOperations ); // // !!! Currently we can't specify complete if oplocked, because // this won't break a batch oplock. Unfortunately this also // means that we can't timeout the open (if the oplock break // takes too long) and fail this SMB gracefully. // status = SrvIoCreateFile( WorkContext, &fileHandle, DELETE, // DesiredAccess &objectAttributes, &ioStatusBlock, NULL, // AllocationSize 0L, // FileAttributes 0L, // ShareAccess FILE_OPEN, // Disposition FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, // CreateOptions NULL, // EaBuffer 0L, // EaLength CreateFileTypeNone, NULL, // ExtraCreateParameters IO_FORCE_ACCESS_CHECK, // Options WorkContext->TreeConnect->Share ); if( status == STATUS_INVALID_PARAMETER ) { status = SrvIoCreateFile( WorkContext, &fileHandle, DELETE, // DesiredAccess &objectAttributes, &ioStatusBlock, NULL, // AllocationSize 0L, // FileAttributes 0L, // ShareAccess FILE_OPEN, // Disposition FILE_NON_DIRECTORY_FILE, // CreateOptions NULL, // EaBuffer 0L, // EaLength CreateFileTypeNone, NULL, // ExtraCreateParameters IO_FORCE_ACCESS_CHECK, // Options WorkContext->TreeConnect->Share ); } if ( NT_SUCCESS(status) ) { SRVDBG_CLAIM_HANDLE( fileHandle, "FIL", 27, 0 ); } ASSERT( status != STATUS_OPLOCK_BREAK_IN_PROGRESS ); if ( !NT_SUCCESS(status) ) { IF_DEBUG(ERRORS) { KdPrint(( "SrvSmbDelete: SrvIoCreateFile failed: %X\n", status )); } // // If the user didn't have this permission, update the // statistics database. // if ( status == STATUS_ACCESS_DENIED ) { SrvStatistics.AccessPermissionErrors++; } if ( fileHandle != NULL ) { SRVDBG_RELEASE_HANDLE( fileHandle, "FIL", 41, 0 ); SrvNtClose( fileHandle, TRUE ); } return status; } // // Make sure that the search attributes jive with the attributes // on the file. // status = SrvCheckSearchAttributesForHandle( fileHandle, SmbSearchAttributes ); if ( !NT_SUCCESS(status) ) { SRVDBG_RELEASE_HANDLE( fileHandle, "FIL", 42, 0 ); SrvNtClose( fileHandle, TRUE ); return status; } // // Now that the file has been opened, delete it with // NtSetInformationFile. // SrvStatistics.TotalFilesOpened++; fileDispositionInformation.DeleteFile = TRUE; status = NtSetInformationFile( fileHandle, &ioStatusBlock, &fileDispositionInformation, sizeof(FILE_DISPOSITION_INFORMATION), FileDispositionInformation ); if ( !NT_SUCCESS(status) ) { INTERNAL_ERROR( ERROR_LEVEL_UNEXPECTED, "SrvSmbDelete: NtSetInformationFile (file disposition) " "returned %X", status, NULL ); SrvLogServiceFailure( SRV_SVC_NT_SET_INFO_FILE, status ); SRVDBG_RELEASE_HANDLE( fileHandle, "FIL", 43, 0 ); SrvNtClose( fileHandle, TRUE ); return status; } IF_SMB_DEBUG(FILE_CONTROL2) { if( NT_SUCCESS( status ) ) { KdPrint(( "SrvSmbDelete: %wZ successfully deleted.\n", FullFileName )); } } // // Close the opened file so that it can be deleted. This will // happen automatically, since the FCB_STATE_FLAG_DELETE_ON_CLOSE // flag of the FCB has been set by NtSetInformationFile. // SRVDBG_RELEASE_HANDLE( fileHandle, "FIL", 44, 0 ); SrvNtClose( fileHandle, TRUE ); } else { FILE_DISPOSITION_INFORMATION fileDispositionInformation; HANDLE fileHandle = NULL; // // The file was opened by the server in compatibility mode // or as an FCB open. Check the granted access to make sure // that the file can be deleted. // ACCESS_MASK deleteAccess = DELETE; PLFCB lfcb = CONTAINING_RECORD( mfcb->LfcbList.Blink, LFCB, MfcbListEntry ); // // If this file has been closed. Go back to no mfcb case. // // *** The specific motivation for this change was to fix a problem // where a compatibility mode open was closed, the response was // sent, and a Delete SMB was received before the mfcb was // completely cleaned up. This resulted in the MFCB and LFCB // still being present, which caused the delete processing to // try to use the file handle in the LFCB. // if ( lfcb->FileHandle == 0 ) { goto del_no_file_handle; } // // Make sure that the session which sent this request is the // same as the one which has the file open. // if ( lfcb->Session != WorkContext->Session ) { // // A different session has the file open in compatibility // mode, so reject the request. // RELEASE_LOCK( &nonpagedMfcb->Lock ); SrvDereferenceMfcb( mfcb ); return STATUS_SHARING_VIOLATION; } if ( !NT_SUCCESS(IoCheckDesiredAccess( &deleteAccess, lfcb->GrantedAccess )) ) { // // The client cannot delete this file, so close all the // RFCBs and return an error. // SrvCloseRfcbsOnLfcb( lfcb ); RELEASE_LOCK( &nonpagedMfcb->Lock ); SrvDereferenceMfcb( mfcb ); return STATUS_ACCESS_DENIED; } // // Delete the file with NtSetInformationFile. // fileHandle = lfcb->FileHandle; fileDispositionInformation.DeleteFile = TRUE; status = NtSetInformationFile( fileHandle, &ioStatusBlock, &fileDispositionInformation, sizeof(FILE_DISPOSITION_INFORMATION), FileDispositionInformation ); if ( !NT_SUCCESS(status) ) { INTERNAL_ERROR( ERROR_LEVEL_EXPECTED, "SrvSmbDelete: NtSetInformationFile (disposition) " "returned %X", status, NULL ); SrvLogServiceFailure( SRV_SVC_NT_SET_INFO_FILE, status ); SrvCloseRfcbsOnLfcb( lfcb ); RELEASE_LOCK( &nonpagedMfcb->Lock ); SrvDereferenceMfcb( mfcb ); return status; } IF_SMB_DEBUG(FILE_CONTROL2) { KdPrint(( "SrvSmbDelete: %wZ successfully deleted.\n", FullFileName )); } // // Close the RFCBs on the MFCB. Since this is a compatability // or FCB open, there is only a single LFCB for the MFCB. This // will result in the LFCB's file handle being closed, so there // is no need to call NtClose here. // SrvCloseRfcbsOnLfcb( lfcb ); RELEASE_LOCK( &nonpagedMfcb->Lock ); SrvDereferenceMfcb( mfcb ); } return STATUS_SUCCESS; } // DoDelete SMB_PROCESSOR_RETURN_TYPE SrvSmbRename ( SMB_PROCESSOR_PARAMETERS ) /*++ Routine Description: Processes the Rename SMB. Arguments: SMB_PROCESSOR_PARAMETERS - See smbprocs.h for a description of the parameters to SMB processor routines. Return Value: SMB_PROCESSOR_RETURN_TYPE - See smbprocs.h --*/ { PAGED_CODE(); if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT) WorkContext->PreviousSMB = EVENT_TYPE_SMB_RENAME; SrvWmiStartContext(WorkContext); // // This SMB must be processed in a blocking thread. // WorkContext->FspRestartRoutine = BlockingRename; SrvQueueWorkToBlockingThread( WorkContext ); SrvWmiEndContext(WorkContext); return SmbStatusInProgress; } // SrvSmbRename VOID SRVFASTCALL BlockingRename ( IN OUT PWORK_CONTEXT WorkContext ) /*++ Routine Description: This routine processes the Rename SMB. Arguments: WorkContext - work context block Return Value: None. --*/ { PREQ_RENAME request; PREQ_NTRENAME ntrequest; PUCHAR RenameBuffer; PRESP_RENAME response; NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING sourceName; UNICODE_STRING targetName; USHORT smbFlags; USHORT ByteCount; PCHAR target; PCHAR lastPositionInBuffer; PTREE_CONNECT treeConnect; PSESSION session; PSHARE share; BOOLEAN isUnicode; BOOLEAN isNtRename; BOOLEAN isDfs; PSRV_DIRECTORY_INFORMATION directoryInformation; ULONG renameRetries; PAGED_CODE( ); if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT) WorkContext->PreviousSMB = EVENT_TYPE_SMB_RENAME; SrvWmiStartContext(WorkContext); IF_SMB_DEBUG(FILE_CONTROL1) { KdPrint(( "Rename file request header at 0x%p, response header at 0x%p\n", WorkContext->RequestHeader, WorkContext->ResponseHeader )); KdPrint(( "Rename file request parameters at 0x%p, response parameters at 0x%p\n", WorkContext->RequestParameters, WorkContext->ResponseParameters )); } response = (PRESP_RENAME)WorkContext->ResponseParameters; request = (PREQ_RENAME)WorkContext->RequestParameters; ntrequest = (PREQ_NTRENAME)WorkContext->RequestParameters; isNtRename = (BOOLEAN)(WorkContext->RequestHeader->Command == SMB_COM_NT_RENAME); if (isNtRename) { RenameBuffer = ntrequest->Buffer; ByteCount = SmbGetUshort(&ntrequest->ByteCount); } else { RenameBuffer = request->Buffer; ByteCount = SmbGetUshort(&request->ByteCount); } // // If a session block has not already been assigned to the current // work context , verify the UID. If verified, the address of the // session block corresponding to this user is stored in the // WorkContext block and the session block is referenced. // // Find tree connect corresponding to given TID if a tree connect // pointer has not already been put in the WorkContext block by an // AndX command. // status = SrvVerifyUidAndTid( WorkContext, &session, &treeConnect, ShareTypeDisk ); if ( !NT_SUCCESS(status) ) { IF_DEBUG(SMB_ERRORS) { KdPrint(( "BlockingRename: Invalid UID or TID\n" )); } goto error_exit; } // // If the session has expired, return that info // if( session->IsSessionExpired ) { status = SESSION_EXPIRED_STATUS_CODE; goto error_exit; } // // Get the share block from the tree connect block. This does not need // to be a referenced pointer because we have referenced the tree // connect, and it has the share referenced. // share = treeConnect->Share; // // Set up the path name for the file we will search for. The +1 // accounts for the ASCII token of the SMB protocol. // isUnicode = SMB_IS_UNICODE( WorkContext ); isDfs = SMB_CONTAINS_DFS_NAME( WorkContext ); status = SrvCanonicalizePathName( WorkContext, share, NULL, (PVOID)(RenameBuffer + 1), END_OF_REQUEST_SMB( WorkContext ), TRUE, isUnicode, &sourceName ); if( !NT_SUCCESS( status ) ) { IF_DEBUG(SMB_ERRORS) { KdPrint(( "BlockingRename: illegal path name: %s\n", (PSZ)RenameBuffer + 1 )); } goto error_exit; } if( !sourceName.Length ) { IF_DEBUG(SMB_ERRORS) { KdPrint(( "BlockingRename: No source name\n" )); } status = STATUS_OBJECT_PATH_SYNTAX_BAD; goto error_exit; } // // Get a pointer to the new pathname of the file. This is in the // buffer field of the request SMB after the source name. The // target is delimited by the SMB_FORMAT_ASCII. // // While doing this, make sure that we do not walk off the end of the // SMB buffer if the client did not include the SMB_FORMAT_ASCII // token. // lastPositionInBuffer = (PCHAR)RenameBuffer + ByteCount; if( !isUnicode ) { for ( target = (PCHAR)RenameBuffer + 1; (target < lastPositionInBuffer) && (*target != SMB_FORMAT_ASCII); target++ ) { ; } } else { PWCHAR p = (PWCHAR)(RenameBuffer + 1); // // Skip the Original filename part. The name is null-terminated // (see rdr\utils.c RdrCopyNetworkPath()) // // // Ensure p is suitably aligned // p = ALIGN_SMB_WSTR(p); // // Skip over the source filename // for( p = ALIGN_SMB_WSTR(p); p < (PWCHAR)lastPositionInBuffer && *p != UNICODE_NULL; p++ ) { ; } // // Search for SMB_FORMAT_ASCII which preceeds the target name // // for ( target = (PUCHAR)(p + 1); target < lastPositionInBuffer && *target != SMB_FORMAT_ASCII; target++ ) { ; } } // // If there was no SMB_FORMAT_ASCII in the passed buffer, fail. // if ( (target >= lastPositionInBuffer) || (*target != SMB_FORMAT_ASCII) ) { if ( !isUnicode ) { RtlFreeUnicodeString( &sourceName ); } status = STATUS_INVALID_SMB; goto error_exit; } // // If the SMB was originally marked as containing Dfs names, then the // call to SrvCanonicalizePathName for the source path has cleared that // flag. So, re-mark the SMB as containing Dfs names before calling // SrvCanonicalizePathName on the target path. // if (isDfs) { SMB_MARK_AS_DFS_NAME( WorkContext ); } status = SrvCanonicalizePathName( WorkContext, share, NULL, target + 1, END_OF_REQUEST_SMB( WorkContext ), TRUE, isUnicode, &targetName ); if( !NT_SUCCESS( status ) ) { IF_DEBUG(SMB_ERRORS) { KdPrint(( "BlockingRename: illegal path name: %s\n", target + 1 )); } if ( !isUnicode ) { RtlFreeUnicodeString( &sourceName ); } goto error_exit; } if( !targetName.Length ) { IF_DEBUG(SMB_ERRORS) { KdPrint(( "BlockingRename: No target name\n" )); } if( !isUnicode ) { RtlFreeUnicodeString( &sourceName ); } status = STATUS_OBJECT_PATH_SYNTAX_BAD; goto error_exit; } // // Ensure this client's RFCB cache is empty. This covers the case // where a client has open files in a directory we are trying // to rename. // SrvCloseCachedRfcbsOnConnection( WorkContext->Connection ); if ( !FsRtlDoesNameContainWildCards( &sourceName ) ) { USHORT InformationLevel = SMB_NT_RENAME_RENAME_FILE; ULONG ClusterCount = 0; if (isNtRename) { InformationLevel = SmbGetUshort(&ntrequest->InformationLevel); ClusterCount = SmbGetUlong(&ntrequest->ClusterCount); } smbFlags = 0; // // Use SrvMoveFile to rename the file. The SmbOpenFunction is // set to indicate that existing files may not be overwritten, // and we may create new files. Also, the target may not be // a directory; if it already exists as a directory, fail. // renameRetries = SrvSharingViolationRetryCount; start_retry1: status = SrvMoveFile( WorkContext, WorkContext->TreeConnect->Share, SMB_OFUN_CREATE_CREATE | SMB_OFUN_OPEN_FAIL, &smbFlags, SmbGetUshort( &request->SearchAttributes ), TRUE, InformationLevel, ClusterCount, &sourceName, &targetName ); if ( (status == STATUS_SHARING_VIOLATION) && (renameRetries-- > 0) ) { (VOID) KeDelayExecutionThread( KernelMode, FALSE, &SrvSharingViolationDelay ); goto start_retry1; } if ( !isUnicode ) { RtlFreeUnicodeString( &targetName ); RtlFreeUnicodeString( &sourceName ); } if ( !NT_SUCCESS(status) ) { goto error_exit; } } else if (isNtRename) { // Wild cards not allowed! status = STATUS_OBJECT_PATH_SYNTAX_BAD; goto error_exit; } else { BOOLEAN firstCall = TRUE; UNICODE_STRING subdirInfo; CLONG bufferLength; BOOLEAN filterLongNames; // // We need the full path name of each file that is returned by // SrvQueryDirectoryFile, so we need to find the part of the // passed filename that contains subdirectory information (e.g. // for a\b\c\*.*, we want a string that indicates a\b\c). // subdirInfo.Buffer = sourceName.Buffer; subdirInfo.Length = SrvGetSubdirectoryLength( &sourceName ); subdirInfo.MaximumLength = subdirInfo.Length; // // SrvQueryDirectoryFile requires a buffer from nonpaged pool. // Since this routine does not use the buffer field of the // request SMB after the pathname, use this. The buffer must be // quadword-aligned. // directoryInformation = (PSRV_DIRECTORY_INFORMATION)((ULONG_PTR)((PCHAR)RenameBuffer + ByteCount + 7) & ~7); bufferLength = WorkContext->RequestBuffer->BufferLength - PTR_DIFF(directoryInformation, WorkContext->RequestBuffer->Buffer); smbFlags = 0; // // Determine whether long filenames (non-8.3) should be filtered out // or processed. // if ( (SmbGetAlignedUshort( &WorkContext->RequestHeader->Flags2 ) & SMB_FLAGS2_KNOWS_LONG_NAMES) != 0 ) { filterLongNames = FALSE; } else { filterLongNames = TRUE; } // // Call SrvQueryDirectoryFile to get file(s) to rename, renaming as // we get each file. // // *** We ask for FileBothDirectoryInformation so that we will // pick up long names on NTFS that have short name // equivalents. Without this, DOS clients will not be able // to rename long names on NTFS volumes. // while ( ( status = SrvQueryDirectoryFile( WorkContext, firstCall, filterLongNames, FALSE, FileBothDirectoryInformation, 0, &sourceName, NULL, SmbGetUshort( &request->SearchAttributes ), directoryInformation, bufferLength ) ) != STATUS_NO_MORE_FILES ) { PFILE_BOTH_DIR_INFORMATION bothDirInfo; UNICODE_STRING sourceFileName; UNICODE_STRING sourcePathName; if ( !NT_SUCCESS(status) ) { IF_DEBUG(ERRORS) { KdPrint(( "BlockingRename: SrvQueryDirectoryFile failed: %X\n", status )); } if ( !isUnicode ) { RtlFreeUnicodeString( &targetName ); RtlFreeUnicodeString( &sourceName ); } goto error_exit1; } bothDirInfo = (PFILE_BOTH_DIR_INFORMATION)directoryInformation->CurrentEntry; // // Note that we use the standard name to do the delete, even // though we may have matched on the NTFS short name. The // client doesn't care which name we use to do the delete. // sourceFileName.Length = (SHORT)bothDirInfo->FileNameLength; sourceFileName.MaximumLength = sourceFileName.Length; sourceFileName.Buffer = bothDirInfo->FileName; IF_SMB_DEBUG(FILE_CONTROL2) { KdPrint(( "SrvQueryDirectoryFile--name %wZ, length = %ld, " "status = %X\n", &sourceFileName, sourceFileName.Length, status )); } firstCall = FALSE; // // Set up the full source name string. // SrvAllocateAndBuildPathName( &subdirInfo, &sourceFileName, NULL, &sourcePathName ); if ( sourcePathName.Buffer == NULL ) { IF_DEBUG(ERRORS) { KdPrint(( "BlockingRename: SrvAllocateAndBuildPathName failed: " "%X\n", status )); } if ( !isUnicode ) { RtlFreeUnicodeString( &targetName ); RtlFreeUnicodeString( &sourceName ); } status = STATUS_INSUFF_SERVER_RESOURCES; goto error_exit1; } // // Use SrvMoveFile to copy or rename the file. The // SmbOpenFunction is set to indicate that existing files // may not be overwritten, and we may create new files. // // *** SrvQueryDirectoryFile has already filtered based on // the search attributes, so tell SrvMoveFile that files // with the system and hidden bits are OK. This will // prevent the call to NtQueryDirectoryFile performed in // SrvCheckSearchAttributesForHandle. // renameRetries = SrvSharingViolationRetryCount; start_retry2: status = SrvMoveFile( WorkContext, WorkContext->TreeConnect->Share, SMB_OFUN_CREATE_CREATE | SMB_OFUN_OPEN_FAIL, &smbFlags, (USHORT)(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN), TRUE, SMB_NT_RENAME_RENAME_FILE, 0, &sourcePathName, &targetName ); if ( (status == STATUS_SHARING_VIOLATION) && (renameRetries-- > 0) ) { (VOID) KeDelayExecutionThread( KernelMode, FALSE, &SrvSharingViolationDelay ); goto start_retry2; } FREE_HEAP( sourcePathName.Buffer ); if ( !NT_SUCCESS(status) ) { if ( !isUnicode ) { RtlFreeUnicodeString( &targetName ); RtlFreeUnicodeString( &sourceName ); } goto error_exit1; } } // // Clean up now that the search is done. // if ( !isUnicode ) { RtlFreeUnicodeString( &targetName ); RtlFreeUnicodeString( &sourceName ); } SrvCloseQueryDirectory( directoryInformation ); // // If no files were found, return an error to the client. // if ( firstCall ) { status = STATUS_NO_SUCH_FILE; goto error_exit; } } // // Build the response SMB. // response->WordCount = 0; SmbPutUshort( &response->ByteCount, 0 ); WorkContext->ResponseParameters = NEXT_LOCATION( response, RESP_RENAME, 0 ); IF_DEBUG(TRACE2) KdPrint(( "BlockingRename complete.\n" )); goto normal_exit; error_exit1: SrvCloseQueryDirectory( directoryInformation ); error_exit: SrvSetSmbError( WorkContext, status ); normal_exit: SrvEndSmbProcessing( WorkContext, SmbStatusSendResponse ); SrvWmiEndContext(WorkContext); return; } // BlockingRename SMB_PROCESSOR_RETURN_TYPE SrvSmbMove ( SMB_PROCESSOR_PARAMETERS ) /*++ Routine Description: Processes the Move SMB. Arguments: SMB_PROCESSOR_PARAMETERS - See smbprocs.h for a description of the parameters to SMB processor routines. Return Value: SMB_PROCESSOR_RETURN_TYPE - See smbprocs.h --*/ { PAGED_CODE(); if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT) WorkContext->PreviousSMB = EVENT_TYPE_SMB_MOVE; SrvWmiStartContext(WorkContext); // // This SMB must be processed in a blocking thread. // WorkContext->FspRestartRoutine = BlockingMove; SrvQueueWorkToBlockingThread( WorkContext ); SrvWmiEndContext(WorkContext); return SmbStatusInProgress; } // SrvSmbMove VOID SRVFASTCALL BlockingMove ( IN OUT PWORK_CONTEXT WorkContext ) /*++ Routine Description: This routine processes the Move SMB. Arguments: WorkContext - work context block Return Value: None. --*/ { PREQ_MOVE request; PRESP_MOVE response; NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING sourceName; UNICODE_STRING sourceFileName; UNICODE_STRING sourcePathName; UNICODE_STRING targetName; PSRV_DIRECTORY_INFORMATION directoryInformation; USHORT tid2; USHORT smbFlags; PCHAR lastPositionInBuffer; PCHAR target; BOOLEAN isRenameOperation; BOOLEAN isUnicode = TRUE; BOOLEAN isDfs; USHORT smbOpenFunction; USHORT errorPathNameLength = 0; USHORT count = 0; PTREE_CONNECT sourceTreeConnect, targetTreeConnect; PSESSION session; PSHARE share; PAGED_CODE( ); if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT) WorkContext->PreviousSMB = EVENT_TYPE_SMB_MOVE; SrvWmiStartContext(WorkContext); IF_SMB_DEBUG(FILE_CONTROL1) { KdPrint(( "Move/Copy request header at 0x%p, response header at 0x%p\n", WorkContext->RequestHeader, WorkContext->ResponseHeader )); KdPrint(( "Move/Copy request parameters at 0x%p, response parameters at 0x%p\n", WorkContext->RequestParameters, WorkContext->ResponseParameters )); } request = (PREQ_MOVE)WorkContext->RequestParameters; response = (PRESP_MOVE)WorkContext->ResponseParameters; // // Set pointers to NULL so that we know how to clean up on exit. // directoryInformation = NULL; targetTreeConnect = NULL; sourceName.Buffer = NULL; targetName.Buffer = NULL; sourcePathName.Buffer = NULL; // // If a session block has not already been assigned to the current // work context , verify the UID. If verified, the address of the // session block corresponding to this user is stored in the WorkContext // block and the session block is referenced. // // Find tree connect corresponding to given TID if a tree connect // pointer has not already been put in the WorkContext block by an // AndX command. // status = SrvVerifyUidAndTid( WorkContext, &session, &sourceTreeConnect, ShareTypeDisk ); if ( !NT_SUCCESS(status) ) { IF_DEBUG(SMB_ERRORS) { KdPrint(( "BlockingMove: Invalid UID or TID\n" )); } goto exit; } if( session->IsSessionExpired ) { status = SESSION_EXPIRED_STATUS_CODE; goto exit; } // // Get the share block from the tree connect block. This does not need // to be a referenced pointer because we have referenced the tree // connect, and it has the share referenced. // share = sourceTreeConnect->Share; // // Get the target tree connect. The TID for this is in the Tid2 // field of the request SMB. Because SrvVerifyTid sets the // TreeConnect field of the WorkContext block, set it back after // calling the routine. Remember to dereference this pointer before // exiting this routine, as it will not be automatically // dereferenced because it is not in the WorkContext block. // // If Tid2 is -1 (0xFFFF), then the TID specified in the SMB header // is used. // tid2 = SmbGetUshort( &request->Tid2 ); if ( tid2 == (USHORT)0xFFFF ) { tid2 = SmbGetAlignedUshort( &WorkContext->RequestHeader->Tid ); } WorkContext->TreeConnect = NULL; // Must be NULL for SrvVerifyTid targetTreeConnect = SrvVerifyTid( WorkContext, tid2 ); WorkContext->TreeConnect = sourceTreeConnect; if ( targetTreeConnect == NULL || targetTreeConnect->Share->ShareType != ShareTypeDisk ) { IF_DEBUG(SMB_ERRORS) { KdPrint(( "BlockingMove: Invalid TID2: 0x%lx\n", tid2 )); } status = STATUS_SMB_BAD_TID; goto exit; } // // Determine whether this is a rename or a copy. // if ( WorkContext->RequestHeader->Command == SMB_COM_MOVE ) { isRenameOperation = TRUE; } else { isRenameOperation = FALSE; } // // Store the open function. // smbOpenFunction = SmbGetUshort( &request->OpenFunction ); // // Set up the target pathnames. We must do the target first, as the // SMB rename extended protocol does not use the ASCII tokens, so we // will lose the information about the start of the target name when // we canonicalize the source name. // // Instead of using strlen() to find the end of the source string, // do it here so that we can make a check to ensure that we don't // walk off the end of the SMB buffer and cause an access violation. // lastPositionInBuffer = (PCHAR)request->Buffer + SmbGetUshort( &request->ByteCount ); for ( target = (PCHAR)request->Buffer; (target < lastPositionInBuffer) && (*target != 0); target++ ) { ; } // // If there was no zero terminator in the buffer, fail. // if ( (target == lastPositionInBuffer) || (*target != 0) ) { IF_DEBUG(SMB_ERRORS) { KdPrint(( "No terminator on first name.\n" )); } SrvLogInvalidSmb( WorkContext ); status = STATUS_INVALID_SMB; goto exit; } target++; isUnicode = SMB_IS_UNICODE( WorkContext ); isDfs = SMB_CONTAINS_DFS_NAME( WorkContext ); status = SrvCanonicalizePathName( WorkContext, share, NULL, target, END_OF_REQUEST_SMB( WorkContext ), TRUE, isUnicode, &targetName ); if( !NT_SUCCESS( status ) ) { IF_DEBUG(SMB_ERRORS) { KdPrint(( "BlockingMove: illegal path name (target): %wZ\n", &targetName )); } goto exit; } // // If the SMB was originally marked as containing Dfs names, then the // call to SrvCanonicalizePathName for the target path has cleared that // flag. So, re-mark the SMB as containing Dfs names before calling // SrvCanonicalizePathName on the source path. // if (isDfs) { SMB_MARK_AS_DFS_NAME( WorkContext ); } // // Set up the source name. // status = SrvCanonicalizePathName( WorkContext, share, NULL, request->Buffer, END_OF_REQUEST_SMB( WorkContext ), TRUE, isUnicode, &sourceName ); if( !NT_SUCCESS( status ) ) { IF_DEBUG(SMB_ERRORS) { KdPrint(( "BlockingMove: illegal path name (source): %s\n", request->Buffer )); } goto exit; } smbFlags = SmbGetUshort( &request->Flags ); // // Copy interprets ; as *. If the last character was ; and this was // not at the end of a file name with other characters (as in // "file;" then convert the ; to *. // if ( sourceName.Buffer[(sourceName.Length/sizeof(WCHAR))-1] == ';' && ( sourceName.Length == 2 || sourceName.Buffer[(sourceName.Length/sizeof(WCHAR))-2] == '\\' ) ) { sourceName.Buffer[(sourceName.Length/sizeof(WCHAR))-1] = '*'; } // // Tree copy not implemented. If this is a single file copy, // let it go through. For now, we make sure that it does not // have any wild card characters, we do additional checking // inside SrvMoveFile. // if ( ( (smbFlags & SMB_COPY_TREE) != 0 ) && FsRtlDoesNameContainWildCards(&sourceName) ) { INTERNAL_ERROR( ERROR_LEVEL_EXPECTED, "Tree copy not implemented.", NULL, NULL ); status = STATUS_NOT_IMPLEMENTED; goto exit; } if ( !FsRtlDoesNameContainWildCards( &sourceName ) ) { // // Use SrvMoveFile to copy or move the file. // // *** These SMBs do not include search attributes, so set // this field equal to zero. If will not be possible // to move a file that has the system or hidden bits on. status = SrvMoveFile( WorkContext, targetTreeConnect->Share, smbOpenFunction, &smbFlags, (USHORT)0, // SmbSearchAttributes FALSE, (USHORT)(isRenameOperation? SMB_NT_RENAME_RENAME_FILE : SMB_NT_RENAME_MOVE_FILE), 0, &sourceName, &targetName ); if ( !NT_SUCCESS(status) ) { goto exit; } count = 1; } else { UNICODE_STRING subdirInfo; BOOLEAN firstCall = TRUE; CLONG bufferLength; BOOLEAN filterLongNames; // // If wildcards were in the original source name, we set the // SmbFlags to SMB_TARGET_IS_DIRECTORY to indicate that the // target must be a directory--this is always the case when // wildcards are used for a rename. (For a copy, it is legal to // specify that the destination is a file and append to that // file--then all the source files are concatenated to that one // target file.) // if ( isRenameOperation ) { smbFlags |= SMB_TARGET_IS_DIRECTORY; } // // SrvQueryDirectoryFile requires a buffer from nonpaged pool. // Since this routine does not use the buffer field of the // request SMB after the pathname, use this. The buffer must be // quadword-aligned. // directoryInformation = (PSRV_DIRECTORY_INFORMATION)( (ULONG_PTR)((PCHAR)request->Buffer + SmbGetUshort( &request->ByteCount ) + 7) & ~7 ); bufferLength = WorkContext->RequestBuffer->BufferLength - PTR_DIFF(directoryInformation, WorkContext->RequestBuffer->Buffer); // // We need the full path name of each file that is returned by // SrvQueryDirectoryFile, so we need to find the part of the // passed filename that contains subdirectory information (e.g. // for a\b\c\*.*, we want a string that indicates a\b\c). // subdirInfo.Buffer = sourceName.Buffer; subdirInfo.Length = SrvGetSubdirectoryLength( &sourceName ); subdirInfo.MaximumLength = subdirInfo.Length; // // Determine whether long filenames (non-8.3) should be filtered out // or processed. // if ( (SmbGetAlignedUshort( &WorkContext->RequestHeader->Flags2 ) & SMB_FLAGS2_KNOWS_LONG_NAMES) != 0 ) { filterLongNames = FALSE; } else { filterLongNames = TRUE; } // // As long as SrvQueryDirectoryFile is able to return file names, // keep renaming. // // *** Set search attributes to find archive files, but not // system or hidden files. This duplicates the LM 2.0 // server behavior. // // *** We ask for FileBothDirectoryInformation so that we will // pick up long names on NTFS that have short name // equivalents. Without this, DOS clients will not be able // to move long names on NTFS volumes. // while ( ( status = SrvQueryDirectoryFile( WorkContext, firstCall, filterLongNames, FALSE, FileBothDirectoryInformation, 0, &sourceName, NULL, FILE_ATTRIBUTE_ARCHIVE, // SmbSearchAttributes directoryInformation, bufferLength ) ) != STATUS_NO_MORE_FILES ) { PFILE_BOTH_DIR_INFORMATION bothDirInfo; if ( !NT_SUCCESS(status) ) { IF_DEBUG(ERRORS) { KdPrint(( "BlockingMove: SrvQueryDirectoryFile failed: %X\n", status )); } goto exit; } bothDirInfo = (PFILE_BOTH_DIR_INFORMATION)directoryInformation->CurrentEntry; // // If we're filtering long names, and the file has a short // name equivalent, then use that name to do the delete. We // do this because we need to return a name to the client if // the operation fails, and we don't want to return a long // name. Note that if the file has no short name, and we're // filtering, then the standard name must be a valid 8.3 // name, so it's OK to return to the client. // if ( filterLongNames && (bothDirInfo->ShortNameLength != 0) ) { sourceFileName.Length = (SHORT)bothDirInfo->ShortNameLength; sourceFileName.Buffer = bothDirInfo->ShortName; } else { sourceFileName.Length = (SHORT)bothDirInfo->FileNameLength; sourceFileName.Buffer = bothDirInfo->FileName; } sourceFileName.MaximumLength = sourceFileName.Length; IF_SMB_DEBUG(FILE_CONTROL2) { KdPrint(( "SrvQueryDirectoryFile--name %wZ, length = %ld, " "status = %X\n", &sourceFileName, sourceFileName.Length, status )); } firstCall = FALSE; // // Set up the full source name string. // SrvAllocateAndBuildPathName( &subdirInfo, &sourceFileName, NULL, &sourcePathName ); if ( sourcePathName.Buffer == NULL ) { status = STATUS_INSUFF_SERVER_RESOURCES; goto exit; } // // Use SrvMoveFile to copy or rename the file. // status = SrvMoveFile( WorkContext, targetTreeConnect->Share, smbOpenFunction, &smbFlags, (USHORT)0, // SmbSearchAttributes FALSE, (USHORT)(isRenameOperation? SMB_NT_RENAME_RENAME_FILE : SMB_NT_RENAME_MOVE_FILE), 0, &sourcePathName, &targetName ); if ( !NT_SUCCESS(status) ) { goto exit; } count++; // // Free the buffer that holds that source name. // FREE_HEAP( sourcePathName.Buffer ); sourcePathName.Buffer = NULL; // // If this is a copy operation with wildcards and the target is // a file, then all files should be appended to the target. The // target is truncated on the first call to SrvMoveFile if that // was specified by the caller. // // This is done by turning off the truncate bit in the // SmbOpenFunction and turning on the append bit. // if ( !isRenameOperation && directoryInformation->Wildcards && (smbFlags & SMB_TARGET_IS_FILE) ) { smbOpenFunction &= ~SMB_OFUN_OPEN_TRUNCATE; smbOpenFunction |= SMB_OFUN_OPEN_APPEND; } } // // If no files were found, return an error to the client. // if ( firstCall ) { status = STATUS_NO_SUCH_FILE; goto exit; } } // // Build the response SMB. // SmbPutUshort( &response->ByteCount, 0 ); WorkContext->ResponseParameters = NEXT_LOCATION( response, RESP_MOVE, 0 ); status = STATUS_SUCCESS; exit: response->WordCount = 1; SmbPutUshort( &response->Count, count ); if ( directoryInformation != NULL ) { SrvCloseQueryDirectory( directoryInformation ); } if ( targetTreeConnect != NULL) { SrvDereferenceTreeConnect( targetTreeConnect ); } if ( !NT_SUCCESS(status) ) { SrvSetSmbError( WorkContext, status ); if ( sourcePathName.Buffer != NULL ) { // // Put the name of the file where the error occurred in the // buffer field of the response SMB. // RtlCopyMemory( response->Buffer, sourcePathName.Buffer, sourcePathName.Length ); response->Buffer[sourcePathName.Length] = '\0'; SmbPutUshort( &response->ByteCount, (SHORT)(sourcePathName.Length+1) ); WorkContext->ResponseParameters = NEXT_LOCATION( response, RESP_MOVE, sourcePathName.Length+1 ); FREE_HEAP( sourcePathName.Buffer ); } else if ( sourceName.Buffer != NULL ) { // // Put the name of the file where the error occurred in the // buffer field of the response SMB. // RtlCopyMemory( response->Buffer, sourceName.Buffer, sourceName.Length ); response->Buffer[sourceName.Length] = '\0'; SmbPutUshort( &response->ByteCount, (SHORT)(sourceName.Length+1) ); WorkContext->ResponseParameters = NEXT_LOCATION( response, RESP_MOVE, sourceName.Length+1 ); } } if ( !isUnicode ) { if ( targetName.Buffer != NULL ) { RtlFreeUnicodeString( &targetName ); } if ( sourceName.Buffer != NULL ) { RtlFreeUnicodeString( &sourceName ); } } IF_DEBUG(TRACE2) KdPrint(( "BlockingMove complete.\n" )); SrvEndSmbProcessing( WorkContext, SmbStatusSendResponse ); SrvWmiEndContext(WorkContext); return; } // BlockingMove SMB_TRANS_STATUS SrvSmbNtRename ( IN OUT PWORK_CONTEXT WorkContext ) /*++ Routine Description: Processes the NT rename request. This request arrives in an NT transact SMB. Arguments: WorkContext - Supplies the address of a Work Context Block describing the current request. See smbtypes.h for a more complete description of the valid fields. Return Value: SMB_TRANS_STATUS - Indicates whether an error occurred, and, if so, whether data should be returned to the client. See smbtypes.h for a more complete description. --*/ { PREQ_NT_RENAME request; NTSTATUS status; PTRANSACTION transaction; PRFCB rfcb; PAGED_CODE( ); transaction = WorkContext->Parameters.Transaction; IF_SMB_DEBUG( FILE_CONTROL1 ) { KdPrint(( "SrvSmbNtRename entered; transaction 0x%p\n", transaction )); } request = (PREQ_NT_RENAME)transaction->InParameters; // // Verify that enough parameter bytes were sent and that we're allowed // to return enough parameter bytes. // if ( transaction->ParameterCount < sizeof(REQ_NT_RENAME) ) { // // Not enough parameter bytes were sent. // IF_SMB_DEBUG( FILE_CONTROL1 ) { KdPrint(( "SrvSmbNtRename: bad parameter byte count: " "%ld\n", transaction->ParameterCount )); } SrvSetSmbError( WorkContext, STATUS_INVALID_SMB ); return SmbTransStatusErrorWithoutData; } // // Verify the FID. If verified, the RFCB block is referenced // and its addresses is stored in the WorkContext block, and the // RFCB address is returned. // rfcb = SrvVerifyFid( WorkContext, SmbGetUshort( &request->Fid ), TRUE, NULL, // don't serialize with raw write &status ); if ( rfcb == SRV_INVALID_RFCB_POINTER ) { // // Invalid file ID or write behind error. Reject the request. // IF_DEBUG(ERRORS) { KdPrint(( "SrvSmbNtRename: Status %X on FID: 0x%lx\n", status, SmbGetUshort( &request->Fid ) )); } SrvSetSmbError( WorkContext, status ); return SmbTransStatusErrorWithoutData; } // // Verify the information level and the number of input and output // data bytes available. // IF_DEBUG(TRACE2) KdPrint(( "SrvSmbNtRename complete.\n" )); return SmbTransStatusSuccess; } // SrvSmbNtRename