/*++ Copyright (c) 1997 Microsoft Corporation All rights reserved Module Name: handle.c Abstract: Contains all functions related to the maintanence of print handles. Author: Environment: User Mode -Win32 Revision History: --*/ #include "precomp.h" #pragma hdrstop #include "client.h" DWORD OpenPrinterRPC( PSPOOL pSpool ); BOOL ClosePrinterRPC( IN PSPOOL pSpool, IN BOOL bRevalidate ); BOOL ClosePrinterContextHandle( HANDLE hPrinter ); BOOL ClosePrinterWorker( PSPOOL pSpool ); DWORD gcClientHandle = 0; #ifdef DBG_TRACE_HANDLE PSPOOL gpFirstSpool = NULL; #endif EProtectResult eProtectHandle( IN HANDLE hPrinter, IN BOOL bClose ) /*++ Routine Description: Protect a print handle so that it will not be deleted while it is being used. If this is called by the Close routine, then this call returns whether the Close should continue or be aborted. Note: This only provides close protection--it does not guard against simultaneous access by non-close operations. There must always be a matching vUnprotect call when the callee is done with the handle. Arguments: hPrinter - pSpool to protect. bClose - If TRUE, indicates that the callee wants to close the handle. (Generally called by ClosePrinter only.) The return value will indicate whether the calleeis allowed to close the printer. Return Value: kProtectHandleSuccess - Call succeeded; printer handle can be used normally. kProtectHandleInvalid - Handle is invalid; call failed. kProtectHandlePendingDeletion - This only occurs when bClose is TRUE. The Operation on handle is in process, and the close will happen when the other thread has completed. LastError only set if handle kProtectHandleInvalid is returned. --*/ { EProtectResult eResult = kProtectHandleInvalid; PSPOOL pSpool = (PSPOOL)hPrinter; vEnterSem(); try { if( pSpool && (pSpool->signature == SP_SIGNATURE ) && !( pSpool->Status & ( SPOOL_STATUS_CLOSE | SPOOL_STATUS_PENDING_DELETION ))){ // // Valid handle. // eResult = kProtectHandleSuccess; } else { DBGMSG( DBG_WARN, ( "Bad hPrinter %x %x\n", pSpool, pSpool ? pSpool->signature : 0 )); } } except( EXCEPTION_EXECUTE_HANDLER ){ DBGMSG( DBG_WARN, ( "Unmapped pSpool %x\n", pSpool )); } if( eResult == kProtectHandleSuccess ){ // // If bClose, then see if an operation is currently executing. // if( bClose ){ if(( pSpool->Status & SPOOL_STATUS_PENDING_DELETION ) || pSpool->cActive ){ // // Mark pSpool to close itself once the operation has // completed in the other thread. // pSpool->Status |= SPOOL_STATUS_PENDING_DELETION; eResult = kProtectHandlePendingDeletion; } else { // // No call is active, so mark ourselves as closing so // that no other call will succeed using this handle. // pSpool->Status |= SPOOL_STATUS_CLOSE; } } } else { // // Not a valid handle. // SetLastError( ERROR_INVALID_HANDLE ); } if( eResult == kProtectHandleSuccess ){ // // Returning success, we are now active. // ++pSpool->cActive; } vLeaveSem(); return eResult; } VOID vUnprotectHandle( IN HANDLE hPrinter ) /*++ Routine Description: Unprotect a print handle. This must be called once for each successful bProtectHandle. Arguments: hPrinter - Handle to unprotect. Return Value: --*/ { PSPOOL pSpool = (PSPOOL)hPrinter; BOOL bCallClosePrinter = FALSE; vEnterSem(); // // No longer active. However, it it's closing, leave it marked // as closing since we don't want anyone else to use it. // --pSpool->cActive; if( pSpool->Status & SPOOL_STATUS_PENDING_DELETION && !pSpool->cActive ){ // // Someone called Close while we were active. Since we are now // going to close it, don't let anyone else initiate a close by // marking SPOOL_STATUS_CLOSE. // pSpool->Status |= SPOOL_STATUS_CLOSE; pSpool->Status &= ~SPOOL_STATUS_PENDING_DELETION; bCallClosePrinter = TRUE; } vLeaveSem(); if( bCallClosePrinter ){ ClosePrinterWorker( pSpool ); } } /******************************************************************** OpenPrinter worker functions. ********************************************************************/ BOOL OpenPrinterW( LPWSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTS pDefault ) { HANDLE hPrinter; PSPOOL pSpool = NULL; DWORD dwError; // // Pre-initialize the out parameter, so that *phPrinter is NULL // on failure. This fixes Borland Paradox 7. // try { *phPrinter = NULL; } except( EXCEPTION_EXECUTE_HANDLER ){ SetLastError(TranslateExceptionCode(GetExceptionCode())); return FALSE; } pSpool = AllocSpool(); if( !pSpool ){ goto Fail; } // // Copy DevMode, defaults. The printer name doesn't change. // if( !UpdatePrinterDefaults( pSpool, pPrinterName, pDefault )){ goto Fail; } // // Update the access, since this is not set by UpdatePrinterDefaults. // if( pDefault ){ pSpool->Default.DesiredAccess = pDefault->DesiredAccess; } dwError = OpenPrinterRPC( pSpool ); if( dwError != ERROR_SUCCESS ){ SetLastError( dwError ); goto Fail; } // // We finally have a good pSpool. Only now update the output // handle. Since it was NULL initialized, this guarantees that // OpenPrinter returns *phPrinter NULL when it fails. // *phPrinter = pSpool; return TRUE; Fail: FreeSpool( pSpool ); return FALSE; } DWORD OpenPrinterRPC( PSPOOL pSpool ) /*++ Routine Description: Open the printer handle using information in the pSpool object. Arguments: pSpool - Printer handle to open. Internal state of pSpool updated. Return Value: ERROR_SUCCES - Succeed. Status code - Failed. --*/ { DEVMODE_CONTAINER DevModeContainer; HANDLE hPrinter = NULL; DWORD dwReturn; DWORD dwSize; SPLCLIENT_CONTAINER SplClientContainer; DevModeContainer.cbBuf = 0; DevModeContainer.pDevMode = NULL; SplClientContainer.Level = 2; SplClientContainer.ClientInfo.pClientInfo2 = NULL; RpcTryExcept { // // Construct the DevMode container. // if( bValidDevModeW( pSpool->Default.pDevMode )){ dwSize = pSpool->Default.pDevMode->dmSize + pSpool->Default.pDevMode->dmDriverExtra; DevModeContainer.cbBuf = pSpool->Default.pDevMode->dmSize + pSpool->Default.pDevMode->dmDriverExtra; DevModeContainer.pDevMode = (LPBYTE)pSpool->Default.pDevMode; } // // If the call is made from within the spooler, we also retrieve the // server side hPrinter. This will help avoid unnecessary RPC. We cant, // however, avoid RPC in this case since the spooler may need a client side // handle to pass to other functions or the driver. // if (bLoadedBySpooler) { if (SplClientContainer.ClientInfo.pClientInfo2 = (LPSPLCLIENT_INFO_2) AllocSplMem(sizeof(SPLCLIENT_INFO_2))) { SplClientContainer.ClientInfo.pClientInfo2->hSplPrinter = 0; dwReturn = RpcSplOpenPrinter( (LPTSTR)pSpool->pszPrinter, &hPrinter, pSpool->Default.pDatatype, &DevModeContainer, pSpool->Default.DesiredAccess, &SplClientContainer ); } else { SetLastError(ERROR_NOT_ENOUGH_MEMORY); dwReturn = ERROR_NOT_ENOUGH_MEMORY; } } else { dwReturn = RpcOpenPrinter( (LPTSTR)pSpool->pszPrinter, &hPrinter, pSpool->Default.pDatatype, &DevModeContainer, pSpool->Default.DesiredAccess ); } } RpcExcept(I_RpcExceptionFilter(RpcExceptionCode())) { dwReturn = TranslateExceptionCode( RpcExceptionCode() ); } RpcEndExcept if( dwReturn == ERROR_SUCCESS ){ vEnterSem(); // // hPrinter gets adopted by pSpool->hPrinter. // pSpool->hPrinter = hPrinter; if (bLoadedBySpooler) { pSpool->hSplPrinter = (HANDLE) SplClientContainer.ClientInfo.pClientInfo2->hSplPrinter; } else { pSpool->hSplPrinter = NULL; } vLeaveSem(); } if (SplClientContainer.ClientInfo.pClientInfo2) { FreeSplMem(SplClientContainer.ClientInfo.pClientInfo2); } return dwReturn; } /******************************************************************** ClosePrinter worker functions. ********************************************************************/ BOOL ClosePrinter( HANDLE hPrinter ) { PSPOOL pSpool = (PSPOOL)hPrinter; switch( eProtectHandle( hPrinter, TRUE )){ case kProtectHandleInvalid: return FALSE; case kProtectHandlePendingDeletion: return TRUE; default: break; } // // Note, there isn't a corresponding vUnprotectHandle, but that's ok // since we're deleting the handle. // return ClosePrinterWorker( pSpool ); } // // A simpler way to have a central function for closing spool file handles so we // don't have to reproduce code constantly. // VOID CloseSpoolFileHandles( PSPOOL pSpool ) { if ( pSpool->hSpoolFile != INVALID_HANDLE_VALUE ) { CloseHandle( pSpool->hSpoolFile ); pSpool->hSpoolFile = INVALID_HANDLE_VALUE; } if (pSpool->hFile != INVALID_HANDLE_VALUE) { CloseHandle(pSpool->hFile); pSpool->hFile = INVALID_HANDLE_VALUE; } } BOOL ClosePrinterWorker( PSPOOL pSpool ) { BOOL bReturnValue; FlushBuffer(pSpool, NULL); if (pSpool->Status & SPOOL_STATUS_ADDJOB) ScheduleJobWorker( pSpool, pSpool->JobId ); vEnterSem(); if( pSpool->pNotify ){ // // There is a notification; disassociate it from // pSpool, since we are about to free it. // pSpool->pNotify->pSpool = NULL; } vLeaveSem(); // // Close any open file handles, we do this before the RPC closeprinter // to allow the closeprinter on the other side a chance to delete the spool // files if they still exist. // CloseSpoolFileHandles( pSpool ); bReturnValue = ClosePrinterRPC( pSpool, FALSE ); FreeSpool( pSpool ); return bReturnValue; } BOOL ClosePrinterRPC( IN PSPOOL pSpool, IN BOOL bRevalidate ) /*++ Routine Description: Close down all RPC/network handles related to the pSpool object. Must be called outside the critical section. This function also is called handle revalidation in which case we don't want to close the event handle on the client side. Arguments: pSpool - Spooler handle to shut down. bRevalidate - If TRUE, this is being called as a result of a handle revalidation. Return Value: TRUE - Success FALSE - Failed, LastError set. --*/ { BOOL bRetval = FALSE; HANDLE hPrinterRPC = NULL; vEnterSem(); hPrinterRPC = pSpool->hPrinter; if ( hPrinterRPC ) { pSpool->hPrinter = NULL; FindClosePrinterChangeNotificationWorker( pSpool->pNotify, hPrinterRPC, bRevalidate ); vLeaveSem(); bRetval = ClosePrinterContextHandle( hPrinterRPC ); } else { vLeaveSem(); SetLastError( ERROR_INVALID_HANDLE ); } return bRetval; } BOOL ClosePrinterContextHandle( HANDLE hPrinterRPC ) /*++ Routine Description: Close a printer context handle. Arguments: hPrinterRPC - RPC context handle to close. Return Value: TRUE - Success FALSE - Failure; LastError set --*/ { BOOL bReturnValue; DWORD Status; if( !hPrinterRPC ){ return FALSE; } RpcTryExcept { if( Status = RpcClosePrinter( &hPrinterRPC )) { SetLastError( Status ); bReturnValue = FALSE; } else { bReturnValue = TRUE; } } RpcExcept(I_RpcExceptionFilter(RpcExceptionCode())) { SetLastError(TranslateExceptionCode(RpcExceptionCode())); bReturnValue = FALSE; } RpcEndExcept // // If we failed for some reason, then RpcClosePrinter did not // zero out the context handle. Destroy it here. // if( hPrinterRPC ){ RpcSmDestroyClientContext( &hPrinterRPC ); } return bReturnValue; } /******************************************************************** Constructor and destructor of pSpool. ********************************************************************/ PSPOOL AllocSpool( VOID ) /*++ Routine Description: Allocate a spool handle. Client should set pSpool->hPrinter when it is acquired. Arguments: Return Value: pSpool - allocated handle. NULL - failed. --*/ { PSPOOL pSpool = AllocSplMem(sizeof(SPOOL)); if( pSpool ){ InterlockedIncrement( &gcClientHandle ); pSpool->signature = SP_SIGNATURE; pSpool->hFile = INVALID_HANDLE_VALUE; pSpool->hSpoolFile = INVALID_HANDLE_VALUE; #ifdef DBG_TRACE_HANDLE { ULONG Hash; // // Add to linked list. // vEnterSem(); pSpool->pNext = gpFirstSpool; gpFirstSpool = pSpool; vLeaveSem(); #if i386 // // Capture backtrace. // RtlCaptureStackBackTrace( 1, COUNTOF( pSpool->apvBackTrace ), pSpool->apvBackTrace, &Hash ); #endif } #endif } return pSpool; } VOID FreeSpool( PSPOOL pSpool ) { if( !pSpool ){ return; } InterlockedDecrement( &gcClientHandle ); if (pSpool->pBuffer != NULL ) { if (!VirtualFree(pSpool->pBuffer, 0, MEM_RELEASE)) { DBGMSG(DBG_WARNING, ("ClosePrinter VirtualFree Failed %x\n", GetLastError())); } DBGMSG(DBG_TRACE, ("Closeprinter cWritePrinters %d cFlushBuffers %d\n", pSpool->cWritePrinters, pSpool->cFlushBuffers)); } FreeSplStr( pSpool->pszPrinter ); FreeSplMem( pSpool->Default.pDevMode ); FreeSplMem( pSpool->Default.pDatatype ); FreeSplMem( pSpool->pDoceventFilter); CloseSpoolFileHandles( pSpool ); #ifdef DBG_TRACE_HANDLE { // // Free from linked list. // PSPOOL *ppSpool; vEnterSem(); for( ppSpool = &gpFirstSpool; *ppSpool; ppSpool = &(*ppSpool)->pNext ){ if( *ppSpool == pSpool ){ break; } } if( *ppSpool ){ *ppSpool = pSpool->pNext; } else { DBGMSG( DBG_WARN, ( "pSpool %x not found on linked list\n", pSpool )); } vLeaveSem(); } #endif FreeSplMem( pSpool ); } /******************************************************************** Utility functions. ********************************************************************/ BOOL RevalidateHandle( PSPOOL pSpool ) /*++ Routine Description: Revalidates a pSpool with a new RPC handle. This allows the spooler to be restarted yet allow the handle to remain valid. This should only be called when a call fails with ERROR_INVALID_HANDLE. We can only save simple state information (pDefaults) from OpenPrinter and ResetPrinter. If a user spooling and the context handle is lost, there is no hope of recovering the spool file state, since the spooler probably died before it could flush its buffers. We should not encounter any infinite loops when the server goes down, since the initial call will timeout with an RPC rather than invalid handle code. Note: If the printer is renamed, the context handle remains valid, but revalidation will fail, since we store the old printer name. Arguments: pSpool - Printer handle to revalidate. Return Value: TRUE - Success FALSE - Failed. --*/ { DWORD dwError; HANDLE hPrinter; // // Close the existing handle. We can't shouldn't just destroy the client // context since an api may return ERROR_INVALID_HANDLE even though // RPC context handle is fine (a handle downstream went bad). // ClosePrinterRPC( pSpool, TRUE ); // // Reopen the printer handle with current defaults. // dwError = OpenPrinterRPC( pSpool ); if( dwError ){ SetLastError( dwError ); return FALSE; } return TRUE; } BOOL UpdatePrinterDefaults( IN OUT PSPOOL pSpool, IN LPCTSTR pszPrinter, OPTIONAL IN PPRINTER_DEFAULTS pDefault OPTIONAL ) /*++ Routine Description: Update the pSpool to the new defaults in pDefault, EXCEPT for pDefault->DesiredAccess. Since this attempts to read and update pSpool, we enter the critical section and revalidate the pSpool. Arguments: pSpool - Spooler handle to update. pszPrinter - New printer name. pDefault - New defaults. Return Value: TRUE - Success FALSE - Failure. --*/ { BOOL bReturnValue = FALSE; vEnterSem(); if( !UpdateString( pszPrinter, &pSpool->pszPrinter )){ goto DoneExitSem; } if( pDefault ){ // // Update the Datatype. // if( !UpdateString( pDefault->pDatatype, &pSpool->Default.pDatatype )){ goto DoneExitSem; } // // Update the DevMode. // if( bValidDevModeW( pDefault->pDevMode )){ DWORD dwSize; PDEVMODE pDevModeNew; dwSize = pDefault->pDevMode->dmSize + pDefault->pDevMode->dmDriverExtra; pDevModeNew = AllocSplMem( dwSize ); if( !pDevModeNew ){ goto DoneExitSem; } CopyMemory( pDevModeNew, pDefault->pDevMode, dwSize ); FreeSplMem( pSpool->Default.pDevMode ); pSpool->Default.pDevMode = pDevModeNew; } } bReturnValue = TRUE; DoneExitSem: vLeaveSem(); return bReturnValue; }