windows-nt/Source/XPSP1/NT/printscan/print/spooler/spoolss/client/change.c
2020-09-26 16:20:57 +08:00

1019 lines
21 KiB
C

/*++
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 <change.h>
#include <ntfytab.h>
//
// 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;
}