1045 lines
25 KiB
C
1045 lines
25 KiB
C
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// MacPrint - Windows NT Print Server for Macintosh Clients
|
|
// Copyright (c) Microsoft Corp., 1991, 1992, 1993
|
|
//
|
|
// macpsq.c - Macintosh Print Service queue service routines
|
|
//
|
|
// Author: Frank D. Byrum
|
|
// adapted from MacPrint from LAN Manager Services for Macintosh
|
|
//
|
|
// DESCRIPTION:
|
|
// This module provides the routines to manage an NT Printer Object
|
|
// on an AppleTalk network. A QueueServiceThread is started for
|
|
// each NT Printer Object that is to be shared on the AppleTalk
|
|
// network. This thread publishes an NBP name for the printer,
|
|
// listens for connection requests from Macintosh clients, and
|
|
// handles the communication between the Macintosh and the NT
|
|
// Print Spooler.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <windows.h>
|
|
#include <winsvc.h>
|
|
#include <macps.h>
|
|
#include <macpsmsg.h>
|
|
#include <debug.h>
|
|
|
|
extern HANDLE DbgSpoolFile;
|
|
extern PQR pqrHead;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// QueueServiceThread() - Thread routine to service an NT Printer Object
|
|
//
|
|
// DESCRIPTION:
|
|
// This routine fields all AppleTalk PAP requests and service all
|
|
// events associtated with each job.
|
|
//
|
|
// pqr ===> points to the Print Queue record for the printer to
|
|
// be serviced.
|
|
//
|
|
// On exit from this routine, the queue is shut down and all resources
|
|
// associated with the queue are freed.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void
|
|
QueueServiceThread(
|
|
PQR pqr
|
|
)
|
|
{
|
|
PQR * ppQr;
|
|
PJR pjr;
|
|
|
|
|
|
DBGPRINT(("Enter QueueServiceThread for %ws\n", pqr->pPrinterName));
|
|
if (CreateListenerSocket(pqr) != NO_ERROR)
|
|
{
|
|
DBGPRINT(("ERROR: failed to create session listener.\n"));
|
|
pqr->ExitThread = TRUE;
|
|
}
|
|
else
|
|
{
|
|
ReportEvent(hEventLog,
|
|
EVENTLOG_INFORMATION_TYPE,
|
|
EVENT_CATEGORY_ADMIN,
|
|
EVENT_PRINTER_REGISTERED,
|
|
NULL,
|
|
1,
|
|
0,
|
|
&(pqr->pPrinterName),
|
|
NULL);
|
|
}
|
|
|
|
// service jobs until told to exit
|
|
while (!pqr->ExitThread)
|
|
{
|
|
//
|
|
// service PAP events. HandleNextPAPEvent will wait for up to 2
|
|
// seconds for a read or open to occur on this queue. If one
|
|
// happens, pjr is the job record the event happened on. If
|
|
// pjr is NULL, then no event was found.
|
|
//
|
|
HandleNextPAPEvent(pqr);
|
|
|
|
//
|
|
// check for service stop
|
|
//
|
|
if (WaitForSingleObject(hevStopRequested, 0) == WAIT_OBJECT_0)
|
|
{
|
|
DBGPRINT(("%ws thread gets service stop request\n", pqr->pPrinterName));
|
|
pqr->ExitThread = TRUE;
|
|
break;
|
|
}
|
|
} // end while !ExitThread
|
|
|
|
DBGPRINT(("%ws received signal to die\n", pqr->pPrinterName));
|
|
|
|
// Remove all outstanding pending jobs
|
|
DBGPRINT(("%ws removing pending jobs\n", pqr->pPrinterName));
|
|
|
|
while ((pjr = pqr->PendingJobs) != NULL)
|
|
{
|
|
RemoveJob(pjr);
|
|
}
|
|
|
|
|
|
// close the listener
|
|
DBGPRINT(("%ws closing listener socket\n", pqr->pPrinterName));
|
|
if (pqr->sListener != INVALID_SOCKET)
|
|
{
|
|
closesocket(pqr->sListener);
|
|
|
|
// report printer removed
|
|
DBGPRINT(("%ws reporting printer removed\n", pqr->pPrinterName));
|
|
ReportEvent(hEventLog,
|
|
EVENTLOG_INFORMATION_TYPE,
|
|
EVENT_CATEGORY_ADMIN,
|
|
EVENT_PRINTER_DEREGISTERED,
|
|
NULL,
|
|
1,
|
|
0,
|
|
&(pqr->pPrinterName),
|
|
NULL);
|
|
}
|
|
|
|
|
|
// remove ourselves from the queue list
|
|
DBGPRINT(("queue thread waiting for the queue list mutex\n"));
|
|
WaitForSingleObject(mutexQueueList, INFINITE);
|
|
DBGPRINT(("queue thread removing self from queue\n"));
|
|
|
|
for (ppQr = &pqrHead; ; ppQr = &(*ppQr)->pNext)
|
|
{
|
|
if (*ppQr == pqr)
|
|
{
|
|
*ppQr = pqr->pNext;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DBGPRINT(("queue thread releasing list mutex\n"));
|
|
ReleaseMutex(mutexQueueList);
|
|
|
|
|
|
// close the handle to the thread that was opened on create
|
|
CloseHandle(pqr->hThread);
|
|
|
|
|
|
DBGPRINT(("closed thread for %ws\n", pqr->pPrinterName));
|
|
|
|
|
|
// all of this memory allocated in PScriptQInit()
|
|
DBGPRINT(("%ws freeing memory\n", pqr->pPrinterName));
|
|
|
|
if (pqr->pPrinterName != NULL)
|
|
{
|
|
LocalFree(pqr->pPrinterName);
|
|
}
|
|
|
|
if (pqr->pMacPrinterName != NULL)
|
|
{
|
|
LocalFree(pqr->pMacPrinterName);
|
|
}
|
|
|
|
if (pqr->pDriverName != NULL)
|
|
{
|
|
LocalFree(pqr->pDriverName);
|
|
}
|
|
|
|
if (pqr->IdleStatus != NULL)
|
|
{
|
|
LocalFree(pqr->IdleStatus);
|
|
}
|
|
|
|
if (pqr->SpoolingStatus != NULL)
|
|
{
|
|
LocalFree(pqr->SpoolingStatus);
|
|
}
|
|
|
|
if (pqr->pPortName != NULL)
|
|
{
|
|
LocalFree(pqr->pPortName);
|
|
}
|
|
|
|
if (pqr->pDataType != NULL)
|
|
{
|
|
LocalFree(pqr->pDataType);
|
|
}
|
|
|
|
if (pqr->fonts != NULL)
|
|
{
|
|
LocalFree(pqr->fonts);
|
|
}
|
|
|
|
LocalFree(pqr);
|
|
|
|
DBGPRINT(("leaving QueueServiceThread\n"));
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// HandleNewJob() - Handle the open of a print job from a Macintosh
|
|
//
|
|
// DESCRIPTION:
|
|
// This routine does the necessary processing to handle the open
|
|
// of a PAP connection from a Macintosh.
|
|
//
|
|
// If this routine is unable to complete the processesing necessary
|
|
// to open a job, the job is cancelled, the job data structures are
|
|
// cleaned up.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
DWORD
|
|
HandleNewJob(
|
|
PQR pqr
|
|
)
|
|
{
|
|
PJR pjr = NULL;
|
|
DOC_INFO_1 diJobInfo;
|
|
PRINTER_DEFAULTS pdDefaults;
|
|
DWORD dwError = NO_ERROR;
|
|
BOOL boolOK = TRUE;
|
|
DWORD rc = NO_ERROR;
|
|
PJOB_INFO_2 pji2GetJob=NULL;
|
|
DWORD dwNeeded;
|
|
int fNonBlocking;
|
|
|
|
DBGPRINT(("enter HandleNewJob()\n"));
|
|
|
|
do
|
|
{
|
|
// allocate a job structure
|
|
if ((rc = CreateNewJob(pqr)) != NO_ERROR)
|
|
{
|
|
DBGPRINT(("FAIL - cannot create a new job structure\n"));
|
|
break;
|
|
}
|
|
|
|
pjr = pqr->PendingJobs;
|
|
|
|
// accept the connection
|
|
if ((pjr->sJob = accept(pqr->sListener, NULL, NULL)) == INVALID_SOCKET)
|
|
{
|
|
rc = GetLastError();
|
|
DBGPRINT(("accept() fails with %d\n", rc));
|
|
break;
|
|
}
|
|
|
|
// make the socket non-blocking
|
|
fNonBlocking = 1;
|
|
if (ioctlsocket(pjr->sJob, FIONBIO, &fNonBlocking) == SOCKET_ERROR)
|
|
{
|
|
rc = GetLastError();
|
|
DBGPRINT(("ioctlsocket(FIONBIO) fails with %d\n", rc));
|
|
break;
|
|
}
|
|
|
|
// initialize an NT print job
|
|
pdDefaults.pDatatype = pqr->pDataType;
|
|
pdDefaults.pDevMode = NULL;
|
|
pdDefaults.DesiredAccess = PRINTER_ACCESS_USE;
|
|
|
|
if (!OpenPrinter(pqr->pPrinterName, &pjr->hPrinter, &pdDefaults))
|
|
{
|
|
rc = GetLastError();
|
|
DBGPRINT(("OpenPrinter() fails with %d\n"));
|
|
pjr->hPrinter = INVALID_HANDLE_VALUE;
|
|
break;
|
|
}
|
|
|
|
diJobInfo.pDocName = NULL;
|
|
diJobInfo.pOutputFile = NULL;
|
|
diJobInfo.pDatatype = pqr->pDataType;
|
|
|
|
pjr->dwJobId = StartDocPrinter(pjr->hPrinter, 1, (LPBYTE) &diJobInfo);
|
|
if (pjr->dwJobId == 0)
|
|
{
|
|
rc = GetLastError();
|
|
DBGPRINT(("StartDocPrinter() fails with %d\n", rc));
|
|
break;
|
|
}
|
|
|
|
#if DBG_SPOOL_LOCALLY
|
|
if (DbgSpoolFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
DbgSpoolFile = CreateFile( L"e:\\tmp\\injob.ps",
|
|
GENERIC_READ|GENERIC_WRITE,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_TEMPORARY,
|
|
NULL );
|
|
}
|
|
#endif
|
|
|
|
|
|
//
|
|
// set pParameters field of the jobinfo to a unique string that our
|
|
// monitor can identify, so that it can know if the job came from a Mac.
|
|
//
|
|
|
|
dwNeeded = 1024;
|
|
while (1)
|
|
{
|
|
pji2GetJob = LocalAlloc( LMEM_FIXED, dwNeeded );
|
|
if (pji2GetJob == NULL)
|
|
{
|
|
DBGPRINT(("HandleNewJob: alloc for %d bytes failed\n", dwNeeded));
|
|
rc = ERROR_INSUFFICIENT_BUFFER;
|
|
break;
|
|
}
|
|
|
|
rc = 0;
|
|
if (!GetJob( pjr->hPrinter, pjr->dwJobId, 2,
|
|
(LPBYTE)pji2GetJob, dwNeeded, &dwNeeded ))
|
|
{
|
|
rc = GetLastError();
|
|
}
|
|
|
|
if ( rc == ERROR_INSUFFICIENT_BUFFER )
|
|
{
|
|
LocalFree(pji2GetJob);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (rc != 0)
|
|
{
|
|
DBGPRINT(("HandleNewJob: GetJob failed, rc=%d\n", rc));
|
|
break;
|
|
}
|
|
|
|
pji2GetJob->pParameters = LFILTERCONTROL;
|
|
pji2GetJob->Position = JOB_POSITION_UNSPECIFIED;
|
|
|
|
SetJob( pjr->hPrinter,pjr->dwJobId, 2, (LPBYTE)pji2GetJob, 0 );
|
|
|
|
LocalFree(pji2GetJob);
|
|
|
|
pjr->FirstWrite = TRUE;
|
|
|
|
// prime for a read
|
|
if (setsockopt(pjr->sJob,
|
|
SOL_APPLETALK,
|
|
SO_PAP_PRIME_READ,
|
|
pjr->bufPool[pjr->bufIndx].Buffer,
|
|
PAP_DEFAULT_BUFFER) == SOCKET_ERROR)
|
|
{
|
|
DBGPRINT(("setsockopt(SO_PAP_PRIME_READ) fails with %d\n", GetLastError()));
|
|
rc = GetLastError();
|
|
break;
|
|
}
|
|
} while (FALSE);
|
|
|
|
if ((rc != NO_ERROR) && (NULL != pjr))
|
|
{
|
|
RemoveJob(pjr);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// HandleRead() - Handle a read event from a Macintosh print job
|
|
//
|
|
// DESCRIPTION:
|
|
// This routine does the necessary processing to handle a read
|
|
// on a PAP connection from a Macintosh.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
DWORD
|
|
HandleRead(
|
|
PJR pjr
|
|
)
|
|
{
|
|
DWORD rc = NO_ERROR;
|
|
DWORD dwParseError = NO_ERROR;
|
|
PQR pqr = pjr->job_pQr;
|
|
WSABUF wsaBuf;
|
|
int iRecvFlags = 0;
|
|
DWORD dwBytesRead;
|
|
BOOL fRemoveJob = FALSE;
|
|
#if DBG
|
|
int CheckPoint = 0;
|
|
#endif
|
|
|
|
DBGPRINT(("enter HandleRead()\n"));
|
|
|
|
do
|
|
{
|
|
// get the data. recv() will return the negative count of
|
|
// bytes read if EOM is not set. SOCKET_ERROR is -1.
|
|
|
|
wsaBuf.len = pjr->dwFlowQuantum * PAP_QUANTUM_SIZE;
|
|
wsaBuf.buf = pjr->bufPool[pjr->bufIndx].Buffer;
|
|
|
|
if (WSARecv(pjr->sJob,
|
|
&wsaBuf,
|
|
1,
|
|
&pjr->cbRead,
|
|
&iRecvFlags,
|
|
NULL,
|
|
NULL) == SOCKET_ERROR)
|
|
{
|
|
DBGPRINT(("CheckPoint = %d\n", CheckPoint = 1));
|
|
rc = GetLastError();
|
|
DBGPRINT(("recv() fails with %d, removing job\n", rc));
|
|
if (rc == WSAEDISCON)
|
|
rc = NO_ERROR;
|
|
RemoveJob(pjr);
|
|
break;
|
|
}
|
|
|
|
// if this is flagged EOM, echo the EOM and ignore any error
|
|
// (disconnect will show when we try to prime for a read)
|
|
|
|
pjr->EOFRecvd = FALSE;
|
|
if (iRecvFlags != MSG_PARTIAL)
|
|
{
|
|
rc = TellClient(pjr, TRUE, NULL, 0);
|
|
pjr->EOFRecvd = TRUE;
|
|
pjr->EOFRecvdAt = GetTickCount();
|
|
}
|
|
|
|
DBGPRINT(("%ws: Read (%d%s)\n", pqr->pPrinterName,
|
|
pjr->cbRead, pjr->EOFRecvd ? ", EOF" : ""));
|
|
|
|
// deal with the pending buffer if there is one
|
|
pjr->DataBuffer = pjr->bufPool[pjr->bufIndx].Buffer;
|
|
pjr->XferLen = pjr->cbRead;
|
|
if (pjr->PendingLen)
|
|
{
|
|
DBGPRINT(("USING PENDING BUFFER\n"));
|
|
pjr->DataBuffer -= pjr->PendingLen;
|
|
pjr->XferLen += pjr->PendingLen;
|
|
pjr->PendingLen = 0;
|
|
}
|
|
|
|
// setup buffers for next read
|
|
pjr->bufIndx ^= 1;
|
|
|
|
// prime for the next read if we haven't disconnected
|
|
if (rc == NO_ERROR)
|
|
{
|
|
DBGPRINT(("priming for another read\n"));
|
|
if (setsockopt(pjr->sJob,
|
|
SOL_APPLETALK,
|
|
SO_PAP_PRIME_READ,
|
|
pjr->bufPool[pjr->bufIndx].Buffer,
|
|
PAP_DEFAULT_BUFFER) == SOCKET_ERROR)
|
|
{
|
|
rc = GetLastError();
|
|
DBGPRINT(("setsockopt() fails with %d\n", rc));
|
|
|
|
//
|
|
// this call could fail if the client has disconnected. Therefore,
|
|
// we parse the data we have received first, then return this
|
|
// error code.
|
|
//
|
|
}
|
|
}
|
|
|
|
// parse this data.
|
|
switch (dwParseError = PSParse(pjr, pjr->DataBuffer, pjr->XferLen))
|
|
{
|
|
case NO_ERROR:
|
|
break;
|
|
|
|
case ERROR_NOT_SUPPORTED:
|
|
//
|
|
// job from a downlevel client
|
|
//
|
|
DBGPRINT(("aborting a downlevel driver job\n"));
|
|
ReportEvent(hEventLog,
|
|
EVENTLOG_WARNING_TYPE,
|
|
EVENT_CATEGORY_ADMIN,
|
|
EVENT_DOWNLEVEL_DRIVER,
|
|
NULL,
|
|
0,
|
|
0,
|
|
NULL,
|
|
NULL);
|
|
DBGPRINT(("CheckPoint = %d\n", CheckPoint = 2));
|
|
fRemoveJob = TRUE;
|
|
break;
|
|
|
|
case ERROR_INVALID_PARAMETER:
|
|
//
|
|
// PostScript DSC error.
|
|
//
|
|
DBGPRINT(("ERROR on PSParse(). Aborting job\n"));
|
|
ReportEvent(hEventLog,
|
|
EVENTLOG_WARNING_TYPE,
|
|
EVENT_CATEGORY_USAGE,
|
|
EVENT_DSC_SYNTAX_ERROR,
|
|
NULL,
|
|
1,
|
|
0,
|
|
(LPCWSTR *)(&pjr->pszUser),
|
|
NULL);
|
|
DBGPRINT(("CheckPoint = %d\n", CheckPoint = 3));
|
|
fRemoveJob = TRUE;
|
|
break;
|
|
|
|
case WSAEINVAL:
|
|
//
|
|
// TellClient got a disconnect
|
|
//
|
|
DBGPRINT(("CheckPoint = %d\n", CheckPoint = 4));
|
|
DBGPRINT(("PSParse returns WSAEINVAL, RemoveJob for disconnect\n"));
|
|
fRemoveJob = TRUE;
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// some other error - report unknown error
|
|
// and remove job
|
|
//
|
|
DBGPRINT(("CheckPoint = %d\n", CheckPoint = 5));
|
|
DBGPRINT(("PSParse returns error %d\n", dwParseError));
|
|
ReportWin32Error(dwParseError);
|
|
fRemoveJob = TRUE;
|
|
}
|
|
|
|
// rc is the return code for TellClient. If it is an error, we
|
|
// have a disconnect and need to return it. If it's not, psparse
|
|
// could have gotten a disconnect and we need to return that
|
|
if (rc != NO_ERROR || (fRemoveJob == TRUE))
|
|
{
|
|
DBGPRINT(("HandleRead: rc = %d, fRemoveJob = %d, so removejob\n",rc,fRemoveJob));
|
|
RemoveJob(pjr);
|
|
rc = NO_ERROR;
|
|
}
|
|
} while (FALSE);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CreateNewJob() - Initialize a job data structure
|
|
//
|
|
// DESCRIPTION:
|
|
// This routine allocates, initializes and links a job data structure to the
|
|
// job chain for a queue.
|
|
//
|
|
// if this fails (due to lack of memory), the returned value is NULL.
|
|
// Otherwise, it is a pointer to a job structure.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
DWORD CreateNewJob(PQR pqr)
|
|
{
|
|
|
|
PJR pjr = NULL;
|
|
DWORD rc = NO_ERROR;
|
|
|
|
DBGPRINT(("enter CreateNewJob(%ws)\n", pqr->pPrinterName));
|
|
|
|
do
|
|
{
|
|
// allocate a job structure
|
|
if ((pjr = (PJR)LocalAlloc(LPTR, sizeof(JOB_RECORD))) == NULL)
|
|
{
|
|
//
|
|
// log an error and return
|
|
//
|
|
rc = GetLastError();
|
|
DBGPRINT(("LocalAlloc(pjr) fails with %d\n", rc));
|
|
break;
|
|
}
|
|
|
|
// initialize job structure
|
|
pjr->job_pQr = pqr;
|
|
pjr->NextJob = NULL;
|
|
pjr->dwFlags = JOB_FLAG_NULL;
|
|
pjr->hPrinter = INVALID_HANDLE_VALUE;
|
|
pjr->dwJobId = 0;
|
|
pjr->sJob = INVALID_SOCKET;
|
|
pjr->hicFontFamily = INVALID_HANDLE_VALUE;
|
|
pjr->hicFontFace = INVALID_HANDLE_VALUE;
|
|
pjr->dwFlowQuantum = 8;
|
|
pjr->XferLen = 0;
|
|
pjr->DataBuffer = NULL;
|
|
pjr->bufPool = (PBR)(pjr->buffer);
|
|
pjr->bufIndx = 0;
|
|
pjr->cbRead = 0;
|
|
pjr->PendingLen = 0;
|
|
pjr->psJobState = psStandardJob;
|
|
pjr->JSState = JSWrite;
|
|
pjr->SavedJSState = JSWrite;
|
|
pjr->InProgress = NOTHING;
|
|
pjr->InBinaryOp = 0;
|
|
#if DBG
|
|
pjr->PapEventCount = 1;
|
|
#endif
|
|
pjr->JSKeyWord[0] = 0;
|
|
|
|
// get an information context for font family query
|
|
if ((pjr->hicFontFamily = CreateIC(pqr->pDriverName,
|
|
pqr->pPrinterName,
|
|
pqr->pPortName,
|
|
NULL)) == NULL)
|
|
{
|
|
rc = GetLastError();
|
|
DBGPRINT(("CreateIC(hicFontFamily) fails with %d\n", rc));
|
|
break;
|
|
}
|
|
|
|
// get an information context for font face query
|
|
if ((pjr->hicFontFace = CreateIC(pqr->pDriverName,
|
|
pqr->pPrinterName,
|
|
pqr->pPortName,
|
|
NULL)) == NULL)
|
|
{
|
|
rc = GetLastError();
|
|
DBGPRINT(("CreateIC(hicFontFace) fails with %d\n", rc));
|
|
break;
|
|
}
|
|
|
|
// if this is first job, bump thread priority and change our status
|
|
if (pqr->PendingJobs == NULL)
|
|
{
|
|
DBGPRINT(("first job on queue, bumping thread priority\n"));
|
|
SetThreadPriority(pqr->hThread, THREAD_PRIORITY_ABOVE_NORMAL);
|
|
|
|
// Change our status from idle to spooling
|
|
DBGPRINT(("setting status to %s\n", pqr->IdleStatus));
|
|
if ((setsockopt(pqr->sListener,
|
|
SOL_APPLETALK,
|
|
SO_PAP_SET_SERVER_STATUS,
|
|
pqr->SpoolingStatus,
|
|
strlen(pqr->SpoolingStatus))) == SOCKET_ERROR)
|
|
{
|
|
rc = GetLastError();
|
|
DBGPRINT(("setsockopt(status) fails with %d\n", rc));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Add the new job to the list of pending jobs for this print queue.
|
|
pjr->NextJob = pqr->PendingJobs;
|
|
pqr->PendingJobs = pjr;
|
|
} while (FALSE);
|
|
|
|
if (rc != NO_ERROR)
|
|
{
|
|
if (pjr != NULL)
|
|
{
|
|
if ((pjr->hicFontFamily != NULL) && (pjr->hicFontFamily != INVALID_HANDLE_VALUE))
|
|
{
|
|
DeleteDC(pjr->hicFontFamily);
|
|
}
|
|
|
|
if ((pjr->hicFontFace != NULL) && (pjr->hicFontFace != INVALID_HANDLE_VALUE))
|
|
{
|
|
DeleteDC(pjr->hicFontFace);
|
|
}
|
|
|
|
LocalFree(pjr);
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// RemoveJob() - Close a job and clean up the job list
|
|
//
|
|
// DESCRIPTION:
|
|
// This routine examines the state of a job and cleans up appropriately.
|
|
// It then unlinks the job structure from the job list and frees it.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void
|
|
RemoveJob(
|
|
PJR pjr
|
|
)
|
|
{
|
|
PJR * ppjob;
|
|
char psEOF = '\04';
|
|
DWORD cbWritten;
|
|
PQR pqr = pjr->job_pQr;
|
|
|
|
DBGPRINT(("enter RemoveJob(%ws)\n", pqr->pPrinterName));
|
|
|
|
// find the job in the pending list
|
|
ppjob = &pqr->PendingJobs;
|
|
while (*ppjob != NULL && *ppjob != pjr)
|
|
ppjob = &(*ppjob)->NextJob;
|
|
|
|
// remove it from the list
|
|
*ppjob = pjr->NextJob;
|
|
|
|
// clean up the socket
|
|
if (pjr->sJob != INVALID_SOCKET)
|
|
{
|
|
DBGPRINT(("closing socket\n"));
|
|
closesocket(pjr->sJob);
|
|
}
|
|
|
|
// clean up information contexts
|
|
if (pjr->hicFontFamily != NULL)
|
|
{
|
|
DeleteDC(pjr->hicFontFamily);
|
|
}
|
|
|
|
if (pjr->hicFontFace != NULL)
|
|
{
|
|
DeleteDC(pjr->hicFontFace);
|
|
}
|
|
|
|
// end the NT print job and close the printer
|
|
if (pjr->hPrinter != INVALID_HANDLE_VALUE)
|
|
{
|
|
if (pqr->ExitThread)
|
|
{
|
|
// we are aborting, so delete the job
|
|
if (!SetJob(pjr->hPrinter, pjr->dwJobId, 0, NULL, JOB_CONTROL_CANCEL))
|
|
{
|
|
DBGPRINT(("ERROR: unable to cancel print job on service stop, rc=%d\n", GetLastError()));
|
|
}
|
|
}
|
|
|
|
// Do not write anything if we have not written anything yet !!!
|
|
if (!pjr->FirstWrite && !wcscmp(pqr->pDataType, MACPS_DATATYPE_RAW))
|
|
{
|
|
WritePrinter(pjr->hPrinter,
|
|
&psEOF,
|
|
1,
|
|
&cbWritten);
|
|
}
|
|
|
|
EndDocPrinter(pjr->hPrinter);
|
|
|
|
#if DBG_SPOOL_LOCALLY
|
|
CloseHandle(DbgSpoolFile);
|
|
DbgSpoolFile = INVALID_HANDLE_VALUE;
|
|
#endif
|
|
|
|
ClosePrinter(pjr->hPrinter);
|
|
}
|
|
|
|
// if all the jobs in this queue handled, drop back to normal priority
|
|
if (pqr->PendingJobs == NULL)
|
|
{
|
|
DBGPRINT(("last job removed, dropping thread priority\n"));
|
|
SetThreadPriority(pqr->hThread, THREAD_PRIORITY_NORMAL);
|
|
|
|
// change the status from spooling to idle
|
|
DBGPRINT(("setting status to %s\n", pqr->IdleStatus));
|
|
setsockopt(pqr->sListener,
|
|
SOL_APPLETALK,
|
|
SO_PAP_SET_SERVER_STATUS,
|
|
pqr->IdleStatus,
|
|
strlen(pqr->IdleStatus));
|
|
}
|
|
|
|
// free the job structure
|
|
LocalFree(pjr);
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// HandleNextPAPEvent() - Wait for a PAP event
|
|
//
|
|
// DESCRIPTION:
|
|
// This routine waits for a service stop request or an Open or Read to
|
|
// complete on an outstanding job. In the event of an Open or Read
|
|
// event, the routine finds the job that the event completed for and
|
|
// returns a pointer to that job.
|
|
//
|
|
// In the case of a service stop event, the return value is NULL
|
|
//
|
|
// NOTES:
|
|
//
|
|
// Finding the job that corresponds to the event is tricky. In the
|
|
// case of the open event it is simple as only one job ever has an
|
|
// open pending. However, for reads, most jobs will have reads
|
|
// pending simultaneously.
|
|
//
|
|
// To find a job with a completed read, we depend on three things.
|
|
// First, all reads are done so that they will trigger a single
|
|
// NT Event. When this event is signalled, we start looking for
|
|
// completed reads. Second, when a read completes it changes a
|
|
// status code that is stored on a per job basis, so it's possible
|
|
// to walk a list to find reads that have completed. Third, we
|
|
// need to be careful about when we reset the event. The race
|
|
// condition to avoid is between walking the list and reseting
|
|
// the event. If there are reads outstanding, a read at the beginning
|
|
// of the list could complete before we finish walking the list.
|
|
// To avoid this, we only reset the event when no reads are outstanding
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void
|
|
HandleNextPAPEvent(
|
|
PQR pqr
|
|
)
|
|
{
|
|
DWORD rc = NO_ERROR;
|
|
DWORD dwIndex;
|
|
PJR pjr, pjrNext, pjrOrgFirst;
|
|
fd_set readfds;
|
|
fd_set exceptfds;
|
|
struct timeval timeout;
|
|
int cEvents;
|
|
|
|
do
|
|
{
|
|
//
|
|
// check to see if any OTI-jobs need to be timed out
|
|
// this is a hack to work-around the Apple's OTI bug where the Mac client fails to
|
|
// send the ConnectionClose to us after it has sent EOF (because it crashes!). To
|
|
// avoid the job staying in our spooler forever, we force the connection closed if
|
|
// we haven't heard from the mac for 60 seconds after it sends an EOF
|
|
//
|
|
pjr = pqr->PendingJobs;
|
|
while(pjr != NULL)
|
|
{
|
|
pjrNext = pjr->NextJob;
|
|
|
|
if (pjr->EOFRecvd && EXECUTE_OTI_HACK(pjr->EOFRecvdAt))
|
|
{
|
|
DBGPRINT(("%ws must be OTI user ! closing the connection on behalf of client!\n",pjr->pszUser));
|
|
RemoveJob(pjr);
|
|
}
|
|
|
|
pjr = pjrNext;
|
|
}
|
|
|
|
// setup socket list with all pending jobs and listener socket
|
|
FD_ZERO(&readfds);
|
|
FD_ZERO(&exceptfds);
|
|
FD_SET(pqr->sListener, &readfds);
|
|
|
|
for (dwIndex = 1, pjr = pqr->PendingJobs;
|
|
(dwIndex < FD_SETSIZE) && (pjr != NULL);
|
|
dwIndex++, pjr = pjr->NextJob)
|
|
{
|
|
FD_SET(pjr->sJob, &readfds);
|
|
FD_SET(pjr->sJob, &exceptfds);
|
|
}
|
|
|
|
// wait for up to 2 seconds for a set of sockets to be ready
|
|
timeout.tv_sec = 2;
|
|
timeout.tv_usec = 0;
|
|
|
|
|
|
if ((cEvents = select(0, &readfds, NULL, &exceptfds, &timeout)) == SOCKET_ERROR)
|
|
{
|
|
rc = GetLastError();
|
|
DBGPRINT(("select() fails with %d: CLOSING DOWN QUEUE\n", rc));
|
|
pqr->ExitThread = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (cEvents == 0)
|
|
{
|
|
// timeout, done
|
|
break;
|
|
}
|
|
|
|
// handle a new connection if there is one
|
|
if (FD_ISSET(pqr->sListener, &readfds))
|
|
{
|
|
if ((rc = HandleNewJob(pqr)) != NO_ERROR)
|
|
{
|
|
DBGPRINT(("ERROR - could not open new job - CLOSING DOWN QUEUE\n"));
|
|
pqr->ExitThread = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pjr = pqr->PendingJobs;
|
|
pjrOrgFirst = NULL;
|
|
|
|
// since every pjr that succeeds on select goes to the tail of the list, make
|
|
// sure we have a way of getting out of this loop! pjrOrgFirst is the way
|
|
while(pjr != NULL && pjr != pjrOrgFirst)
|
|
{
|
|
pjrNext = pjr->NextJob;
|
|
|
|
if (FD_ISSET(pjr->sJob, &exceptfds))
|
|
{
|
|
DBGPRINT(("job for user %ws ends\n", pjr->pszUser));
|
|
RemoveJob(pjr);
|
|
}
|
|
|
|
else if (FD_ISSET(pjr->sJob, &readfds))
|
|
{
|
|
// mark the first pjr that's going to be moved to the tail
|
|
if (pjrOrgFirst == NULL)
|
|
{
|
|
pjrOrgFirst = pjr;
|
|
}
|
|
|
|
// Move this job to the end of the queue
|
|
MoveJobAtEnd(pqr, pjr);
|
|
|
|
// HandleRead() will remove pjr if a disconnect happens
|
|
HandleRead(pjr);
|
|
}
|
|
|
|
pjr = pjrNext;
|
|
}
|
|
|
|
rc = NO_ERROR;
|
|
} while (FALSE);
|
|
|
|
if (rc != NO_ERROR)
|
|
{
|
|
ReportWin32Error(rc);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
** MoveJobAtEnd - Move this job to end of queue.
|
|
**
|
|
** This is to ensure TRUE round robin scheduling of jobs within a queue.
|
|
** Since we always start at head of queue at GetNextPAPEvent, we need to
|
|
** do this for any job which got service. The way we achieve this is as
|
|
** follows: Ji will be pushed to the end of the queue.
|
|
**
|
|
** Before the change:
|
|
**
|
|
** Q -> J1 -> J2 -> ... -> Ji -> Jj -> ... -> Jn -> NULL
|
|
**
|
|
** After the change:
|
|
**
|
|
** Q -> J1 -> J2 -> ... -> Jj -> ... -> Jn -> Ji -> NULL
|
|
**
|
|
** Note that in the boundary conditions of n = 1 OR i = n, it is a NOP i.e.
|
|
** its unlinked and linked back - BIG DEAL !!
|
|
*/
|
|
void
|
|
MoveJobAtEnd(PQR pqr, PJR pjr)
|
|
{
|
|
PJR * ppjob = &pqr->PendingJobs;
|
|
BOOL found = FALSE;
|
|
|
|
for (ppjob = &pqr->PendingJobs;
|
|
*ppjob != NULL;
|
|
ppjob = &(*ppjob)->NextJob)
|
|
{
|
|
if (*ppjob == pjr)
|
|
{
|
|
/* Unlink it from its current position */
|
|
*ppjob = pjr->NextJob;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (NOTHING;
|
|
*ppjob != NULL;
|
|
ppjob = &(*ppjob)->NextJob)
|
|
{
|
|
NOTHING;
|
|
}
|
|
|
|
/* Link job at tail */
|
|
*ppjob = pjr;
|
|
|
|
// and terminate the tail
|
|
pjr->NextJob = NULL;
|
|
}
|
|
|
|
|
|
void
|
|
ReportWin32Error (
|
|
DWORD dwError
|
|
)
|
|
{
|
|
LPWSTR pszError = NULL;
|
|
DWORD rc = NO_ERROR;
|
|
|
|
DBGPRINT(("enter ReportWin32Error(%d)\n", dwError));
|
|
|
|
do
|
|
{
|
|
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS |
|
|
FORMAT_MESSAGE_FROM_SYSTEM,
|
|
NULL,
|
|
dwError,
|
|
0,
|
|
(LPWSTR)(&pszError),
|
|
128,
|
|
NULL) == 0)
|
|
{
|
|
// Report unknown error
|
|
ReportEvent(
|
|
hEventLog,
|
|
EVENTLOG_WARNING_TYPE,
|
|
EVENT_CATEGORY_INTERNAL,
|
|
EVENT_MESSAGE_NOT_FOUND,
|
|
NULL,
|
|
0,
|
|
sizeof(DWORD),
|
|
NULL,
|
|
&dwError);
|
|
|
|
}
|
|
else
|
|
{
|
|
// report known error
|
|
ReportEvent(hEventLog,
|
|
EVENTLOG_WARNING_TYPE,
|
|
EVENT_CATEGORY_INTERNAL,
|
|
EVENT_SYSTEM_ERROR,
|
|
NULL,
|
|
1,
|
|
0,
|
|
&pszError,
|
|
NULL);
|
|
}
|
|
} while (FALSE);
|
|
|
|
if (NULL != pszError)
|
|
{
|
|
LocalFree(pszError);
|
|
}
|
|
}
|