/*++ Copyright (c) 1990-1994 Microsoft Corporation All rights reserved Module Name: Change.c Abstract: Handles the wait for printer change new code. FindFirstPrinterChangeNotification FindNextPrinterChangeNotification FindClosePrinterChangeNotification Author: Albert Ting (AlbertT) 20-Jan-94 Environment: User Mode -Win32 Revision History: --*/ #include "precomp.h" #pragma hdrstop #include "client.h" #include #include // // Globals // PNOTIFY pNotifyHead; extern DWORD ClientHandleCount; INT UnicodeToAnsiString( LPWSTR pUnicode, LPSTR pAnsi, DWORD StringLength); VOID CopyAnsiDevModeFromUnicodeDevMode( LPDEVMODEA pANSIDevMode, LPDEVMODEW pUnicodeDevMode); // // Prototypes: // PNOTIFY WPCWaitAdd( PSPOOL pSpool); VOID WPCWaitDelete( PNOTIFY pNotify); DWORD WPCSimulateThreadProc(PVOID pvParm); HANDLE FindFirstPrinterChangeNotificationWorker( HANDLE hPrinter, DWORD fdwFilter, DWORD fdwOptions, PPRINTER_NOTIFY_OPTIONS pPrinterNotifyOptions ) /*++ Routine Description: The FindFirstChangeNotification function creates a change notification handle and sets up initial change notification filter conditions. A wait on a notification handle succeeds when a change matching the filter conditions occurs in the specified directory or subtree. Arguments: hPrinter - Handle to a printer the user wishes to watch. fdwFlags - Specifies the filter conditions that satisfy a change notification wait. This parameter can be one or more of the following values: Value Meaning PRINTER_CHANGE_PRINTER Notify changes to a printer. PRINTER_CHANGE_JOB Notify changes to a job. PRINTER_CHANGE_FORM Notify changes to a form. PRINTER_CHANGE_PORT Notify changes to a port. PRINTER_CHANGE_PRINT_PROCESSOR Notify changes to a print processor. PRINTER_CHANGE_PRINTER_DRIVER Notify changes to a printer driver. fdwOptions - Specifies options to FFPCN. PRINTER_NOTIFY_OPTION_SIM_FFPCN Trying to simulate a FFPCN using a WPC PRINTER_NOTIFY_OPTION_SIM_FFPCN_ACTIVE Simulation of FFPCN active PRINTER_NOTIFY_OPTION_SIM_FFPCN_CLOSE Waiting thread must close pSpool PRINTER_NOTIFY_OPTION_SIM_WPC Trying to simulate a WPC using a FFPCN Return Value: Not -1 - Returns a find first handle that can be used in a subsequent call to FindNextFile or FindClose. -1 - The operation failed. Extended error status is available using GetLastError. --*/ { PSPOOL pSpool = (PSPOOL)hPrinter; DWORD dwError; PNOTIFY pNotify; HANDLE hEvent = INVALID_HANDLE_VALUE; // // Nothing to watch. // if (!fdwFilter && !pPrinterNotifyOptions) { SetLastError(ERROR_INVALID_PARAMETER); return INVALID_HANDLE_VALUE; } vEnterSem(); if (eProtectHandle( hPrinter, FALSE )) { vLeaveSem(); return INVALID_HANDLE_VALUE; } // // First check if we are already waiting. // // This is broken if we are daytona client->528 server and // the app does a FFPCN, FCPCN, FFPCN on the same printer, // and the WPC hasn't returned yet. We really can't fix this // because there's no way to interrupt the WPC. // // The only thing we can do is check if it's simulating and waiting // to close. If so, then we can reuse it. // if (pSpool->pNotify) { if ((pSpool->pNotify->fdwOptions & PRINTER_NOTIFY_OPTION_SIM_FFPCN_CLOSE) && (fdwFilter == pSpool->pNotify->fdwFlags)) { // // No longer closing, since we are using it. // pSpool->pNotify->fdwOptions &= ~PRINTER_NOTIFY_OPTION_SIM_FFPCN_CLOSE; hEvent = pSpool->pNotify->hEvent; goto Done; } SetLastError(ERROR_ALREADY_WAITING); goto Done; } // // Create and add our pSpool to the linked list of wait requests. // pNotify = WPCWaitAdd(pSpool); if (!pNotify) { goto Done; } vLeaveSem(); pNotify->fdwOptions = fdwOptions; pNotify->fdwFlags = fdwFilter; RpcTryExcept { if (dwError = RpcClientFindFirstPrinterChangeNotification( pSpool->hPrinter, fdwFilter, fdwOptions, GetCurrentProcessId(), (PRPC_V2_NOTIFY_OPTIONS)pPrinterNotifyOptions, (LPDWORD)&pNotify->hEvent)) { hEvent = INVALID_HANDLE_VALUE; } else { hEvent = pNotify->hEvent; } } RpcExcept(I_RpcExceptionFilter(RpcExceptionCode())) { dwError = TranslateExceptionCode(RpcExceptionCode()); hEvent = INVALID_HANDLE_VALUE; } RpcEndExcept vEnterSem(); // // If we encounter a 528 server, then we need to simulate the // FFPCN using a WPC. If the client originally wanted a WPC anyway, // then fail out and let the client thread do the blocking. // if (dwError == RPC_S_PROCNUM_OUT_OF_RANGE && !(fdwOptions & PRINTER_NOTIFY_OPTION_SIM_WPC)) { DWORD dwIDThread; HANDLE hThread; // // If pPrinterNotifyOptions is set, we can't handle it. // just fail. // if (pPrinterNotifyOptions) { WPCWaitDelete(pNotify); SetLastError( ERROR_CALL_NOT_IMPLEMENTED ); hEvent = INVALID_HANDLE_VALUE; goto Done; } hEvent = pNotify->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if( !hEvent ){ hEvent = INVALID_HANDLE_VALUE; } else { // // We're simulating a FFPCN using WPC now. // pNotify->fdwOptions |= PRINTER_NOTIFY_OPTION_SIM_FFPCN | PRINTER_NOTIFY_OPTION_SIM_FFPCN_ACTIVE; // // Also mark that we failed trying to use FFPCN so we never // try again on this handle. // pSpool->fdwFlags |= SPOOL_FLAG_FFPCN_FAILED; hThread = CreateThread(NULL, INITIAL_STACK_COMMIT, WPCSimulateThreadProc, pNotify, 0, &dwIDThread); if (hThread) { CloseHandle(hThread); } else { CloseHandle(hEvent); hEvent = INVALID_HANDLE_VALUE; dwError = GetLastError(); pNotify->fdwOptions &= ~PRINTER_NOTIFY_OPTION_SIM_FFPCN_ACTIVE; } } } // // On error case, remove us from the list of waiting handles // if( hEvent == INVALID_HANDLE_VALUE ){ WPCWaitDelete(pNotify); SetLastError(dwError); } Done: vUnprotectHandle( hPrinter ); vLeaveSem(); return hEvent; } HANDLE WINAPI FindFirstPrinterChangeNotification( HANDLE hPrinter, DWORD fdwFilter, DWORD fdwOptions, PPRINTER_NOTIFY_OPTIONS pPrinterNotifyOptions) { if (fdwOptions) { SetLastError(ERROR_INVALID_PARAMETER); return INVALID_HANDLE_VALUE; } return FindFirstPrinterChangeNotificationWorker(hPrinter, fdwFilter, fdwOptions, pPrinterNotifyOptions); } BOOL WINAPI FindNextPrinterChangeNotification( HANDLE hChange, LPDWORD pdwChange, LPVOID pPrinterNotifyOptions, LPVOID* ppInfo) { BOOL bReturnValue; DWORD dwError; HANDLE hPrinter; PSPOOL pSpool; PNOTIFY pNotify; PVOID pvIgnore; DWORD dwIgnore; DWORD fdwFlags; if (!pdwChange) { pdwChange = &dwIgnore; } if (ppInfo) { *ppInfo = NULL; fdwFlags = PRINTER_NOTIFY_NEXT_INFO; } else { ppInfo = &pvIgnore; fdwFlags = 0; } vEnterSem(); pNotify = WPCWaitFind(hChange); // // Either the handle is bad, or it doesn't have a wait or we have // if (!pNotify || !pNotify->pSpool || pNotify->bHandleInvalid) { SetLastError(ERROR_INVALID_HANDLE); goto FailExitWaitList; } pSpool = pNotify->pSpool; hPrinter = pSpool->hPrinter; // // If we are simulating FFPCN using WPC, we must use the thread. // if (pNotify->fdwOptions & PRINTER_NOTIFY_OPTION_SIM_FFPCN) { HANDLE hThread; DWORD dwIDThread; ResetEvent(pNotify->hEvent); // // Get the last return status. Client should not call FNCPN // until the WPC sets the event, so this value should be // initialized. // *pdwChange = pNotify->dwReturn; // // If the thread is active anyway, then don't try to create another // Best we can do at this point. // if (pNotify->fdwOptions & PRINTER_NOTIFY_OPTION_SIM_FFPCN_ACTIVE) { vLeaveSem(); return TRUE; } // // We're simulating a FFPCN using WPC now. // pNotify->fdwOptions |= PRINTER_NOTIFY_OPTION_SIM_FFPCN_ACTIVE; hThread = CreateThread(NULL, INITIAL_STACK_COMMIT, WPCSimulateThreadProc, pNotify, 0, &dwIDThread); if (hThread) { CloseHandle(hThread); vLeaveSem(); return TRUE; } pNotify->fdwOptions &= ~PRINTER_NOTIFY_OPTION_SIM_FFPCN_ACTIVE; goto FailExitWaitList; } vLeaveSem(); RpcTryExcept { if (dwError = RpcFindNextPrinterChangeNotification( hPrinter, fdwFlags, pdwChange, (PRPC_V2_NOTIFY_OPTIONS)pPrinterNotifyOptions, (PRPC_V2_NOTIFY_INFO*)ppInfo)) { SetLastError(dwError); bReturnValue = FALSE; } else { bReturnValue = TRUE; } } RpcExcept(I_RpcExceptionFilter(RpcExceptionCode())) { SetLastError(TranslateExceptionCode(RpcExceptionCode())); bReturnValue = FALSE; } RpcEndExcept // // Thunk from W to A if necessary. // if (pSpool->Status & SPOOL_STATUS_ANSI && bReturnValue && fdwFlags & PRINTER_NOTIFY_NEXT_INFO && *ppInfo) { DWORD i; PPRINTER_NOTIFY_INFO_DATA pData; for(pData = (*(PPRINTER_NOTIFY_INFO*)ppInfo)->aData, i=(*(PPRINTER_NOTIFY_INFO*)ppInfo)->Count; i; pData++, i--) { switch ((BYTE)pData->Reserved) { case TABLE_STRING: UnicodeToAnsiString( pData->NotifyData.Data.pBuf, pData->NotifyData.Data.pBuf, (pData->NotifyData.Data.cbBuf/sizeof(WCHAR)) -1); break; case TABLE_DEVMODE: if (pData->NotifyData.Data.cbBuf) { CopyAnsiDevModeFromUnicodeDevMode( pData->NotifyData.Data.pBuf, pData->NotifyData.Data.pBuf); } break; } } } return bReturnValue; FailExitWaitList: vLeaveSem(); return FALSE; } BOOL WINAPI FindClosePrinterChangeNotification( HANDLE hChange) { PNOTIFY pNotify; HANDLE hPrinterRPC = NULL; DWORD dwError; vEnterSem(); pNotify = WPCWaitFind(hChange); if (!pNotify) { SetLastError(ERROR_INVALID_HANDLE); vLeaveSem(); return FALSE; } if (pNotify->pSpool) hPrinterRPC = pNotify->pSpool->hPrinter; dwError = FindClosePrinterChangeNotificationWorker(pNotify, hPrinterRPC, FALSE); vLeaveSem(); if (dwError) { SetLastError(dwError); return FALSE; } return TRUE; } DWORD FindClosePrinterChangeNotificationWorker( IN PNOTIFY pNotify, IN HANDLE hPrinterRPC, IN BOOL bRevalidate ) /*++ Routine Description: Does the actual FindClose work. Arguments: pNotify - notification to close hPrinterRPC - handle to printer to close bRevalidate - If this is TRUE, we were called to revalidate the handle rather than close it. Return Value: TRUE - success FALSE - fail Note: assume in critical section --*/ { DWORD dwError; PSPOOL pSpool; if (!pNotify) { return ERROR_INVALID_HANDLE; } // // Detach the pNotify and pSpool objects completely. Only if we are not // revalidating. // pSpool = pNotify->pSpool; if (!bRevalidate) { if (pSpool) { pSpool->pNotify = NULL; pSpool->fdwFlags = 0; } pNotify->pSpool = NULL; } // // If we are simulating a FFPCN with a WPC, then let the WPC thread // free up the data structure or clean it up if the thread is done. // if (pNotify->fdwOptions & PRINTER_NOTIFY_OPTION_SIM_FFPCN) { if (pNotify->fdwOptions & PRINTER_NOTIFY_OPTION_SIM_FFPCN_ACTIVE) { pNotify->fdwOptions |= PRINTER_NOTIFY_OPTION_SIM_FFPCN_CLOSE; } else { // // The thread has exited, so we need to do the cleanup. // Set the event to release any waiting threads. Since the caller // does not necessarily know how to handle the failure on // WaitForMultipleObjects, // SetEvent(pNotify->hEvent); if (!bRevalidate) { CloseHandle(pNotify->hEvent); WPCWaitDelete(pNotify); } else { pNotify->bHandleInvalid = TRUE; } } return ERROR_SUCCESS; } SetEvent(pNotify->hEvent); // // If we are not revalidating, we can close the handle for real, otherwise // we just want to set the handle to invalid. // if (!bRevalidate) { CloseHandle(pNotify->hEvent); WPCWaitDelete(pNotify); } else { pNotify->bHandleInvalid = TRUE; } if (!hPrinterRPC) return ERROR_SUCCESS; vLeaveSem(); RpcTryExcept { dwError = RpcFindClosePrinterChangeNotification(hPrinterRPC); } RpcExcept(I_RpcExceptionFilter(RpcExceptionCode())) { dwError = TranslateExceptionCode(RpcExceptionCode()); } RpcEndExcept vEnterSem(); return dwError; } // // WPC Wait structures // Currently implemented as a linked list // PNOTIFY WPCWaitAdd( PSPOOL pSpool) /*++ Routine Description: Allocates a wait structure on the client side, which allows the user program to refer to events only. Arguments: pSpool - object to add to list Return Value: NOTE: Asssumes already in critical section --*/ { PNOTIFY pNotify; pNotify = AllocSplMem(sizeof(NOTIFY)); if (!pNotify) return NULL; pNotify->pSpool = pSpool; pSpool->pNotify = pNotify; pNotify->pNext = pNotifyHead; pNotifyHead = pNotify; return pNotify; } VOID WPCWaitDelete( PNOTIFY pNotify) /*++ Routine Description: Find wait structure based on hEvent. Arguments: pNotify - delete it Return Value: VOID NOTE: Asssumes already in critical section --*/ { PNOTIFY pNotifyTmp; if (!pNotify) return; // // Check head case first // if (pNotifyHead == pNotify) { pNotifyHead = pNotify->pNext; } else { // // Scan list to delete // for(pNotifyTmp = pNotifyHead; pNotifyTmp; pNotifyTmp = pNotifyTmp->pNext) { if (pNotify == pNotifyTmp->pNext) { pNotifyTmp->pNext = pNotify->pNext; break; } } // // If not found, return without freeing // if (!pNotifyTmp) return; } // // Remove link from pSpool to us... but only if we've found // ourselves on the linked list (could have been removed by // ClosePrinter in a different thread). // if (pNotify->pSpool) { pNotify->pSpool->pNotify = NULL; } FreeSplMem(pNotify); return; } PNOTIFY WPCWaitFind( HANDLE hFind) /*++ Routine Description: Find wait structure based on hEvent. Arguments: hFind - Handle to event returned from FindFirstPrinterChangeNotification or hPrinter Return Value: pWait pointer, or NULL if not found NOTE: assumes already in critical section --*/ { PNOTIFY pNotify; for(pNotify = pNotifyHead; pNotify; pNotify=pNotify->pNext) { if (hFind == pNotify->hEvent) { return pNotify; } } return NULL; } DWORD WPCSimulateThreadProc( PVOID pvParm) /*++ Routine Description: This thread simulates the FFPCN when daytona apps run on daytona clients connected to 528 servers. Arguments: pvParm - pSpool Return Value: VOID Note: --*/ { PNOTIFY pNotify = (PNOTIFY)pvParm; pNotify->dwReturn = WaitForPrinterChange(pNotify->pSpool, pNotify->fdwFlags); vEnterSem(); pNotify->fdwOptions &= ~PRINTER_NOTIFY_OPTION_SIM_FFPCN_ACTIVE; // // !! POLICY !! // // How do we handle timeouts? // SetEvent(pNotify->hEvent); if (pNotify->fdwOptions & PRINTER_NOTIFY_OPTION_SIM_FFPCN_CLOSE) { CloseHandle(pNotify->hEvent); WPCWaitDelete(pNotify); } vLeaveSem(); // // We are no longer active; the FindClose must clean up for us. // return 0; } DWORD WaitForPrinterChange( HANDLE hPrinter, DWORD Flags ) { DWORD ReturnValue; PSPOOL pSpool = (PSPOOL)hPrinter; HANDLE hEvent; DWORD rc; if( eProtectHandle( hPrinter, FALSE )){ return(FALSE); } // // Try using FFPCN first, if we haven't failed on this printer before. // if (!(pSpool->fdwFlags & SPOOL_FLAG_FFPCN_FAILED)) { if (pSpool->fdwFlags & SPOOL_FLAG_LAZY_CLOSE) { vEnterSem(); if (pSpool->pNotify) hEvent = pSpool->pNotify->hEvent; vLeaveSem(); } else { hEvent = FindFirstPrinterChangeNotificationWorker( hPrinter, Flags, PRINTER_NOTIFY_OPTION_SIM_WPC, NULL); } if (hEvent != INVALID_HANDLE_VALUE) { // // Found notification, now wait for it. // rc = WaitForSingleObject(hEvent, PRINTER_CHANGE_TIMEOUT_VALUE); switch (rc) { case WAIT_TIMEOUT: ReturnValue = PRINTER_CHANGE_TIMEOUT; break; case WAIT_OBJECT_0: if (!FindNextPrinterChangeNotification( hEvent, &ReturnValue, 0, NULL)) { ReturnValue = 0; DBGMSG(DBG_WARNING, ("QueryPrinterChange failed %d\n", GetLastError())); } break; default: ReturnValue = 0; break; } // // !! Policy !! // // Do we want to close it? The app might just reopen it. // If we leave it open, it will get cleaned-up at ClosePrinter // time. We would need an api to clear out pending events. // pSpool->fdwFlags |= SPOOL_FLAG_LAZY_CLOSE; goto Done; } // // FFPCN failed. Only if entry not found (511 client) do // we try old WPC. Otherwise return here. // if (GetLastError() != RPC_S_PROCNUM_OUT_OF_RANGE) { ReturnValue = 0; goto Done; } pSpool->fdwFlags |= SPOOL_FLAG_FFPCN_FAILED; } RpcTryExcept { if (ReturnValue = RpcWaitForPrinterChange( pSpool->hPrinter, Flags, &Flags)) { SetLastError(ReturnValue); ReturnValue = 0; } else ReturnValue = Flags; } RpcExcept(I_RpcExceptionFilter(RpcExceptionCode())) { SetLastError(TranslateExceptionCode(RpcExceptionCode())); ReturnValue = 0; } RpcEndExcept Done: vUnprotectHandle( pSpool ); return ReturnValue; } BOOL WINAPI FreePrinterNotifyInfo( PPRINTER_NOTIFY_INFO pInfo) { DWORD i; PPRINTER_NOTIFY_INFO_DATA pData; if (!pInfo) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } for(pData = pInfo->aData, i=pInfo->Count; i; pData++, i--) { if ((BYTE)pData->Reserved != TABLE_DWORD && pData->NotifyData.Data.pBuf) { midl_user_free(pData->NotifyData.Data.pBuf); } } midl_user_free(pInfo); return TRUE; }