|
|
////////////////////////////////////////////////////////////////////////////////
//
// 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); } }
|