Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1998 lines
55 KiB

/*****************************************************************************\
* MODULE: ppjobs.c
*
* This module contains the print-job manipulating routines.
*
*
* Copyright (C) 1996-1997 Microsoft Corporation
* Copyright (C) 1996-1997 Hewlett Packard
*
* History:
* 07-Oct-1996 HWP-Guys Initiated port from win95 to winNT
*
\*****************************************************************************/
#include "precomp.h"
#include "priv.h"
BOOL
_ppprn_end_docprinter_async (
PCINETMONPORT pIniPort,
PJOBMAP pjmJob);
typedef struct _ADDJOB_INFO_2W {
LPWSTR pData;
DWORD JobId;
} ADDJOB_INFO_2W, *PADDJOB_INFO_2W, *LPADDJOB_INFO_2W;
DWORD
ppjob_GetOneSize (
DWORD dwLevel)
{
DWORD cbIdx;
switch (dwLevel) {
case PRINT_LEVEL_1:
cbIdx = sizeof(JOB_INFO_1);
break;
case PRINT_LEVEL_2:
cbIdx = sizeof(JOB_INFO_2);
break;
case PRINT_LEVEL_3:
cbIdx = sizeof(JOB_INFO_3);
break;
}
return cbIdx;
}
/*****************************************************************************\
* ppjob_IppPrtRsp (Local Routine)
*
* Retrieves a get response from the IPP server. Our (pjmJob) in the
* parameter list references a job-entry.
*
\*****************************************************************************/
BOOL CALLBACK ppjob_IppPrtRsp(
CAnyConnection *pConnection,
HINTERNET hJobReq,
PCINETMONPORT pIniPort,
PJOBMAP pjmJob)
{
HANDLE hIpp;
DWORD dwRet;
DWORD cbRd;
LPBYTE lpDta;
DWORD cbDta;
LPIPPRET_JOB lpRsp;
DWORD cbRsp;
BYTE bBuf[MAX_IPP_BUFFER];
BOOL bRet = FALSE;
if (hIpp = WebIppRcvOpen(IPP_RET_PRINTJOB)) {
while (TRUE) {
cbRd = 0;
if (pIniPort->ReadFile ( pConnection, hJobReq, (LPVOID)bBuf, sizeof(bBuf), &cbRd) && cbRd) {
dwRet = WebIppRcvData(hIpp, bBuf, cbRd, (LPBYTE*)&lpRsp, &cbRsp, &lpDta, &cbDta);
switch (dwRet) {
case WEBIPP_OK:
if (bRet = lpRsp->bRet) {
// Set the remote-job-id to the job-entry. This
// entry was added at the time the spool-job-file
// was created.
//
semEnterCrit();
pjmSetJobRemote(pjmJob, lpRsp->ji.ji2.JobId, lpRsp->ji.ipp.pJobUri);
semLeaveCrit();
} else {
// If the job failed to open on the server, then
// we will set the last-error from the server
// response.
//
SetLastError(lpRsp->dwLastError);
}
WebIppFreeMem(lpRsp);
goto EndPrtRsp;
case WEBIPP_MOREDATA:
// Need to do another read to fullfill our header-response.
//
break;
default:
DBG_MSG(DBG_LEV_ERROR, (TEXT("ppjob_IppPrtRsp : Receive Data Error")));
SetLastError(ERROR_INVALID_DATA);
goto EndPrtRsp;
}
} else {
goto EndPrtRsp;
}
}
EndPrtRsp:
WebIppRcvClose(hIpp);
} else {
SetLastError(ERROR_OUTOFMEMORY);
}
return bRet;
}
/*****************************************************************************\
* ppjob_GetJobSize (Local Routine)
*
* Returns the size necessary to hold the jobinfo.
*
\*****************************************************************************/
DWORD ppjob_GetJobSize(
LPJOB_INFO_2 pji2,
DWORD dwLevel)
{
DWORD cbSize;
switch (dwLevel) {
case PRINT_LEVEL_1:
cbSize = sizeof(JOB_INFO_1) +
utlStrSize(pji2->pPrinterName) +
utlStrSize(pji2->pMachineName) +
utlStrSize(pji2->pUserName) +
utlStrSize(pji2->pDocument) +
utlStrSize(pji2->pDatatype) +
utlStrSize(pji2->pStatus);
break;
case PRINT_LEVEL_2:
cbSize = sizeof(JOB_INFO_2) +
utlStrSize(pji2->pPrinterName) +
utlStrSize(pji2->pMachineName) +
utlStrSize(pji2->pUserName) +
utlStrSize(pji2->pDocument) +
utlStrSize(pji2->pNotifyName) +
utlStrSize(pji2->pDatatype) +
utlStrSize(pji2->pPrintProcessor) +
utlStrSize(pji2->pParameters) +
utlStrSize(pji2->pDriverName) +
utlStrSize(pji2->pStatus);
if (pji2->pDevMode)
cbSize += (pji2->pDevMode->dmSize + pji2->pDevMode->dmDriverExtra);
cbSize = (cbSize + sizeof(DWORD)-1) & ~(sizeof(DWORD)-1);
break;
case PRINT_LEVEL_3:
cbSize = sizeof(JOB_INFO_3);
break;
default:
cbSize = 0;
break;
}
return cbSize;
}
/*****************************************************************************\
* ppjob_CopyJob (Local Routine)
*
* Copies a job-info structure to another.
*
\*****************************************************************************/
LPBYTE ppjob_CopyJob(
LPBYTE pbJobDst,
DWORD dwLevel,
LPJOB_INFO_2 pji2Src,
LPBYTE pbEnd)
{
LPJOB_INFO_1 pji1Dst;
LPJOB_INFO_2 pji2Dst;
LPJOB_INFO_3 pji3Dst;
LPJOBMAP pjm;
LPDWORD pOffsets;
DWORD cbDM;
LPTSTR* lpszSrc;
LPTSTR aszSrc[(sizeof(JOB_INFO_2) / sizeof(LPTSTR))];
static DWORD s_JI1Offsets[] = {
offsetof(LPJOB_INFO_1, pPrinterName),
offsetof(LPJOB_INFO_1, pMachineName),
offsetof(LPJOB_INFO_1, pUserName),
offsetof(LPJOB_INFO_1, pDocument),
offsetof(LPJOB_INFO_1, pDatatype),
offsetof(LPJOB_INFO_1, pStatus),
0xFFFFFFFF
};
static DWORD s_JI2Offsets[] = {
offsetof(LPJOB_INFO_2, pPrinterName),
offsetof(LPJOB_INFO_2, pMachineName),
offsetof(LPJOB_INFO_2, pUserName),
offsetof(LPJOB_INFO_2, pDocument),
offsetof(LPJOB_INFO_2, pNotifyName),
offsetof(LPJOB_INFO_2, pDatatype),
offsetof(LPJOB_INFO_2, pPrintProcessor),
offsetof(LPJOB_INFO_2, pParameters),
offsetof(LPJOB_INFO_2, pDriverName),
offsetof(LPJOB_INFO_2, pDevMode),
offsetof(LPJOB_INFO_2, pStatus),
offsetof(LPJOB_INFO_2, pSecurityDescriptor),
0xFFFFFFFF
};
static DWORD s_JI3Offsets[]={0xFFFFFFFF};
// Set the start of the string-buffer.
//
ZeroMemory(aszSrc, sizeof(aszSrc));
lpszSrc = aszSrc;
// Process the appropriate structure.
//
switch (dwLevel) {
case PRINT_LEVEL_1:
pji1Dst = (LPJOB_INFO_1)pbJobDst;
pOffsets = s_JI1Offsets;
// Copy fixed values.
//
pji1Dst->JobId = pji2Src->JobId;
pji1Dst->Status = pji2Src->Status;
pji1Dst->Priority = pji2Src->Priority;
pji1Dst->Position = pji2Src->Position;
pji1Dst->TotalPages = pji2Src->TotalPages;
pji1Dst->PagesPrinted = pji2Src->PagesPrinted;
pji1Dst->Submitted = pji2Src->Submitted;
// Copy strings.
//
*lpszSrc++ = pji2Src->pPrinterName;
*lpszSrc++ = pji2Src->pMachineName;
*lpszSrc++ = pji2Src->pUserName;
*lpszSrc++ = pji2Src->pDocument;
*lpszSrc++ = pji2Src->pDatatype;
*lpszSrc++ = pji2Src->pStatus;
break;
case PRINT_LEVEL_2:
pji2Dst = (LPJOB_INFO_2)pbJobDst;
pOffsets = s_JI2Offsets;
// Copy fixed values.
//
pji2Dst->JobId = pji2Src->JobId;
pji2Dst->Status = pji2Src->Status;
pji2Dst->Priority = pji2Src->Priority;
pji2Dst->Position = pji2Src->Position;
pji2Dst->StartTime = pji2Src->StartTime;
pji2Dst->UntilTime = pji2Src->UntilTime;
pji2Dst->TotalPages = pji2Src->TotalPages;
pji2Dst->Size = pji2Src->Size;
pji2Dst->Submitted = pji2Src->Submitted;
pji2Dst->Time = pji2Src->Time;
pji2Dst->PagesPrinted = pji2Src->PagesPrinted;
pji2Dst->pSecurityDescriptor = NULL;
pji2Dst->pDevMode = NULL;
// Copy strings.
//
*lpszSrc++ = pji2Src->pPrinterName;
*lpszSrc++ = pji2Src->pMachineName;
*lpszSrc++ = pji2Src->pUserName;
*lpszSrc++ = pji2Src->pDocument;
*lpszSrc++ = pji2Src->pNotifyName;
*lpszSrc++ = pji2Src->pDatatype;
*lpszSrc++ = pji2Src->pPrintProcessor;
*lpszSrc++ = pji2Src->pParameters;
*lpszSrc++ = pji2Src->pDriverName;
*lpszSrc++ = NULL;
*lpszSrc++ = pji2Src->pStatus;
*lpszSrc++ = NULL;
if (pji2Src->pDevMode) {
cbDM = pji2Src->pDevMode->dmSize + pji2Src->pDevMode->dmDriverExtra;
pbEnd -= cbDM;
pbEnd = (LPBYTE)((UINT_PTR)pbEnd & ~((UINT_PTR)sizeof(UINT_PTR) - 1));
pji2Dst->pDevMode = (LPDEVMODE)pbEnd;
CopyMemory(pji2Dst->pDevMode, pji2Src->pDevMode, cbDM);
}
break;
case PRINT_LEVEL_3:
pji3Dst = (LPJOB_INFO_3)pbJobDst;
pOffsets = s_JI3Offsets;
pji3Dst->JobId = pji2Src->JobId;
break;
}
return utlPackStrings(aszSrc, (LPBYTE)pbJobDst, pOffsets, pbEnd);
}
BOOL ppjob_CalcAndCopyJob(
LPBYTE pbJob,
DWORD cbJob,
PDWORD pcbNeed,
PJOB_INFO_2 pji2,
DWORD dwLevel)
{
BOOL bRet = FALSE;
LPBYTE pbEnd;
// Fill in what we need.
//
*pcbNeed = ppjob_GetJobSize(pji2, dwLevel);
// If our buffer is big-enough, then
// proceed to fill in the info.
//
if (cbJob >= *pcbNeed) {
pbEnd = pbJob + cbJob;
ppjob_CopyJob(pbJob, dwLevel, pji2, pbEnd);
bRet = TRUE;
} else {
SetLastError (ERROR_INSUFFICIENT_BUFFER);
}
return bRet;
}
/*****************************************************************************\
* ppjob_IppEnuRsp (Local Callback Routine)
*
* Retrieves a get response from the IPP server. Our (lParam) in the
* parameter list references a LPPPJOB_ENUM pointer which we are to fill
* in from the enumeration.
*
\*****************************************************************************/
BOOL CALLBACK ppjob_IppEnuRsp(
CAnyConnection *pConnection,
HINTERNET hReq,
PCINETMONPORT pIniPort,
LPARAM lParam)
{
HANDLE hIpp;
DWORD dwRet;
DWORD cbRd;
LPBYTE pbEnd;
DWORD idx;
DWORD idx2;
LPBYTE lpDta;
DWORD cbDta;
LPIPPRET_ENUJOB lpRsp;
DWORD cbRsp;
LPPPJOB_ENUM pje;
LPIPPJI2 pji2;
LPJOBMAP pjm;
PDWORD pidJob;
DWORD cbSize;
PJOBMAP* pjmList;
BYTE bBuf[MAX_IPP_BUFFER];
BOOL bRet = FALSE;
time_t dwPrinterT0;
if (hIpp = WebIppRcvOpen(IPP_RET_ENUJOB)) {
while (TRUE) {
cbRd = 0;
if (pIniPort->ReadFile ( pConnection, hReq, (LPVOID)bBuf, sizeof(bBuf), &cbRd) && cbRd) {
dwRet = WebIppRcvData(hIpp, bBuf, cbRd, (LPBYTE*)&lpRsp, &cbRsp, &lpDta, &cbDta);
switch (dwRet) {
case WEBIPP_OK:
if (bRet = lpRsp->bRet) {
if (lpRsp->cItems && lpRsp->cbItems) {
semEnterCrit();
pjmList = pIniPort->GetPJMList();
pjmCleanRemoteFlag(pjmList);
pji2 = lpRsp->pItems;
// We go over the IPP response and put them into PJM list
// At the mean time, we convert the remote job ID to
// local job id and store them into the IPP response
// data structure.
//
for (idx = 0; idx < lpRsp->cItems; idx++) {
// Fixup the job-id to the local id we
// can deal with.
//
pidJob = & (pji2[idx].ji2.JobId);
if (pjm = pjmFind(pjmList, PJM_REMOTEID, *pidJob)) {
*pidJob = pjmJobId(pjm, PJM_LOCALID);
} else {
if (pjm = pjmAdd(pjmList, pIniPort, NULL, NULL))
pjmSetJobRemote(pjm, *pidJob, pji2[idx].ipp.pJobUri);
*pidJob = pjmJobId(pjm, PJM_LOCALID);
}
}
// Call our routine to clean our client-list
// of jobs. This will remove any entries
// that no longer exist on the server.
//
cbSize = sizeof(PPJOB_ENUM) + lpRsp->cbItems;
// Allocate storage for enumeration.
//
if (pje = (LPPPJOB_ENUM)memAlloc(cbSize)) {
dwPrinterT0 = pIniPort->GetPowerUpTime();
// This now containts the powerup time for the printer in
// our time
pje->cItems = lpRsp->cItems;
pje->cbSize = lpRsp->cbItems;
pji2 = lpRsp->pItems;
pbEnd = ((LPBYTE)pje->ji2) + pje->cbSize;
for (idx = 0; idx < lpRsp->cItems; idx++) {
pbEnd = ppjob_CopyJob((LPBYTE)&pje->ji2[idx],
2,
&pji2[idx].ji2,
pbEnd);
WebIppConvertSystemTime(&pje->ji2[idx].ji2.Submitted, dwPrinterT0);
}
pjmRemoveOldEntries(pjmList);
semLeaveCrit();
*((LPPPJOB_ENUM *)lParam) = pje;
} else {
SetLastError(ERROR_OUTOFMEMORY);
}
}
else {
//
// This is the case where the job count is 0 on the server
// We still need to allocate the structure so that the client
// enum-job function can merge the localjobs.
//
cbSize = sizeof(PPJOB_ENUM);
// Allocate storage for enumeration.
//
if (pje = (LPPPJOB_ENUM)memAlloc(cbSize)) {
pje->cItems = 0;
pje->cbSize = 0;
*((LPPPJOB_ENUM *)lParam) = pje;
} else {
SetLastError(ERROR_OUTOFMEMORY);
}
}
} else {
SetLastError(lpRsp->dwLastError);
}
WebIppFreeMem(lpRsp);
goto EndEnuRsp;
case WEBIPP_MOREDATA:
// Need to do another read to fullfill our header-response.
//
break;
default:
DBG_MSG(DBG_LEV_ERROR, (TEXT("ppjob_IppEnuRsp : Receive Data Error")));
SetLastError(ERROR_INVALID_DATA);
goto EndEnuRsp;
}
} else {
goto EndEnuRsp;
}
}
EndEnuRsp:
WebIppRcvClose(hIpp);
} else {
SetLastError(ERROR_OUTOFMEMORY);
}
return bRet;
}
/*****************************************************************************\
* ppjob_IppSetRsp (Local Callback Routine)
*
* Retrieves a SetJob response from the IPP server
*
\*****************************************************************************/
BOOL CALLBACK ppjob_IppSetRsp(
CAnyConnection *pConnection,
HINTERNET hReq,
PCINETMONPORT pIniPort,
LPARAM lParam)
{
HANDLE hIpp;
DWORD dwRet;
DWORD cbRd;
LPBYTE lpDta;
DWORD cbDta;
LPIPPRET_JOB lpRsp;
DWORD cbRsp;
BYTE bBuf[MAX_IPP_BUFFER];
BOOL bRet = FALSE;
if (hIpp = WebIppRcvOpen((WORD)(LPARAM)lParam)) {
while (TRUE) {
cbRd = 0;
if (pIniPort->ReadFile (pConnection, hReq, (LPVOID)bBuf, sizeof(bBuf), &cbRd) && cbRd) {
dwRet = WebIppRcvData(hIpp, bBuf, cbRd, (LPBYTE*)&lpRsp, &cbRsp, &lpDta, &cbDta);
switch (dwRet) {
case WEBIPP_OK:
if ((bRet = lpRsp->bRet) == FALSE)
SetLastError(lpRsp->dwLastError);
WebIppFreeMem(lpRsp);
goto EndSetRsp;
case WEBIPP_MOREDATA:
// Need to do another read to fullfill our header-response.
//
break;
default:
DBG_MSG(DBG_LEV_ERROR, (TEXT("ppjob_IppSetRsp : Receive Data Error")));
SetLastError(ERROR_INVALID_DATA);
goto EndSetRsp;
}
} else {
goto EndSetRsp;
}
}
EndSetRsp:
WebIppRcvClose(hIpp);
} else {
SetLastError(ERROR_OUTOFMEMORY);
}
return bRet;
}
/*****************************************************************************\
* ppjob_IppGetRsp (Local Callback Routine)
*
* Retrieves a get response from the IPP server. Our (lParam) in the
* parameter list references a JOB_INFO_2 pointer which we are to fill
* in from the call.
*
\*****************************************************************************/
BOOL CALLBACK ppjob_IppGetRsp(
CAnyConnection *pConnection,
HINTERNET hReq,
PCINETMONPORT pIniPort,
LPARAM lParam)
{
HANDLE hIpp;
DWORD dwRet;
DWORD cbRd;
LPBYTE pbEnd;
DWORD idx;
LPBYTE lpDta;
DWORD cbDta;
LPIPPRET_JOB lpRsp;
DWORD cbRsp;
LPJOB_INFO_2 pji2;
LPJOBMAP pjm;
DWORD cbSize;
BYTE bBuf[MAX_IPP_BUFFER];
PJOBMAP* pjmList;
BOOL bRet = FALSE;
if (hIpp = WebIppRcvOpen(IPP_RET_GETJOB)) {
while (TRUE) {
cbRd = 0;
if (pIniPort->ReadFile ( pConnection, hReq, (LPVOID)bBuf, sizeof(bBuf), &cbRd) && cbRd) {
dwRet = WebIppRcvData(hIpp, bBuf, cbRd, (LPBYTE*)&lpRsp, &cbRsp, &lpDta, &cbDta);
switch (dwRet) {
case WEBIPP_OK:
if (bRet = lpRsp->bRet) {
cbSize = ppjob_GetJobSize(&lpRsp->ji.ji2, 2);
// Allocate storage for enumeration.
//
if (pji2 = (LPJOB_INFO_2)memAlloc(cbSize)) {
pbEnd = ((LPBYTE)pji2) + cbSize;
ppjob_CopyJob((LPBYTE)pji2, 2, &lpRsp->ji.ji2, pbEnd);
semEnterCrit();
pjmList = pIniPort->GetPJMList();
// Fixup the job-id to the local id we
// can deal with.
//
if (pjm = pjmFind(pjmList, PJM_REMOTEID, pji2->JobId)) {
pji2->JobId = pjmJobId(pjm, PJM_LOCALID);
} else {
if (pjm = pjmAdd(pjmList, pIniPort, NULL, NULL))
pjmSetJobRemote(pjm, pji2->JobId, lpRsp->ji.ipp.pJobUri);
pji2->JobId = pjmJobId(pjm, PJM_LOCALID);
}
semLeaveCrit();
*((LPJOB_INFO_2 *)lParam) = pji2;
} else {
SetLastError(ERROR_OUTOFMEMORY);
}
} else {
SetLastError(lpRsp->dwLastError);
}
WebIppFreeMem(lpRsp);
goto EndGetRsp;
case WEBIPP_MOREDATA:
// Need to do another read to fullfill our header-response.
//
break;
default:
DBG_MSG(DBG_LEV_ERROR, (TEXT("ppjob_IppGetRsp - Err : Receive Data Error (dwRet=%d, LE=%d)"),
dwRet, WebIppGetError(hIpp)));
SetLastError(ERROR_INVALID_DATA);
goto EndGetRsp;
}
} else {
goto EndGetRsp;
}
}
EndGetRsp:
WebIppRcvClose(hIpp);
} else {
SetLastError(ERROR_OUTOFMEMORY);
}
return bRet;
}
/*****************************************************************************\
* ppjob_Set (Local Routine)
*
* Sets a job command in the queue.
*
\*****************************************************************************/
BOOL ppjob_Set(
PCINETMONPORT pIniPort,
DWORD idJob,
DWORD dwCmd)
{
PIPPREQ_SETJOB psj;
PJOBMAP pjm;
WORD wReq;
REQINFO ri;
DWORD dwRet;
LPBYTE lpIpp;
DWORD cbIpp;
PJOBMAP* pjmList;
BOOL bRet = FALSE;
// Make sure we have a JobMap entry which we can
// obtain the remote information.
//
pjmList = pIniPort->GetPJMList();
if (pjm = pjmFind(pjmList, PJM_LOCALID, idJob)) {
// If we're still spooling, then we haven't yet
// hit the server. Otherwise, we've performed the EndDoc()
// and the job is being processed remotely.
//
if (pjmChkState(pjm, PJM_SPOOLING)) {
switch (dwCmd) {
case JOB_CONTROL_CANCEL:
case JOB_CONTROL_DELETE:
pjmSetState(pjm, PJM_CANCEL);
//
// If the async thread is on, we let that thread to clean the job queue
//
if (!pjmChkState(pjm, PJM_ASYNCON))
{
//
// Otherwise, we delete the job here.
//
pjmClrState (pjm, PJM_SPOOLING);
}
break;
case JOB_CONTROL_PAUSE:
pjmSetState(pjm, PJM_PAUSE);
break;
case JOB_CONTROL_RESUME:
pjmClrState(pjm, PJM_PAUSE);
break;
case JOB_CONTROL_RESTART:
pjmUpdateLocalJobStatus (pjm, JOB_STATUS_RESTART);
if (!pjmChkState(pjm, PJM_ASYNCON))
{
_ppprn_end_docprinter_async (pIniPort, pjm);
}
break;
}
bRet = TRUE;
} else {
// Look through list to get local/remote job mappings.
//
psj = WebIppCreateSetJobReq(pjmJobId(pjm, PJM_REMOTEID),
dwCmd,
pIniPort->GetPortName());
if (psj) {
switch (dwCmd) {
case JOB_CONTROL_CANCEL:
case JOB_CONTROL_DELETE:
wReq = IPP_REQ_CANCELJOB;
break;
case JOB_CONTROL_PAUSE:
wReq = IPP_REQ_PAUSEJOB;
break;
case JOB_CONTROL_RESUME:
wReq = IPP_REQ_RESUMEJOB;
break;
case JOB_CONTROL_RESTART:
wReq = IPP_REQ_RESTARTJOB;
break;
default:
wReq = 0;
break;
}
// Convert the reqest to IPP, and perform the
// post.
//
ZeroMemory(&ri, sizeof(REQINFO));
ri.cpReq = CP_UTF8;
ri.idReq = wReq;
ri.fReq[0] = IPP_REQALL;
ri.fReq[1] = IPP_REQALL;
dwRet = WebIppSndData(wReq,
&ri,
(LPBYTE)psj,
psj->cbSize,
&lpIpp,
&cbIpp);
// The request-structure has been converted to IPP format,
// so it is ready to go to the server.
//
if (dwRet == WEBIPP_OK) {
bRet = pIniPort->SendReq(lpIpp,
cbIpp,
ppjob_IppSetRsp,
(LPARAM)(wReq | IPP_RESPONSE),
TRUE);
WebIppFreeMem(lpIpp);
} else {
SetLastError(ERROR_OUTOFMEMORY);
}
// Once we've verified the request for cancel, then
// we should remove the job from our list.
//
// NOTE: Should this be deleted always? Or should
// we make this dependent on the success of
// the server-call?
//
// 06-Jan-1998 <chriswil> Will Revisit.
//
if (dwCmd == JOB_CONTROL_CANCEL)
pjmDel(pjmList, pjm);
WebIppFreeMem(psj);
} else {
SetLastError(ERROR_OUTOFMEMORY);
}
}
} else {
SetLastError(ERROR_INVALID_PARAMETER);
}
return bRet;
}
/*****************************************************************************\
* ppjob_Enum (Local Routine)
*
* Enumerates jobs. IPP has the ENUJOB request which can be used for both
* specific jobs, or enumerated-jobs. We will distinguish whether we're
* enumerating by a (IPP_GETJOB_ALL) job-id.
*
\*****************************************************************************/
BOOL ppjob_Enum(
PCINETMONPORT pIniPort,
DWORD nJobStart,
DWORD cJobs,
DWORD dwLevel,
LPBYTE pbJob,
DWORD cbJob,
LPDWORD pcbNeed,
LPDWORD pcItems)
{
PIPPREQ_ENUJOB pgj;
REQINFO ri;
DWORD dwRet;
LPBYTE lpIpp;
DWORD cbIpp;
DWORD idx;
DWORD cbSize;
DWORD cbIdx;
DWORD dwLastError = ERROR_INVALID_DATA;
LPBYTE pbEnd;
LPPPJOB_ENUM pje = NULL;
BOOL bRet = FALSE;
DWORD curIdx = 0;
DWORD dwLocalJobCount = 0;
DWORD cbLocalJobSize = 0;
PJOBMAP* pjmList;
PJOBMAP pjmTmpList;
JOB_INFO_2 JobInfo2;
BOOL bFound;
// Specifying (IPP_GETJOB_ALL) will enumerate all jobs.
//
pjmList = pIniPort->GetPJMList ();
pbEnd = pbJob + cbJob;
cbIdx = ppjob_GetOneSize (dwLevel);
if (pIniPort->BeginReadEnumJobsCache (&pje)) {
bRet = TRUE;
dwLastError = GetLastError();
// Upon return, our (pje) pointer contains an
// enumeration structure of JOB_INFO_2 items.
//
// Based upon the level passed in, we need to either
// return these items or a converted JOB_INFO_1.
//
if (pje) {
// Calculate the size we'll need to store the
// enumerated items.
//
for (idx = 0, cbSize = 0; idx < pje->cItems; idx++)
cbSize += ppjob_GetJobSize(&pje->ji2[idx].ji2, dwLevel);
dwLocalJobCount = pjmGetLocalJobCount(pjmList, &cbLocalJobSize);
if (dwLocalJobCount > 0) {
cbSize += cbLocalJobSize;
}
// Fill in the return-value indicating
// the buffer necessary to hold the items.
//
*pcbNeed = cbSize;
// If the user buffer is of sufficient size,
// then copy/convert the items.
//
if (cbJob >= cbSize) {
*pcItems = pje->cItems + dwLocalJobCount;
for (idx = 0; idx < pje->cItems && cJobs; idx++) {
if ((idx >= nJobStart)) {
pbEnd = ppjob_CopyJob(pbJob,
dwLevel,
&pje->ji2[idx].ji2,
pbEnd);
pbJob += cbIdx;
cJobs--;
}
}
curIdx = idx;
} else {
bRet = FALSE;
dwLastError = ERROR_INSUFFICIENT_BUFFER;
}
}
}
else {
dwLocalJobCount = pjmGetLocalJobCount(pjmList, &cbLocalJobSize);
if (dwLocalJobCount > 0) {
cbSize = cbLocalJobSize;
// Fill in the return-value indicating
// the buffer necessary to hold the items.
//
*pcbNeed = cbSize;
// If the user buffer is of sufficient size,
// then copy/convert the items.
//
if (cbJob >= cbSize) {
*pcItems = dwLocalJobCount;
bRet = TRUE;
} else {
bRet = FALSE;
dwLastError = ERROR_INSUFFICIENT_BUFFER;
}
}
else {
dwLastError = GetLastError();
}
}
if (bRet) {
pjmTmpList = *pjmList;
for (idx = curIdx; idx < curIdx + dwLocalJobCount && cJobs; idx++) {
pjmTmpList = pjmNextLocalJob (&pjmTmpList, &JobInfo2, &bFound);
if ((idx >= nJobStart)) {
if (bFound) {
DBG_ASSERT( ((pbJob < pbEnd)?TRUE:FALSE),
(TEXT("ppjob_Enum: idx = %d, cbIdx = %d, cJobs = %d dwLocalJobCount = %d dwLocalSize=%d, pjd=%p\n"),
idx, cbIdx, cJobs, dwLocalJobCount, dwLocalJobCount, pje));
pbEnd = ppjob_CopyJob(pbJob,
dwLevel,
&JobInfo2,
pbEnd);
pbJob += cbIdx;
cJobs--;
}
else {
bRet = FALSE;
dwLastError = ERROR_INVALID_PARAMETER;
break;
}
}
}
}
// This function has to be called to release the critical section
//
pIniPort->EndReadEnumJobsCache ();
if (!bRet) {
SetLastError(dwLastError);
}
return bRet;
}
/*****************************************************************************\
* ppjob_EnumForCache (Local Routine)
*
* Enumerates jobs. IPP has the ENUJOB request which can be used for both
* specific jobs, or enumerated-jobs. We will distinguish whether we're
* enumerating by a (IPP_GETJOB_ALL) job-id.
*
* Upon return, ppbJob stores a pointer to the cache
*
\*****************************************************************************/
BOOL ppjob_EnumForCache(
PCINETMONPORT pIniPort,
LPPPJOB_ENUM *ppje)
{
PIPPREQ_ENUJOB pgj;
REQINFO ri;
DWORD dwRet;
LPBYTE lpIpp;
DWORD cbIpp;
DWORD idx;
DWORD cbSize;
DWORD cbIdx;
DWORD dwLastError = ERROR_INVALID_DATA;
LPBYTE pbEnd;
LPPPJOB_ENUM pje = NULL;
BOOL bRet = FALSE;
// Specifying (IPP_GETJOB_ALL) will enumerate all jobs.
//
pgj = WebIppCreateEnuJobReq(IPP_GETJOB_ALL, pIniPort->GetPortName());
if (pgj) {
// Convert the reqest to IPP, and perform the
// post.
//
ZeroMemory(&ri, sizeof(REQINFO));
ri.cpReq = CP_UTF8;
ri.idReq = IPP_REQ_ENUJOB;
ri.fReq[0] = IPP_REQALL;
ri.fReq[1] = IPP_REQALL;
dwRet = WebIppSndData(IPP_REQ_ENUJOB,
&ri,
(LPBYTE)pgj,
pgj->cbSize,
&lpIpp,
&cbIpp);
// The request-structure has been converted to IPP format,
// so it is ready to go to the server.
//
if (dwRet == WEBIPP_OK) {
// This routine returns with a LastError set to that
// which the response-routine sets.
//
if (pIniPort->SendReq(lpIpp,
cbIpp,
ppjob_IppEnuRsp,
(LPARAM)&pje,
TRUE)) {
dwLastError = GetLastError();
bRet = TRUE;
*ppje = pje;
} else {
dwLastError = GetLastError();
}
WebIppFreeMem(lpIpp);
} else {
dwLastError = ERROR_OUTOFMEMORY;
}
WebIppFreeMem(pgj);
} else {
dwLastError = ERROR_OUTOFMEMORY;
}
if (!bRet) {
SetLastError(dwLastError);
}
return bRet;
}
/*****************************************************************************\
* ppjob_Get (Local Routine)
*
* Returns information regarding a job.
*
\*****************************************************************************/
BOOL ppjob_Get(
PCINETMONPORT pIniPort,
DWORD idJob,
DWORD dwLevel,
LPBYTE pbJob,
DWORD cbJob,
LPDWORD pcbNeed)
{
PJOBMAP pjm;
PIPPREQ_GETJOB pgj;
REQINFO ri;
LPBYTE lpIpp;
DWORD cbIpp;
DWORD dwRet;
LPBYTE pbEnd;
PJOBMAP* pjmList;
LPJOB_INFO_2 pji2 = NULL;
BOOL bRet = FALSE;
DWORD dwLastError = ERROR_INVALID_DATA;
// Look in our JobMap list for the local-job-id. If we see
// one, the we can get the job-information.
//
pjmList = pIniPort->GetPJMList();
if (pjm = pjmFind(pjmList, PJM_LOCALID, idJob)) {
if (pjm->bRemoteJob) {
// Build a request-structure that we will pass into
// the IPP layer for processing.
//
pgj = WebIppCreateGetJobReq(pjmJobId(pjm, PJM_REMOTEID), pIniPort->GetPortName());
if (pgj) {
// Convert the reqest to IPP that is suitible for
// our post.
//
ZeroMemory(&ri, sizeof(REQINFO));
ri.cpReq = CP_UTF8;
ri.idReq = IPP_REQ_GETJOB;
ri.fReq[0] = IPP_REQALL;
ri.fReq[1] = IPP_REQALL;
dwRet = WebIppSndData(IPP_REQ_GETJOB,
&ri,
(LPBYTE)pgj,
pgj->cbSize,
&lpIpp,
&cbIpp);
// The request-structure has been converted to IPP format,
// so it is ready to go to the server. We set a callback
// to the function that will receive our data.
//
if (dwRet == WEBIPP_OK) {
pIniPort->SendReq(lpIpp,
cbIpp,
ppjob_IppGetRsp,
(LPARAM)&pji2,
TRUE);
// Upon return, our (pji2) contains the JOB_INFO_2
// structure.
//
if (pji2) {
bRet = ppjob_CalcAndCopyJob(pbJob, cbJob, pcbNeed, pji2, dwLevel);
if (!bRet) {
dwLastError = GetLastError ();
}
memFree(pji2, memGetSize(pji2));
}
WebIppFreeMem(lpIpp);
} else {
dwLastError = ERROR_OUTOFMEMORY;
}
WebIppFreeMem(pgj);
} else {
dwLastError = ERROR_OUTOFMEMORY;
}
}
else {
//
// This is a local job
//
if (pjm = pjmFind(pjmList, PJM_LOCALID, idJob)) {
JOB_INFO_2 JobInfo2;
BOOL bFound;
pjmNextLocalJob(&pjm, &JobInfo2, &bFound);
if (bFound) {
bRet = ppjob_CalcAndCopyJob(pbJob, cbJob, pcbNeed, &JobInfo2, dwLevel);
if (!bRet) {
dwLastError = GetLastError ();
}
}
else
dwLastError = ERROR_INVALID_PARAMETER;
} else {
dwLastError = ERROR_INVALID_PARAMETER;
}
}
} else {
dwLastError = ERROR_INVALID_PARAMETER;
}
// Set the lasterror if failure.
//
if (!bRet) {
SetLastError(dwLastError);
}
return bRet;
}
/*****************************************************************************\
* ppjob_Add (Local Routine)
*
* Returns information for an addjob call.
*
\*****************************************************************************/
BOOL ppjob_Add(
HANDLE hPrinter,
PCINETMONPORT pIniPort,
DWORD dwLevel,
LPCTSTR lpszName,
LPBYTE pbData,
DWORD cbBuf,
LPDWORD pcbNeeded)
{
PJOBMAP pjm;
LPCTSTR lpszSplFile;
LPTSTR* lpszSrc;
LPBYTE pbEnd;
PJOBMAP* pjmList;
LPTSTR aszSrc[(sizeof(ADDJOB_INFO_1) / sizeof(LPTSTR))];
BOOL bRet = FALSE;
static DWORD s_AJI1Offsets[] = {
offsetof(LPADDJOB_INFO_1, Path),
0xFFFFFFFF
};
// Create a spool-file and job that we will use
// for this AddJob() call.
//
pjmList = pIniPort->GetPJMList();
if (pjm = pjmAdd(pjmList, pIniPort, lpszName, NULL)) {
// Set the job into spooling-state. This internally
// creates the spool-file. By specifying PJM_NOOPEN,
// we indicate that no open-handles are to be maintained
// on the spool-file.
//
if (pjmSetState(pjm, PJM_SPOOLING | PJM_NOOPEN)) {
// Get the spool-file.
//
lpszSplFile = pjmSplFile(pjm);
// If a return-size is provided, then set it.
//
if (pcbNeeded)
*pcbNeeded = sizeof(ADDJOB_INFO_1) + utlStrSize(lpszSplFile);
// If the buffer is capable of holding the
// return-structure, then proceed.
//
if (pbData && (cbBuf >= *pcbNeeded)) {
// Clean out the string-array and setup
// for building the structure.
//
ZeroMemory(aszSrc, sizeof(aszSrc));
lpszSrc = aszSrc;
// Initialize fixed values.
//
((LPADDJOB_INFO_1)pbData)->JobId = pjmJobId(pjm, PJM_LOCALID);
// Pack the file-name into the return-structure.
//
pbEnd = pbData + cbBuf;
*lpszSrc++ = (LPTSTR)lpszSplFile;
utlPackStrings(aszSrc, pbData, s_AJI1Offsets, pbEnd);
// Mark this printer to indicate it's in a
// addjob.
//
// NOTE: do we really need to consume the printer
// for an AddJob(). LocalSpl does this and
// sets the job into the printer. I don't
// see why this is necessary.
//
PP_SetStatus(hPrinter, PP_ADDJOB);
bRet = TRUE;
} else {
SetLastError(ERROR_INSUFFICIENT_BUFFER);
}
} else {
SetLastError(ERROR_FILE_NOT_FOUND);
}
} else {
SetLastError(ERROR_INVALID_HANDLE);
}
return bRet;
}
/*****************************************************************************\
* ppjob_Schedule (Local Routine)
*
* Prints the scheduled job.
*
\*****************************************************************************/
BOOL ppjob_Schedule(
HANDLE hPrinter,
PCINETMONPORT pIniPort,
PJOBMAP pjm)
{
HANDLE hOut;
BOOL bRemote;
LPCTSTR lpszUser;
PIPPREQ_PRTJOB ppj;
REQINFO ri;
LPBYTE pbOut;
LPBYTE pbIpp;
LPBYTE pbSpl;
DWORD cbOut;
DWORD cbIpp;
DWORD cbSpl;
DWORD dwRet;
DWORD cbWr;
PJOBMAP* pjmList;
DWORD dwLE = ERROR_INVALID_HANDLE;
CFileStream *pStream = NULL;
CFileStream *pSplStream = NULL;
BOOL bRet = FALSE;
// Lock the file so we can obtain the spool-data.
//
pjmList = pIniPort->GetPJMList();
if (pSplStream = pjmSplLock(pjm)) {
// Check to determine if this is a remote-call.
//
bRemote = TRUE;
// Get the user-name if one was specified in AddJob().
//
lpszUser = pjmSplUser(pjm);
// Create the print-job-request that we'll be using.
//
ppj = WebIppCreatePrtJobReq(FALSE,
(lpszUser ? lpszUser : TEXT("Unknown")),
(bRemote ? g_szDocRemote: g_szDocLocal),
pIniPort->GetPortName());
if (ppj) {
ZeroMemory(&ri, sizeof(REQINFO));
ri.cpReq = CP_UTF8;
ri.idReq = IPP_REQ_PRINTJOB;
ri.fReq[0] = IPP_REQALL;
ri.fReq[1] = IPP_REQALL;
dwRet = WebIppSndData(IPP_REQ_PRINTJOB,
&ri,
(LPBYTE)ppj,
ppj->cbSize,
&pbIpp,
&cbIpp);
// Make sure we were able to get the ipp-header.
//
if (dwRet == WEBIPP_OK) {
// Create the outputfile that will be used to
// contain both the ipp-header as well as the
// spool-data.
//
if (hOut = SplCreate(pjmJobId(pjm, PJM_LOCALID), SPLFILE_SPL)) {
// Output the header and data.
//
if (SplWrite(hOut, pbIpp, cbIpp, &cbWr) &&
SplWrite(hOut, pSplStream) &&
// Output the request.
//
(pStream = SplLock(hOut))) {
bRet = pIniPort->SendReq(pStream,
(IPPRSPPROC)ppjob_IppPrtRsp,
(LPARAM)pjm,
TRUE);
if (bRet == FALSE)
dwLE = GetLastError();
SplUnlock(hOut);
} else {
dwLE = GetLastError();
}
// Free up the spool-output-file.
//
SplFree(hOut);
} else {
dwLE = GetLastError();
}
WebIppFreeMem(pbIpp);
} else {
dwLE = ERROR_OUTOFMEMORY;
}
WebIppFreeMem(ppj);
} else {
dwLE = ERROR_OUTOFMEMORY;
}
pjmSplUnlock(pjm);
} else {
dwLE = GetLastError();
}
// Clear out our spooling-status. This will close
// and delete the spool-file as the job is now in the
// hands of spooler.
//
pjmClrState(pjm, PJM_SPOOLING);
// If a cancel was set on this job, then delete it's entry from
// our list.
//
if (pjmChkState(pjm, PJM_CANCEL) && pjmList != NULL)
pjmDel(pjmList, pjm);
// Set lasterror if problem occured.
//
if (bRet == FALSE)
SetLastError(dwLE);
return bRet;
}
/*****************************************************************************\
* PPEnumJobs
*
* Retrives the information about a specified set of print jobs for a
* specified printer. Returns TRUE if successful. Otherwise, it returns
* FALSE.
*
\*****************************************************************************/
BOOL PPEnumJobs(
HANDLE hPrinter,
DWORD nJobStart,
DWORD cJobs,
DWORD dwLevel,
LPBYTE pbJob,
DWORD cbJob,
LPDWORD pcbNeeded,
LPDWORD pcItems)
{
PCINETMONPORT pIniPort;
BOOL bRet = FALSE;
DBG_MSG(DBG_LEV_CALLTREE, (TEXT("Call: PPEnumJobs: Printer(%08lX) dwLevel(%d)"), hPrinter, dwLevel));
semEnterCrit();
*pcbNeeded = 0;
*pcItems = 0;
// Make sure we have a valid printer handle.
//
if (pIniPort = utlValidatePrinterHandle(hPrinter)) {
// Attempt to get a list of jobs from the ipp print spooler.
// Format the job information to the requested information level.
//
switch (dwLevel) {
case PRINT_LEVEL_1:
case PRINT_LEVEL_2:
bRet = ppjob_Enum(pIniPort,
nJobStart,
cJobs,
dwLevel,
pbJob,
cbJob,
pcbNeeded,
pcItems);
break;
default:
DBG_MSG(DBG_LEV_WARN, (TEXT("Warn: PPEnumJobs: Invalid Level (%d)"), dwLevel));
SetLastError(ERROR_INVALID_LEVEL);
break;
}
}
semLeaveCrit();
return bRet;
}
/*****************************************************************************\
* PPGetJob
*
* Retrieves information about a print job on a specified printer. Returns
* TRUE if successful. Otherwise, it returns FASLSE.
*
\*****************************************************************************/
BOOL PPGetJob(
HANDLE hPrinter,
DWORD idJob,
DWORD dwLevel,
LPBYTE pbJob,
DWORD cbJob,
LPDWORD pcbNeed)
{
PCINETMONPORT pIniPort;
BOOL bRet = FALSE;
DBG_MSG(DBG_LEV_CALLTREE, (TEXT("Call: PPGetJob: Printer(%08lX) dwLevel(%d)"), hPrinter, dwLevel));
semEnterCrit();
*pcbNeed = 0;
// Make sure we're looking at a valid printer handle.
//
if (pIniPort = utlValidatePrinterHandle(hPrinter)) {
// Switch on print-level.
//
switch (dwLevel) {
case PRINT_LEVEL_1:
case PRINT_LEVEL_2:
bRet = ppjob_Get(pIniPort, idJob, dwLevel, pbJob, cbJob, pcbNeed);
break;
default:
DBG_MSG(DBG_LEV_WARN, (TEXT("Warn: PPGetJob: Invalid Level (%d)"), dwLevel));
SetLastError(ERROR_INVALID_LEVEL);
break;
}
}
semLeaveCrit();
return bRet;
}
/*****************************************************************************\
* PPSetJob
*
* Sets information for and issues commands to a print job. Returns TRUE
* if successful. Otherwise, it returns FALSE.
*
\*****************************************************************************/
BOOL PPSetJob(
HANDLE hPrinter,
DWORD dwJobId,
DWORD dwLevel,
LPBYTE pbJob,
DWORD dwCmd)
{
PCINETMONPORT pIniPort;
BOOL bResult = FALSE;
DBG_MSG(DBG_LEV_CALLTREE, (TEXT("Call: PPSetJob: Printer(%08lX) dwLevel(%d)"), hPrinter, dwLevel));
semEnterCrit();
// Make sure we've got a valid printer handle.
//
if (pIniPort = utlValidatePrinterHandle(hPrinter)) {
// Set job parameters.
//
switch (dwLevel) {
case PRINT_LEVEL_0:
// Do not set parameters. (0) represents "no-command".
//
switch (dwCmd) {
case JOB_CONTROL_CANCEL:
case JOB_CONTROL_DELETE:
case JOB_CONTROL_PAUSE:
case JOB_CONTROL_RESUME:
case JOB_CONTROL_RESTART:
bResult = ppjob_Set(pIniPort, dwJobId, dwCmd);
if (bResult) {
// Invalidate has to occur before notfication refresh, otherwise, you
// get an outdated result
//
pIniPort->InvalidateEnumJobsCache ();
pIniPort->InvalidateGetPrinterCache ();
RefreshNotification((LPINET_HPRINTER)hPrinter);
}
break;
case 0:
bResult = TRUE;
break;
}
break;
default:
DBG_MSG(DBG_LEV_WARN, (TEXT("Warn: PPSetJob: Invalid Level (%d)"), dwLevel));
SetLastError(ERROR_INVALID_LEVEL);
break;
}
}
semLeaveCrit();
return bResult;
}
/*****************************************************************************\
* PPAddJob
*
* Sets up for a local-spooled job. Since we are truly a remote-printer, we
* need to fail this call and signify the correct error-code.
*
\*****************************************************************************/
BOOL PPAddJob(
HANDLE hPrinter,
DWORD dwLevel,
LPBYTE pbData,
DWORD cbBuf,
LPDWORD pcbNeeded)
{
PCINETMONPORT pIniPort;
LPTSTR lpszName;
BOOL bRet = FALSE;
DBG_MSG(DBG_LEV_CALLTREE, (TEXT("Call: PPAddJob: Printer(%08lX) dwLevel(%d)"), hPrinter, dwLevel));
// Zero out the return-parameters.
//
*pcbNeeded = 0;
semEnterCrit();
if (pIniPort = utlValidatePrinterHandle(hPrinter)) {
if (pbData && pcbNeeded) {
switch (dwLevel) {
case PRINT_LEVEL_2:
lpszName = (LPTSTR)(pbData + (ULONG_PTR)((LPADDJOB_INFO_2W)pbData)->pData);
// Make sure this string-address does not extend past the
// end of available buffer specified.
//
if (lpszName > (LPTSTR)(pbData + cbBuf)) {
SetLastError(ERROR_INVALID_LEVEL);
goto EndAdd;
}
// Ensure NULL termination.
//
*(PTCHAR)(((ULONG_PTR)(pbData + cbBuf - sizeof(TCHAR))&~1)) = 0;
break;
case PRINT_LEVEL_1:
lpszName = NULL;
break;
default:
SetLastError(ERROR_INVALID_LEVEL);
goto EndAdd;
}
// Do the add.
//
bRet = ppjob_Add(hPrinter,
pIniPort,
dwLevel,
lpszName,
pbData,
cbBuf,
pcbNeeded);
} else {
SetLastError(ERROR_INVALID_PARAMETER);
}
}
EndAdd:
semLeaveCrit();
return bRet;
}
/*****************************************************************************\
* PPScheduleJob
*
* This schedules the job. Since we don't support the PPAddJob(), this call
* must fail.
*
\*****************************************************************************/
BOOL PPScheduleJob(
HANDLE hPrinter,
DWORD idJob)
{
PCINETMONPORT pIniPort;
PJOBMAP pjm;
PJOBMAP* pjmList;
BOOL bRet = FALSE;
DBG_MSG(DBG_LEV_CALLTREE, (TEXT("Call: PPScheduleJob: Printer(%08lX) idJob(%d)"), hPrinter, idJob));
semEnterCrit();
if (pIniPort = utlValidatePrinterHandle(hPrinter)) {
pjmList = pIniPort->GetPJMList();
if (pjm = pjmFind(pjmList, PJM_LOCALID, idJob)) {
if (pjmChkState(pjm, PJM_SPOOLING)) {
bRet = ppjob_Schedule(hPrinter, pIniPort, pjm);
} else {
SetLastError(ERROR_SPL_NO_ADDJOB);
}
} else {
SetLastError(ERROR_INVALID_PARAMETER);
}
PP_ClrStatus(hPrinter, PP_ADDJOB);
}
semLeaveCrit();
return bRet;
}