/*++ Copyright (c) 1990 - 1996 Microsoft Corporation Module Name: spooler.c Abstract: This module provides all the public exported APIs relating to spooling and printing for the Local Print Providor. They include LocalStartDocPrinter LocalWritePrinter LocalReadPrinter LocalEndDocPrinter LocalAbortPrinter Author: Dave Snipp (DaveSn) 15-Mar-1991 Revision History: --*/ #include #pragma hdrstop #include "jobid.h" #include "filepool.hxx" //extern HANDLE hFilePool; BOOL SpoolThisJob( PSPOOL pSpool, DWORD Level, LPBYTE pDocInfo ); BOOL PrintingDirectlyToPort( PSPOOL pSpool, DWORD Level, LPBYTE pDocInfo, LPDWORD pJobId ); BOOL PrintingDirect( PSPOOL pSpool, DWORD Level, LPBYTE pDocInfo ); DWORD ReadFromPrinter( PSPOOL pSpool, LPBYTE pBuf, DWORD cbBuf ); BOOL InternalReadPrinter( HANDLE hPrinter, LPVOID pBuf, DWORD cbBuf, LPBYTE *pMapBuffer, LPDWORD pNoBytesRead, BOOL bReadMappedView ); BOOL SetMappingPointer( PSPOOL pSpool, LPBYTE *pMappedBuffer, DWORD cbReadSize ); DWORD WriteToPrinter( PSPOOL pSpool, LPBYTE pByte, DWORD cbBuf ); BOOL IsGoingToFile( LPWSTR pOutputFile, PINISPOOLER pIniSpooler ); VOID MyPostThreadMessage( IN HANDLE hThread, IN DWORD idThread, IN UINT Msg, IN WPARAM wParam, IN LPARAM lParam) { SplOutSem(); // // PostThreadMessage will fail under the following cases: // a. Too early -- MessageBox has not been created yet // b. Too late -- User has cancelled the dialog // // In case a. if we wait for few seconds and retry Post will succeed // In case b. WaitForSingleObject on thread handle will return WAIT_OBJECT_O // while ( !PostThreadMessage(idThread, Msg, wParam, lParam) ) { DBGMSG(DBG_WARNING, ("PostThreadMessage FAILED %d\n", GetLastError())); // // As far as thread is alive after 1 second retry the post // if ( WaitForSingleObject(hThread, 1000) != WAIT_TIMEOUT ) break; } } VOID SeekPrinterSetEvent( PINIJOB pIniJob, HANDLE hFile, BOOL bEndDoc ) { DWORD dwFileSizeHigh,dwFileSizeLow; if (!hFile) { hFile = pIniJob->hWriteFile; } if (pIniJob->bWaitForSeek && pIniJob->WaitForSeek != NULL ){ if (!bEndDoc) { // Compare the sizes. if (pIniJob->Status & JOB_TYPE_OPTIMIZE) { dwFileSizeHigh = 0; dwFileSizeLow = pIniJob->dwValidSize; } else { dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh); if ((dwFileSizeLow == 0xffffffff) && (GetLastError() != NO_ERROR)) { return; } } if ((pIniJob->liFileSeekPosn.u.HighPart > (LONG)dwFileSizeHigh) || ((pIniJob->liFileSeekPosn.u.HighPart == (LONG)dwFileSizeHigh) && (pIniJob->liFileSeekPosn.u.LowPart > dwFileSizeLow))) { return; } } SetEvent(pIniJob->WaitForSeek); } return; } DWORD LocalStartDocPrinter( HANDLE hPrinter, DWORD Level, LPBYTE pDocInfo ) { PINIPRINTER pIniPrinter; PINIPORT pIniPort; PSPOOL pSpool=(PSPOOL)hPrinter; DWORD LastError=0, JobId=0; PDOC_INFO_1 pDocInfo1 = (PDOC_INFO_1)pDocInfo; BOOL bPrintingDirect; SPLASSERT(Level == 1); if (ValidateSpoolHandle(pSpool, PRINTER_HANDLE_SERVER ) && !(pSpool->Status & SPOOL_STATUS_STARTDOC) && !(pSpool->Status & SPOOL_STATUS_ADDJOB)) { if ((pSpool->TypeofHandle & PRINTER_HANDLE_PORT) && (pIniPort = pSpool->pIniPort) && (pIniPort->signature == IPO_SIGNATURE)) { if (!(PrintingDirectlyToPort(pSpool, Level, pDocInfo, &JobId))) { return FALSE; } } else if ((pSpool->TypeofHandle & PRINTER_HANDLE_PRINTER) && (pIniPrinter = pSpool->pIniPrinter)) { bPrintingDirect = FALSE; if (pIniPrinter->Attributes & PRINTER_ATTRIBUTE_DIRECT) { bPrintingDirect = TRUE; } else { EnterSplSem(); bPrintingDirect = IsGoingToFile(pDocInfo1->pOutputFile, pSpool->pIniSpooler); LeaveSplSem(); } if (bPrintingDirect) { if (!PrintingDirect(pSpool, Level, pDocInfo)) return FALSE; } else { if (!SpoolThisJob(pSpool, Level, pDocInfo)) return FALSE; } } else LastError = ERROR_INVALID_PARAMETER; if (!LastError) { pSpool->Status |= SPOOL_STATUS_STARTDOC; pSpool->Status &= ~SPOOL_STATUS_CANCELLED; } } else LastError = ERROR_INVALID_HANDLE; if (LastError) { DBGMSG(DBG_WARNING, ("StartDoc FAILED %d\n", LastError)); SetLastError(LastError); return FALSE; } if (JobId) return JobId; else return pSpool->pIniJob->JobId; } BOOL LocalStartPagePrinter( HANDLE hPrinter ) /*++ Bug-Bug: StartPagePrinter and EndPagePrinter calls should be supported only for SPOOL_STATUS_STARTDOC handles only. However because of our fixes for the engine, we cannot fail StartPagePrinter and EndPagePrinter for SPOOL_STATUS_ADDJOB as well. --*/ { PSPOOL pSpool = (PSPOOL)hPrinter; HANDLE hFile = INVALID_HANDLE_VALUE; DWORD dwFileSize; if (!ValidateSpoolHandle(pSpool, PRINTER_HANDLE_SERVER )) { return(FALSE); } if (pSpool->Status & SPOOL_STATUS_CANCELLED) { SetLastError(ERROR_PRINT_CANCELLED); return FALSE; } if(!(pSpool->Status & SPOOL_STATUS_STARTDOC) && !(pSpool->Status & SPOOL_STATUS_ADDJOB)) { SetLastError(ERROR_SPL_NO_STARTDOC); return FALSE; } if (pSpool->pIniJob != NULL) { if ( (pSpool->TypeofHandle & PRINTER_HANDLE_PORT) && ((pSpool->pIniJob->Status & JOB_PRINTING) || (pSpool->pIniJob->Status & JOB_DESPOOLING))) { // // Account for Pages Printed in LocalEndPagePrinter // } else { // We Are Spooling UpdateJobAttributes(pSpool->pIniJob); pSpool->pIniJob->cLogicalPages++; if (pSpool->pIniJob->cLogicalPages >= pSpool->pIniJob->dwJobNumberOfPagesPerSide) { pSpool->pIniJob->cLogicalPages = 0; pSpool->pIniJob->cPages++; } if ( pSpool->pIniJob->Status & JOB_TYPE_ADDJOB ) { // If the Job is being written on the client side // the size is not getting updated so do it now on // the start page if ( pSpool->hReadFile != INVALID_HANDLE_VALUE ) { hFile = pSpool->hReadFile; } else { hFile = pSpool->pIniJob->hWriteFile; } if ( hFile != INVALID_HANDLE_VALUE ) { dwFileSize = GetFileSize( hFile, 0 ); if ( pSpool->pIniJob->Size < dwFileSize ) { DBGMSG( DBG_TRACE, ("StartPagePrinter adjusting size old %d new %d\n", pSpool->pIniJob->Size, dwFileSize)); pSpool->pIniJob->dwValidSize = pSpool->pIniJob->Size; pSpool->pIniJob->Size = dwFileSize; // Support for despooling whilst spooling // for Down Level jobs if (pSpool->pIniJob->WaitForWrite != NULL) SetEvent( pSpool->pIniJob->WaitForWrite ); } } } } } else { DBGMSG(DBG_TRACE, ("StartPagePrinter issued with no Job\n")); } return TRUE; } PINIPORT FindFilePort( LPWSTR pFileName, PINISPOOLER pIniSpooler) { PINIPORT pIniPort; SPLASSERT( pIniSpooler->signature == ISP_SIGNATURE ); pIniPort = pIniSpooler->pIniPort; while (pIniPort) { if (!wcscmp(pIniPort->pName, pFileName) && (pIniPort->Status & PP_FILE)){ return (pIniPort); } pIniPort = pIniPort->pNext; } return NULL; } PINIMONITOR FindFilePortMonitor( PINISPOOLER pIniSpooler ) { PINIPORT pIniPort; SPLASSERT( pIniSpooler->signature == ISP_SIGNATURE ); pIniPort = pIniSpooler->pIniPort; while (pIniPort) { if (!wcscmp(pIniPort->pName, L"FILE:")) { return pIniPort->pIniMonitor; } pIniPort = pIniPort->pNext; } return NULL; } BOOL AddIniPrinterToIniPort( PINIPORT pIniPort, PINIPRINTER pIniPrinter ) { DWORD i; PINIPRINTER *ppIniPrinter; // // If Printer already attatched to Port // for (i = 0; i < pIniPort->cPrinters; i++) { if (pIniPort->ppIniPrinter[i] == pIniPrinter) { return TRUE; } } ppIniPrinter = RESIZEPORTPRINTERS(pIniPort, 1); if ( ppIniPrinter != NULL ) { pIniPort->ppIniPrinter = ppIniPrinter; if ( !pIniPort->cPrinters ) CreateRedirectionThread(pIniPort); pIniPort->ppIniPrinter[pIniPort->cPrinters++] = pIniPrinter; DBGMSG( DBG_TRACE, ("AddIniPrinterToIniPort pIniPrinter %x %ws pIniPort %x %ws\n", pIniPrinter, pIniPrinter->pName, pIniPort, pIniPort->pName )); return TRUE; } else { DBGMSG( DBG_WARNING, ("AddIniPrintertoIniPort failed pIniPort %x pIniPrinter %x error %d\n", pIniPort, pIniPrinter, GetLastError() )); return FALSE; } } BOOL AddIniPortToIniPrinter( IN PINIPRINTER pIniPrinter, IN PINIPORT pIniPort ) /*++ Routine Description: Adds a IniPort structure to a IniPrinter. A link between a printer and a port must be bi-directional. It is mandatory to call AddIniPrinterToIniPort in pair with this function or to handle the bi-dir link. Arguments: pIniPrinter - printer that is going to use the port pIniPort - port to be assigned to printer Return Value: BOOL - TRUE if the port succesfully assigned to printer or if the printer is already assigned to port --*/ { DWORD i; PINIPORT *ppIniPorts; // // Search if Port is already attached to Printer and return TRUE if it does // for (i = 0; i < pIniPrinter->cPorts; i++) { if (pIniPrinter->ppIniPorts[i] == pIniPort) { return TRUE; } } ppIniPorts = RESIZEPRINTERPORTS(pIniPrinter, 1); if ( ppIniPorts != NULL ) { pIniPrinter->ppIniPorts = ppIniPorts; pIniPrinter->ppIniPorts[pIniPrinter->cPorts++] = pIniPort; DBGMSG( DBG_TRACE, ("AddIniPortToIniPrinter pIniPort %x %ws pIniPrinter %x %ws\n", pIniPort, pIniPort->pName, pIniPrinter, pIniPrinter->pName )); return TRUE; } else { DBGMSG( DBG_WARNING, ("AddIniPrintertoIniPort failed pIniPort %x pIniPrinter %x error %d\n", pIniPort, pIniPrinter, GetLastError() )); return FALSE; } } VOID AddJobEntry( PINIPRINTER pIniPrinter, PINIJOB pIniJob ) { DWORD Position; SplInSem(); // DO NOT Add the Same Job more than once SPLASSERT(pIniJob != FindJob(pIniPrinter, pIniJob->JobId, &Position)); pIniJob->pIniPrevJob = pIniPrinter->pIniLastJob; if (pIniJob->pIniPrevJob) pIniJob->pIniPrevJob->pIniNextJob = pIniJob; pIniPrinter->pIniLastJob = pIniJob; if (!pIniPrinter->pIniFirstJob) pIniPrinter->pIniFirstJob=pIniJob; } BOOL CheckDataTypes( PINIPRINTPROC pIniPrintProc, LPWSTR pDatatype ) { PDATATYPES_INFO_1 pDatatypeInfo; DWORD i; pDatatypeInfo = (PDATATYPES_INFO_1)pIniPrintProc->pDatatypes; for (i=0; icDatatypes; i++) if (!lstrcmpi(pDatatypeInfo[i].pName, pDatatype)) return TRUE; return FALSE; } PINIPRINTPROC FindDatatype( PINIPRINTPROC pDefaultPrintProc, LPWSTR pDatatype ) { PINIPRINTPROC pIniPrintProc; if ( pDatatype == NULL ) { return NULL; } // // !! HACK !! // // Our method of exposing NT EMF 1.00x is broken. EMF jobs are created // by GDI using NT EMF 1.003 on NT4 and 1.008 on Win2000. Therefore, // a print processor written for NT4 will not work on Win2000 because // they didn't know about the new datatype. Usually the print processor // doesn't parse the EMF. If they do, they are really broken. // // This hack is to call the IHV print processor with 1.008 EMF. // if (pDefaultPrintProc) { // // If the datatype is supported by the print processor OR // the datatype is NT EMF 1.008 (Win2000) and the print processor // supports NT EMF 1.003, then return this print processor. // if (CheckDataTypes(pDefaultPrintProc, pDatatype) || (!_wcsicmp(pDatatype, gszNT5EMF) && CheckDataTypes(pDefaultPrintProc, gszNT4EMF))) { return pDefaultPrintProc; } } pIniPrintProc = pThisEnvironment->pIniPrintProc; while ( pIniPrintProc ) { if ( CheckDataTypes( pIniPrintProc, pDatatype )) { return pIniPrintProc; } pIniPrintProc = pIniPrintProc->pNext; } DBGMSG( DBG_WARNING, ( "FindDatatype: Could not find Datatype\n") ); return FALSE; } BOOL IsGoingToFile( LPWSTR pOutputFile, PINISPOOLER pIniSpooler) { PINIPORT pIniPort; LPWSTR pszShare; SplInSem(); SPLASSERT(pIniSpooler->signature == ISP_SIGNATURE); // // Validate the contents of the pIniJob->pOutputFile // if it is a valid file, then return true // if it is a port name or any other kind of name then ignore // if (pOutputFile && *pOutputFile) { // // we have a non-null pOutputFile // match this with all available ports // pIniPort = pIniSpooler->pIniPort; while ( pIniPort ) { SPLASSERT( pIniPort->signature == IPO_SIGNATURE ); if (!_wcsicmp( pIniPort->pName, pOutputFile )) { // // We have matched the pOutputFile field with a // valid port and the port is not a file port // if (pIniPort->Status & PP_FILE) { pIniPort = pIniPort->pNext; continue; } return FALSE; } pIniPort = pIniPort->pNext; } // // We have no port that matches exactly // so let's assume its a file. // // ugly hack -- check for Net: as the name // // This would normally match files like "NewFile" or "Nextbox," // but since we always fully qualify filenames, we don't encounter // any problems. // if (!_wcsnicmp(pOutputFile, L"Ne", 2)) { return FALSE; } // // We have the problem LAN man ports coming as UNC path and being // treated as files. This is a HACK for that // if ( pOutputFile && pOutputFile[0] == L'\\' && pOutputFile[1] == L'\\' && (pszShare = wcschr(pOutputFile+2, L'\\')) ) { pszShare++; if ( FindPrinter(pszShare, pIniSpooler) || FindPrinterShare(pszShare, pIniSpooler) ) return FALSE; } return TRUE; } return FALSE; } BOOL SpoolThisJob( PSPOOL pSpool, DWORD Level, LPBYTE pDocInfo ) { WCHAR szFileName[MAX_PATH]; PDOC_INFO_1 pDocInfo1=(PDOC_INFO_1)pDocInfo; HANDLE hImpersonationToken; DWORD dwId = 0; HANDLE hWriteFile = INVALID_HANDLE_VALUE; LPWSTR pszDatatype = NULL; HANDLE pSplFilePoolItem = NULL; HRESULT RetVal = S_OK; LPWSTR pszName = NULL; BOOL bRemote; DBGMSG(DBG_TRACE, ("Spooling document %ws\n", pDocInfo1->pDocName ? pDocInfo1->pDocName : L"")); if( pDocInfo1 && pDocInfo1->pDatatype ){ pszDatatype = pDocInfo1->pDatatype; // // !! HACK !! // // We will do not support sending NT4 EMF to NT5 servers (NT EMF 1.003). // However, the HP LJ 1100 monolith installation program requires // that this datatype is available. So we added this back to winprint, // but we don't want people to use it. Therefore we will reject // the datatype here. Big hack. // if( !FindDatatype( NULL, pszDatatype ) || !_wcsicmp(pszDatatype, gszNT4EMF)){ DBGMSG(DBG_WARNING, ("Datatype %ws is invalid\n", pDocInfo1->pDatatype)); SetLastError(ERROR_INVALID_DATATYPE); return FALSE; } } EnterSplSem(); // // Check if we need to disallow EMF printing. // if( pSpool->pIniPrinter->Attributes & PRINTER_ATTRIBUTE_RAW_ONLY ){ if( !pszDatatype ){ pszDatatype = pSpool->pDatatype ? pSpool->pDatatype : pSpool->pIniPrinter->pDatatype; } if( !ValidRawDatatype( pszDatatype )){ LeaveSplSem(); DBGMSG(DBG_WARNING, ("Datatype %ws is not RAW to a RAW printer\n", pDocInfo1->pDatatype)); SetLastError(ERROR_INVALID_DATATYPE); return FALSE; } } dwId = GetNextId( pSpool->pIniPrinter->pIniSpooler->hJobIdMap ); // // If we are using keep printed jobs, or an independent spool directory // exists for this printer, then we don't want to use the file pool. // if ( pSpool->pIniPrinter->Attributes & PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS || pSpool->pIniPrinter->pSpoolDir || pSpool->pIniPrinter->pIniSpooler->dwSpoolerSettings & SPOOLER_NOFILEPOOLING) { GetFullNameFromId(pSpool->pIniPrinter, dwId, TRUE, szFileName, COUNTOF(szFileName), FALSE); } LeaveSplSem(); SplOutSem(); // // WMI Trace Event. // LogWmiTraceEvent(dwId, EVENT_TRACE_TYPE_SPL_SPOOLJOB, NULL); if (!(hImpersonationToken = RevertToPrinterSelf())) { DBGMSG(DBG_WARNING, ("SpoolThisJob RevertToPrinterSelf failed: %d\n", GetLastError())); SplOutSem(); return FALSE; } // // If keep printed jobs is enabled for this printer, or if the printer has // a spool directory, or if the filepooling for the spooler have been // turned off, then we don't use the file pool. // if (pSpool->pIniPrinter->Attributes & PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS || pSpool->pIniPrinter->pSpoolDir || pSpool->pIniPrinter->pIniSpooler->dwSpoolerSettings & SPOOLER_NOFILEPOOLING) { hWriteFile = CreateFile(szFileName, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); } else { // // We're not keeping Printed Jobs, use the Pool. // // // This sets up the Spool and Shadow Files at the same time. // RetVal = GetFileItemHandle(pSpool->pIniPrinter->pIniSpooler->hFilePool, &pSplFilePoolItem, NULL); if (SUCCEEDED(RetVal)) { RetVal = GetWriterFromHandle(pSplFilePoolItem, &hWriteFile, TRUE); if (FAILED(RetVal)) { hWriteFile = INVALID_HANDLE_VALUE; } RetVal = GetNameFromHandle(pSplFilePoolItem, &pszName, TRUE); if (SUCCEEDED(RetVal)) { RetVal = StringCchCopy(szFileName, COUNTOF(szFileName), pszName); } else { szFileName[0] = '\0'; } } else { hWriteFile = INVALID_HANDLE_VALUE; } } if (!ImpersonatePrinterClient(hImpersonationToken)) { DBGMSG(DBG_WARNING, ("SpoolThisJob ImpersonatePrinterClient failed: %d\n", GetLastError())); SplOutSem(); return FALSE; } if ( hWriteFile == INVALID_HANDLE_VALUE ) { DBGMSG(DBG_WARNING, ("SpoolThisJob CreateFile( %ws ) GENERIC_WRITE failed: Error %d\n", szFileName, GetLastError())); SplOutSem(); return FALSE; } else { DBGMSG(DBG_TRACE, ("SpoolThisJob CreateFile( %ws) GENERIC_WRITE Success:hWriteFile %x\n",szFileName, hWriteFile)); } RetVal = CheckLocalCall(); if (RetVal == S_OK) { bRemote = FALSE; } else if (RetVal == S_FALSE) { bRemote = TRUE; } else { SetLastError(SCODE_CODE(RetVal)); return FALSE; } EnterSplSem(); if( !(pSpool->pIniJob = CreateJobEntry(pSpool, Level, pDocInfo, dwId, bRemote, 0, NULL))) { LeaveSplSem(); if ( pSplFilePoolItem ) { FinishedWriting(pSplFilePoolItem, TRUE); ReleasePoolHandle(&pSplFilePoolItem); } else { CloseHandle( hWriteFile ); DeleteFile( szFileName ); } SplOutSem(); return FALSE; } if ( pSplFilePoolItem ) { pSpool->pIniJob->hFileItem = pSplFilePoolItem; if ( pszName ) { pSpool->pIniJob->pszSplFileName = pszName; } } SPLASSERT(!IsGoingToFile(pSpool->pIniJob->pOutputFile, pSpool->pIniSpooler)); InterlockedOr((LONG*)&(pSpool->pIniJob->Status), JOB_SPOOLING); // // Gather Stress Information for Max Number of concurrent spooling jobs // pSpool->pIniPrinter->cSpooling++; if (pSpool->pIniPrinter->cSpooling > pSpool->pIniPrinter->cMaxSpooling) pSpool->pIniPrinter->cMaxSpooling = pSpool->pIniPrinter->cSpooling; pSpool->pIniJob->hWriteFile = hWriteFile; // // !! NOTE !! // // Removed WriteShadowJob call here. // // We shouldn't need it because if the job is spooling and we // restart the spooler, we won't accept the shadow file because it's // not yet completely spooled. Once it has spooled, the EndDocPrinter // will call WriteShadowJob, so we should be fine. // AddJobEntry(pSpool->pIniPrinter, pSpool->pIniJob); // // This bit can be set in the print to file case. Clear it for // a following spool job. Bit should really be in the job. // pSpool->TypeofHandle &= ~PRINTER_HANDLE_DIRECT; SetPrinterChange(pSpool->pIniPrinter, pSpool->pIniJob, NVAddJob, PRINTER_CHANGE_ADD_JOB | PRINTER_CHANGE_SET_PRINTER, pSpool->pIniSpooler); // // RapidPrint might start despooling right away // CHECK_SCHEDULER(); LeaveSplSem(); SplOutSem(); return TRUE; } BOOL PrintingDirect( PSPOOL pSpool, DWORD Level, LPBYTE pDocInfo ) { HRESULT hRes; BOOL bRemote; PDOC_INFO_1 pDocInfo1=(PDOC_INFO_1)pDocInfo; PINIPORT pIniPort = NULL; BOOL bGoingToFile = FALSE; DWORD dwId = 0; // WMI Var DBGMSG(DBG_TRACE, ("Printing document %ws direct\n", pDocInfo1->pDocName ? pDocInfo1->pDocName : L"(Null)")); if (pDocInfo1 && pDocInfo1->pDatatype && !ValidRawDatatype(pDocInfo1->pDatatype)) { DBGMSG(DBG_WARNING, ("Datatype is not RAW\n")); SetLastError(ERROR_INVALID_DATATYPE); return FALSE; } EnterSplSem(); if (pDocInfo1 && pDocInfo1->pOutputFile && IsGoingToFile(pDocInfo1->pOutputFile, pSpool->pIniSpooler)) { bGoingToFile = TRUE; } if (bGoingToFile) { // // If we already have a thread/process printing to this filename // fail. Do not allow multiple processes/threads to write to the // same output file. // if (FindFilePort(pDocInfo1->pOutputFile, pSpool->pIniSpooler)) { LeaveSplSem(); SetLastError(ERROR_SHARING_VIOLATION); return(FALSE); } } // // WMI Trace Events // dwId = GetNextId( pSpool->pIniPrinter->pIniSpooler->hJobIdMap ); LeaveSplSem(); LogWmiTraceEvent(dwId, EVENT_TRACE_TYPE_SPL_SPOOLJOB, NULL); hRes = CheckLocalCall(); if (hRes == S_OK) { bRemote = FALSE; } else if (hRes == S_FALSE) { bRemote = TRUE; } else { SetLastError(SCODE_CODE(hRes)); return FALSE; } EnterSplSem(); pSpool->pIniJob = CreateJobEntry( pSpool, Level, pDocInfo, dwId, bRemote, JOB_DIRECT, NULL); if (!pSpool->pIniJob) { LeaveSplSem(); return FALSE; } pSpool->pIniJob->StartDocComplete = CreateEvent( NULL, EVENT_RESET_AUTOMATIC, EVENT_INITIAL_STATE_NOT_SIGNALED, NULL ); pSpool->pIniJob->WaitForWrite = CreateEvent( NULL, EVENT_RESET_AUTOMATIC, EVENT_INITIAL_STATE_NOT_SIGNALED, NULL ); pSpool->pIniJob->WaitForRead = CreateEvent( NULL, EVENT_RESET_AUTOMATIC, EVENT_INITIAL_STATE_NOT_SIGNALED, NULL ); AddJobEntry(pSpool->pIniPrinter, pSpool->pIniJob); pSpool->TypeofHandle |= PRINTER_HANDLE_DIRECT; if (bGoingToFile) { PINIMONITOR pIniMonitor; InterlockedOr((LONG*)&(pSpool->pIniJob->Status), JOB_PRINT_TO_FILE); pIniMonitor = FindFilePortMonitor( pSpool->pIniSpooler ); pIniPort = CreatePortEntry( pSpool->pIniJob->pOutputFile, pIniMonitor, pSpool->pIniSpooler); if (!pIniPort) { DECJOBREF(pSpool->pIniJob); DeleteJob(pSpool->pIniJob, NO_BROADCAST); LeaveSplSem(); return FALSE; } pIniPort->Status |= PP_FILE; AddIniPrinterToIniPort(pIniPort, pSpool->pIniPrinter); } CHECK_SCHEDULER(); if (pSpool->pIniJob->pIniPort) { SplInSem(); InterlockedOr((LONG*)&(pSpool->pIniJob->Status), JOB_PRINTING); } SetPrinterChange(pSpool->pIniPrinter, pSpool->pIniJob, NVAddJob, PRINTER_CHANGE_ADD_JOB | PRINTER_CHANGE_SET_PRINTER, pSpool->pIniSpooler); LeaveSplSem(); SplOutSem(); // // Wait until the port thread calls StartDocPrinter through // the print processor: // DBGMSG(DBG_PORT, ("PrintingDirect: Calling WaitForSingleObject( %x )\n", pSpool->pIniJob->StartDocComplete)); WaitForSingleObject( pSpool->pIniJob->StartDocComplete, INFINITE ); EnterSplSem(); // // Close the event and set its value to NULL. // If anything goes wrong, or if the job gets cancelled, // the port thread will check this event, and if it's non-NULL, // it will set it to allow this thread to wake up. // DBGMSG(DBG_PORT, ("PrintingDirect: Calling CloseHandle( %x )\n", pSpool->pIniJob->StartDocComplete)); CloseHandle(pSpool->pIniJob->StartDocComplete); pSpool->pIniJob->StartDocComplete = NULL; // // If an error occurred, set the error on this thread: // if (pSpool->pIniJob->StartDocError) { SetLastError(pSpool->pIniJob->StartDocError); // We have to decrement by 2 because we've just created this job // in CreateJobEntry setting it to 1 and the other thread who // actually failed the StartDoc above (PortThread) did // not know to blow away the job. He just failed the StartDocPort. // No, we don't have to decrement by 2 because the PortThread // decrement does go through, am restoring to decrement by 1 SPLASSERT(pSpool->pIniJob->cRef != 0); DECJOBREF(pSpool->pIniJob); DeleteJobCheck(pSpool->pIniJob); DBGMSG(DBG_TRACE, ("PrintingDirect:cRef %d\n", pSpool->pIniJob->cRef)); LeaveSplSem(); return FALSE; } LeaveSplSem(); return TRUE; } VOID ClearJobError( PINIJOB pIniJob ) /*++ Routine Description: Clears the error status bits of a job. This routine should be called when port monitor successfully sends bytes to the printer. Arguments: pIniJob - Job in error state that should be cleared. Return Value: --*/ { SplInSem(); InterlockedAnd((LONG*)&(pIniJob->Status), ~(JOB_PAPEROUT | JOB_OFFLINE | JOB_ERROR)); SetPrinterChange( pIniJob->pIniPrinter, pIniJob, NVJobStatus, PRINTER_CHANGE_SET_JOB, pIniJob->pIniPrinter->pIniSpooler ); } BOOL LocalCloseSpoolFileHandle( HANDLE hPrinter) /*++ Function Description: Sets the end of file pointer for the spool file. In memory mapped writes the spool size grows in 64K chunks and it needs to be truncated after the writes are completed. Parameters: hPrinter -- printer handle Return Values: TRUE if successful; FALSE otherwise --*/ { BOOL bReturn = TRUE; DWORD LastError = ERROR_SUCCESS; PSPOOL pSpool = (PSPOOL) hPrinter; EnterSplSem(); // // Check handle validity // if (!ValidateSpoolHandle(pSpool, PRINTER_HANDLE_SERVER)) { LastError = ERROR_INVALID_HANDLE; } else if (!(pSpool->Status & SPOOL_STATUS_STARTDOC) || (pSpool->Status & SPOOL_STATUS_ADDJOB)) { LastError = ERROR_SPL_NO_STARTDOC; } else if (!pSpool->pIniJob || (pSpool->pIniJob->hWriteFile == INVALID_HANDLE_VALUE) || (pSpool->TypeofHandle & (PRINTER_HANDLE_PORT | PRINTER_HANDLE_DIRECT)) || !(pSpool->pIniJob->Status & JOB_TYPE_OPTIMIZE)) { LastError = ERROR_INVALID_HANDLE; } else if ((pSpool->Status & SPOOL_STATUS_CANCELLED) && (pSpool->pIniJob->Status & (JOB_PENDING_DELETION))) { LastError = ERROR_PRINT_CANCELLED; } if (LastError) { SetLastError(LastError); bReturn = FALSE; goto CleanUp; } if (!(pSpool->pIniJob->Status & JOB_DESPOOLING)) { // // Needed so that SetFilePointer treats the second arg as unsigned value // LONG FileSizeHigh = 0; // // Move the file pointer to the number of bytes committed and set the end of file. // if (SetFilePointer(pSpool->pIniJob->hWriteFile, pSpool->pIniJob->dwValidSize, &FileSizeHigh, FILE_BEGIN) != 0xffffffff) { SetEndOfFile(pSpool->pIniJob->hWriteFile); } } CleanUp: LeaveSplSem(); return bReturn; } BOOL LocalCommitSpoolData( HANDLE hPrinter, DWORD cbCommit) /*++ Function Description: This function updates the Valid data size in the spool file. The application writes directly into the spool file and commits the data written using CommitSpoolData. Parameters: hPrinter -- printer handle cbCommit -- number of bytes to be committed Return Values: TRUE if successful; FALSE otherwise --*/ { BOOL bReturn = TRUE; DWORD LastError = ERROR_SUCCESS, dwPosition; PSPOOL pSpool = (PSPOOL) hPrinter; PINIJOB pIniJob = NULL, pChainedJob; if (!cbCommit) { return bReturn; } EnterSplSem(); // // Check handle validity // if (!ValidateSpoolHandle(pSpool, PRINTER_HANDLE_SERVER)) { LastError = ERROR_INVALID_HANDLE; } else if (!(pSpool->Status & SPOOL_STATUS_STARTDOC) || (pSpool->Status & SPOOL_STATUS_ADDJOB)) { LastError = ERROR_SPL_NO_STARTDOC; } else if (!pSpool->pIniJob || (pSpool->pIniJob->hWriteFile == INVALID_HANDLE_VALUE) || (pSpool->TypeofHandle & (PRINTER_HANDLE_PORT| PRINTER_HANDLE_DIRECT)) || !(pSpool->pIniJob->Status & JOB_TYPE_OPTIMIZE)) { LastError = ERROR_INVALID_HANDLE; } else if ((pSpool->Status & SPOOL_STATUS_CANCELLED) || (pSpool->pIniJob->Status & (JOB_PENDING_DELETION))) { LastError = ERROR_PRINT_CANCELLED; } pIniJob = pSpool->pIniJob; if (pIniJob->dwValidSize > pIniJob->dwValidSize + cbCommit && !ValidRawDatatype(pIniJob->pDatatype)) { LastError = ERROR_ARITHMETIC_OVERFLOW; } if (LastError) { SetLastError(LastError); bReturn = FALSE; goto CleanUp; } pIniJob->dwValidSize += cbCommit; pIniJob->Size += cbCommit; SetFilePointer(pIniJob->hWriteFile, cbCommit, NULL, FILE_CURRENT); // Chained job size include all the jobs in the chain // But since the next jobs size field will have the size // of all subsequent jobs we do not need to walk thru the // whole chain if (pIniJob->NextJobId) { if (pChainedJob = FindJob(pSpool->pIniPrinter, pIniJob->NextJobId, &dwPosition)) { pIniJob->Size += pChainedJob->Size; } else { SPLASSERT(pChainedJob != NULL); } } // SetEvent on WaitForSeek if sufficient number bytes have been written out. SeekPrinterSetEvent(pSpool->pIniJob, NULL, FALSE); // For Printing whilst Despooling, make sure we have enough bytes before // scheduling this job if (((pIniJob->dwValidSize - cbCommit) < dwFastPrintSlowDownThreshold) && (pIniJob->dwValidSize >= dwFastPrintSlowDownThreshold) && (pIniJob->WaitForWrite == NULL)) { CHECK_SCHEDULER(); } // Support for despooling whilst spooling if ( pIniJob->WaitForWrite != NULL ) SetEvent( pIniJob->WaitForWrite ); SetPrinterChange(pSpool->pIniPrinter, pIniJob, NVSpoolJob, PRINTER_CHANGE_WRITE_JOB, pSpool->pIniSpooler); // If there was no error, and the job was marked in an error // state, clear it. if (pIniJob->Status & (JOB_PAPEROUT | JOB_OFFLINE | JOB_ERROR)) { ClearJobError(pIniJob); } CleanUp: LeaveSplSem(); return bReturn; } BOOL LocalGetSpoolFileHandle( HANDLE hPrinter, LPWSTR *pSpoolDir, LPHANDLE phFile, HANDLE hSpoolerProcess, HANDLE hAppProcess) /*++ Function Description: This function duplicates the spoolfile handle for local jobs into the applications process space. For remote jobs it returns the spool directory. The router will create a temp file and return its handle to the application. Parameters: hPrinter -- printer handle pSpoolDir -- pointer to recieve the spool directory phFile -- pointer to get the duplicate handle hSpoolerProcess -- spooler process handle hAppProcess -- application process handle Return Values: TRUE if the LOCAL job and handle can be duplicated OR REMOTE job and spool directory is available FALSE otherwise --*/ { BOOL bReturn = TRUE, bDuplicate; PSPOOL pSpool; DWORD LastError = 0; PMAPPED_JOB pMappedJob = NULL, pTempMappedJob; LPWSTR pszSpoolFile = NULL; if (pSpoolDir) { *pSpoolDir = NULL; } if (phFile) { *phFile = INVALID_HANDLE_VALUE; } if ((hPrinter && !phFile) || (!hPrinter && !pSpoolDir)) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } EnterSplSem(); // For a local hPrinter return the SpoolFile handle if (hPrinter) { pSpool = (PSPOOL) hPrinter; // Check handle validity if (!ValidateSpoolHandle(pSpool, PRINTER_HANDLE_SERVER)) { LastError = ERROR_INVALID_HANDLE; } else if (!(pSpool->Status & SPOOL_STATUS_STARTDOC) || (pSpool->Status & SPOOL_STATUS_ADDJOB)) { LastError = ERROR_SPL_NO_STARTDOC; } else if (!pSpool->pIniJob || (pSpool->pIniJob->hWriteFile == INVALID_HANDLE_VALUE) || (pSpool->TypeofHandle & (PRINTER_HANDLE_PORT | PRINTER_HANDLE_DIRECT))) { LastError = ERROR_INVALID_HANDLE; } else if ((pSpool->Status & SPOOL_STATUS_CANCELLED) && (pSpool->pIniJob->Status & JOB_PENDING_DELETION)) { LastError = ERROR_PRINT_CANCELLED; } if (LastError) { SetLastError(LastError); bReturn = FALSE; } else { // // Duplicate hWriteFile into the App process // bReturn = ((pMappedJob = AllocSplMem(sizeof( MAPPED_JOB ))) != NULL) && ((pszSpoolFile = AllocSplMem(MAX_PATH * sizeof(WCHAR))) != NULL) && DuplicateHandle(hSpoolerProcess, pSpool->pIniJob->hWriteFile, hAppProcess, phFile, 0, TRUE, DUPLICATE_SAME_ACCESS); if (bReturn) { // // Store the jobid and the spool file name in pSpool, so that in the event // that EndDoc is not called by the application/GDI, the spooler can delete the // spool file and free the job id from the id map on ClosePrinter. // InterlockedOr((LONG*)&(pSpool->pIniJob->Status), JOB_TYPE_OPTIMIZE); if (pSpool->pIniJob->hFileItem != INVALID_HANDLE_VALUE) { bReturn = BoolFromHResult(StringCchCopy(pszSpoolFile, MAX_PATH, pSpool->pIniJob->pszSplFileName)); // // If a spool file handle has been duplicated, then // recycling it posses a security risk. So, set the // file item to not recycle. This doesn't affect the // server side because it used SplReadPrinter instead. // if (bReturn) { bReturn = BoolFromHResult(SetFileItemState(pSpool->pIniJob->hFileItem, kDontRecycle)); } } else { GetFullNameFromId(pSpool->pIniJob->pIniPrinter, pSpool->pIniJob->JobId, TRUE, pszSpoolFile, MAX_PATH, FALSE); } } if (bReturn) { // // Avoid duplicate entries in the pSpool->pMappedJob list // bDuplicate = FALSE; for (pTempMappedJob = pSpool->pMappedJob; pTempMappedJob; pTempMappedJob = pTempMappedJob->pNext) { if (pTempMappedJob->JobId == pSpool->pIniJob->JobId) { pTempMappedJob->fStatus |= kMappedJobSpoolFileObtained; bDuplicate = TRUE; break; } } if (!bDuplicate) { pMappedJob->pszSpoolFile = pszSpoolFile; pMappedJob->JobId = pSpool->pIniJob->JobId; pMappedJob->fStatus = kMappedJobSpoolFileObtained; pMappedJob->pNext = pSpool->pMappedJob; pSpool->pMappedJob = pMappedJob; } else { FreeSplMem(pszSpoolFile); FreeSplMem(pMappedJob); } } } } else { // // Use the default spool dir or spool\Printers // if (pLocalIniSpooler->pDefaultSpoolDir) { *pSpoolDir = AllocSplStr(pLocalIniSpooler->pDefaultSpoolDir); bReturn = *pSpoolDir != NULL; } else if (pLocalIniSpooler->pDir) { bReturn = BoolFromStatus(StrCatAlloc(pSpoolDir, pLocalIniSpooler->pDir, L"\\", szPrinterDir, NULL)); } } LeaveSplSem(); if (!bReturn) { if (pMappedJob) { FreeSplMem(pMappedJob); } if (pszSpoolFile) { FreeSplMem(pszSpoolFile); } } return bReturn; } BOOL LocalFlushPrinter( HANDLE hPrinter, LPVOID pBuf, DWORD cbBuf, LPDWORD pcWritten, DWORD cSleep ) /*++ Function Description: FlushPrinter is typically used by the driver to send a burst of zeros to the printer and introduce a delay in the i/o line to the printer. The spooler does not schedule any job for cSleep milliseconds. The driver can call FlushPrinter several times to have a cumulative effect. The printer could be sleeping for a long time, but that is acceptable since the driver is authenticated to keep doing WritePrinters indefinitely. Thus FlushPrinter does not introduce any security loophole. Parameters: hPrinter - printer handle pBuf - buffer to be sent to the printer cbBuf - size of the buffer pcWritten - pointer to return the number of bytes written cSleep - sleep time in milliseconds. Return Values: TRUE if successful; FALSE otherwise --*/ { BOOL bReturn = FALSE; DWORD CurrentTime; PSPOOL pSpool = (PSPOOL)hPrinter; PINIPORT pIniPort; PINIMONITOR pIniMonitor; EnterSplSem(); // // Validate parameters // if (!pcWritten || (cbBuf && !pBuf)) { SetLastError( ERROR_INVALID_PARAMETER ); goto CleanUp; } // // FlushPrinter can be called only for port handles where a prior call to WritePrinter // failed // if (!ValidateSpoolHandle( pSpool, PRINTER_HANDLE_SERVER ) || !(pSpool->TypeofHandle & PRINTER_HANDLE_PORT) || !(pSpool->Status & SPOOL_STATUS_FLUSH_PRINTER)) { SetLastError( ERROR_INVALID_HANDLE ); goto CleanUp; } // // Send the contents of the buffer to the port with a monitor. It doesn't make sense for // file ports or masq printers. // pIniPort = pSpool->pIniPort; if ( pIniPort && pIniPort->Status & PP_ERROR) { // // Don't send more data to a printer who's writing to a port in error // state. LocalFlushPrinter is called in order to reset the printer // By doing this, when printer buffer gets full, the writing will // hung and job cannot be restarted/deleted anymore // SetLastError( ERROR_PRINT_CANCELLED ); goto CleanUp; } if (pIniPort && (pIniPort->Status & PP_MONITOR)) { HANDLE hMonitor = GetMonitorHandle(pIniPort); pIniMonitor = GetOpenedMonitor(pIniPort); if (pIniMonitor && hMonitor) { *pcWritten = 0; // // LeaveSplSem before calling into the monitor // LeaveSplSem(); SplOutSem(); bReturn = (*pIniMonitor->Monitor2.pfnWritePort)( hMonitor, pBuf, cbBuf, pcWritten ); EnterSplSem(); } else { SetLastError(ERROR_INVALID_HANDLE); bReturn = FALSE; } } // // Update the IniPort to introduce cSleep ms delay before scheduling // if (pIniPort) { CurrentTime = GetTickCount(); if (pIniPort->bIdleTimeValid && (int)(pIniPort->IdleTime - CurrentTime) > 0) { pIniPort->IdleTime += cSleep; } else { pIniPort->IdleTime = CurrentTime + cSleep; pIniPort->bIdleTimeValid = TRUE; } } CleanUp: LeaveSplSem(); return bReturn; } VOID QuitThread( LPHANDLE phThread, DWORD dwThreadId, BOOL bInsideSplSem ) { // // This function is called on LocalWritePrinter to destroy thread created on PromptErrorMessage // if( phThread && *phThread ) { if( WAIT_TIMEOUT == WaitForSingleObject( *phThread, 0 )) { if(bInsideSplSem){ SplInSem(); LeaveSplSem(); } // // See if the thread is still running or dismissed by user. // If it is still running, wait for it to terminate before pIniJob can be freed. // MyPostThreadMessage( *phThread, dwThreadId, WM_QUIT, IDRETRY, 0 ); WaitForSingleObject( *phThread, INFINITE ); if(bInsideSplSem){ SplOutSem(); EnterSplSem(); } } CloseHandle( *phThread ); *phThread = NULL; } } BOOL LocalWritePrinter( HANDLE hPrinter, LPVOID pBuf, DWORD cbBuf, LPDWORD pcWritten ) { PSPOOL pSpool=(PSPOOL)hPrinter; PINIPORT pIniPort; DWORD cWritten, cTotal; DWORD rc; LPBYTE pByte=pBuf; DWORD LastError=0; PINIJOB pIniJob, pChainedJob; PINIMONITOR pIniMonitor; HANDLE hThread = NULL; DWORD dwThreadId=0, dwPosition; DWORD dwWaitingResult; DWORD Size = 0; *pcWritten = 0; SplOutSem(); EnterSplSem(); if (!ValidateSpoolHandle(pSpool, PRINTER_HANDLE_SERVER )) LastError = ERROR_INVALID_HANDLE; else if (!(pSpool->Status & SPOOL_STATUS_STARTDOC)) LastError = ERROR_SPL_NO_STARTDOC; else if (pSpool->Status & SPOOL_STATUS_ADDJOB) LastError = ERROR_SPL_NO_STARTDOC; else if (pSpool->pIniJob && !(pSpool->TypeofHandle & (PRINTER_HANDLE_PORT | PRINTER_HANDLE_DIRECT)) && ((pSpool->pIniJob->hWriteFile == INVALID_HANDLE_VALUE) || (pSpool->pIniJob->Status & JOB_TYPE_OPTIMIZE))) { LastError = ERROR_INVALID_HANDLE; DBGMSG( DBG_TRACE, ("LocalWritePrinter: hWriteFile == INVALID_HANDLE_VALUE hPrinter %x\n",hPrinter )); } else if (pSpool->Status & SPOOL_STATUS_CANCELLED) LastError = ERROR_PRINT_CANCELLED; else if (pSpool->pIniJob && (pSpool->pIniJob->Status & JOB_PENDING_DELETION) ) LastError = ERROR_PRINT_CANCELLED; pIniPort = pSpool->pIniPort; if (LastError) { DBGMSG(DBG_TRACE, ("WritePrinter LastError: %x hPrinter %x\n", LastError, hPrinter)); // // Mark port handles to allow FlushPrinter to be called when WritePrinter fails. // if (LastError == ERROR_PRINT_CANCELLED && pSpool->TypeofHandle & PRINTER_HANDLE_PORT) { pSpool->Status |= SPOOL_STATUS_FLUSH_PRINTER; } LeaveSplSem(); SplOutSem(); SetLastError(LastError); return FALSE; } LeaveSplSem(); SplOutSem(); // // WMI Trace Events // // The port thread is already being tracked. if (!(pSpool->TypeofHandle & PRINTER_HANDLE_PORT)) { if( pSpool->pIniJob ) { LogWmiTraceEvent(pSpool->pIniJob->JobId, EVENT_TRACE_TYPE_SPL_TRACKTHREAD, NULL); } else if ( pSpool->pIniPort && pSpool->pIniPort->pIniJob ) { LogWmiTraceEvent(pSpool->pIniPort->pIniJob->JobId, EVENT_TRACE_TYPE_SPL_TRACKTHREAD, NULL); } } cWritten = cTotal = 0; while (cbBuf) { SplOutSem(); if ( pSpool->TypeofHandle & PRINTER_HANDLE_PORT ) { // // For a print pool, check if the port is in error state and if the event that syncronizes // restarting is not null. // A more natural testing if this synchronisation must be done is by testing against // dwRestartJobOnPoolEnabled but this is bogus when dwRestartJobOnPoolEnabled is TRUE // and SNMP is disabled ( LocalSetPort is not called and the event is not created ) // EnterSplSem(); if ( (pSpool->pIniPrinter->cPorts > 1) && (pSpool->pIniPort->Status & PP_ERROR) && (pIniPort->hErrorEvent != NULL) ) { // // This event will be set on LocalSetPort when port gets into a non eror state // or when timeout and job is restarted (on another port). // Printing is cancelled if the event is not set in DelayErrorTime. // LeaveSplSem(); SplOutSem(); dwWaitingResult = WaitForSingleObject( pIniPort->hErrorEvent, pSpool->pIniSpooler->dwRestartJobOnPoolTimeout * 1000 ); EnterSplSem(); if( pSpool->pIniJob ){ pIniJob = pSpool->pIniJob; } else if( pSpool->pIniPort && pSpool->pIniPort->pIniJob ){ pIniJob = pSpool->pIniPort->pIniJob; } else { pIniJob = NULL; } // // Check if the job was be deleted or restarted // if( pIniJob && pIniJob->Status & (JOB_PENDING_DELETION | JOB_RESTART)){ // We had started a message box. See if the thread is still running or dismissed by user. // If it is still running, wait for it to terminate before pIniJob can be freed. // We need to leave the semaphore, since the UI thread we // are waiting on could need to acquire it. QuitThread( &hThread, dwThreadId, TRUE ); SetLastError( ERROR_PRINT_CANCELLED ); rc = FALSE; goto Fail; } // // If the error problem wasn't solved , set the job on error and continue // if( dwWaitingResult == WAIT_TIMEOUT ){ InterlockedOr((LONG*)&(pIniJob->Status), JOB_ERROR); SetPrinterChange( pIniJob->pIniPrinter, pIniJob, NVJobStatus, PRINTER_CHANGE_SET_JOB, pIniJob->pIniPrinter->pIniSpooler ); } } LeaveSplSem(); SplOutSem(); if ( pSpool->pIniPort->Status & PP_MONITOR ) { HANDLE hMonitor = GetMonitorHandle(pIniPort); pIniMonitor = GetOpenedMonitor(pIniPort); if (pIniMonitor && hMonitor) { SplOutSem(); cWritten = 0; rc = (*pIniMonitor->Monitor2.pfnWritePort)( hMonitor, pByte, cbBuf, &cWritten ); // // Only update if cWritten != 0. If it is zero // (for instance, when hpmon is stuck at Status // not available), then we go into a tight loop // sending out notifications. // if (cWritten) { // // For stress Test information gather the total // number of types written. // EnterSplSem(); pSpool->pIniPrinter->cTotalBytes.QuadPart = pSpool->pIniPrinter->cTotalBytes.QuadPart + cWritten; LeaveSplSem(); SplOutSem(); } else { if (rc && dwWritePrinterSleepTime) { // // Sleep to avoid consuming too much CPU. // Hpmon has this problem where they return // success, but don't write any bytes. // // Be very careful: this may get called several // times by a monitor that writes a lot of zero // bytes (perhaps at the beginning of jobs). // Sleep(dwWritePrinterSleepTime); } } } else { rc = FALSE; SetLastError(ERROR_INVALID_HANDLE); EnterSplSem(); goto Fail; } } else { DBGMSG(DBG_TRACE, ("LocalWritePrinter: Port has no monitor\n")); if (pSpool->Status & SPOOL_STATUS_PRINT_FILE) { DBGMSG(DBG_TRACE, ("LocalWritePrinter: Port has no monitor - writing to file\n")); rc = WriteFile(pSpool->hFile, pByte, cbBuf, &cWritten, NULL); } else { DBGMSG(DBG_TRACE, ("LocalWritePrinter: Port has no monitor - calling into router\n")); rc = WritePrinter(pSpool->hPort, pByte, cbBuf, &cWritten); } } } else if ( pSpool->TypeofHandle & PRINTER_HANDLE_DIRECT ) { cWritten = WriteToPrinter(pSpool, pByte, cbBuf); if (cWritten) { if (pSpool->pIniJob->Size > pSpool->pIniJob->Size + cWritten && !ValidRawDatatype(pSpool->pIniJob->pDatatype)) { SetLastError(ERROR_ARITHMETIC_OVERFLOW); rc = FALSE; EnterSplSem(); goto Fail; } pSpool->pIniJob->dwValidSize = pSpool->pIniJob->Size; pSpool->pIniJob->Size+=cWritten; EnterSplSem(); SetPrinterChange(pSpool->pIniPrinter, pSpool->pIniJob, NVSpoolJob, PRINTER_CHANGE_WRITE_JOB, pSpool->pIniSpooler); LeaveSplSem(); } SplOutSem(); rc = (BOOL)cWritten; } else { SplOutSem(); rc = WriteFile(pSpool->pIniJob->hWriteFile, pByte, cbBuf, &cWritten, NULL); if (cWritten) { DWORD FileSizeHigh = 0; Size = GetFileSize( pSpool->pIniJob->hWriteFile, &FileSizeHigh); EnterSplSem(); if (FileSizeHigh && !ValidRawDatatype(pSpool->pIniJob->pDatatype)){ SetLastError(ERROR_ARITHMETIC_OVERFLOW); rc = FALSE; goto Fail; } pSpool->pIniJob->Size = Size; // // Chained job size include all the jobs in the chain // But since the next jobs size field will have the size // of all subsequent jobs we do not need to walk thru the // whole chain // if ( pSpool->pIniJob->NextJobId ) { if ( pChainedJob = FindJob(pSpool->pIniPrinter, pSpool->pIniJob->NextJobId, &dwPosition) ) { if (pSpool->pIniJob->Size > pSpool->pIniJob->Size + pChainedJob->Size && !ValidRawDatatype(pSpool->pIniJob->pDatatype)) { SetLastError(ERROR_ARITHMETIC_OVERFLOW); rc = FALSE; goto Fail; } pSpool->pIniJob->Size += pChainedJob->Size; } else { SPLASSERT(pChainedJob != NULL); } } pSpool->pIniJob->dwValidSize = pSpool->pIniJob->Size; // SetEvent on WaitForSeek if sufficient number bytes have been written out. LeaveSplSem(); SeekPrinterSetEvent(pSpool->pIniJob, NULL, FALSE); EnterSplSem(); // // For Printing whilst Despooling, make sure we have enough bytes before // scheduling this job // if (( (pSpool->pIniJob->Size - cWritten) < dwFastPrintSlowDownThreshold ) && ( pSpool->pIniJob->Size >= dwFastPrintSlowDownThreshold ) && ( pSpool->pIniJob->WaitForWrite == NULL )) { CHECK_SCHEDULER(); } // // Support for despooling whilst spooling // if ( pSpool->pIniJob->WaitForWrite != NULL ) SetEvent( pSpool->pIniJob->WaitForWrite ); SetPrinterChange(pSpool->pIniPrinter, pSpool->pIniJob, NVSpoolJob, PRINTER_CHANGE_WRITE_JOB, pSpool->pIniSpooler); LeaveSplSem(); SplOutSem(); } } SplOutSem(); (*pcWritten)+=cWritten; cbBuf-=cWritten; pByte+=cWritten; EnterSplSem(); if( pSpool->pIniJob ){ pIniJob = pSpool->pIniJob; } else if( pSpool->pIniPort && pSpool->pIniPort->pIniJob ){ pIniJob = pSpool->pIniPort->pIniJob; } else { pIniJob = NULL; } if( pIniJob ){ if( pIniJob && pIniJob->Status & (JOB_PENDING_DELETION | JOB_RESTART) ){ // We had started a message box. See if the thread is still running or dismissed by user. // If it is still running, wait for it to terminate before pIniJob can be freed. // We need to leave the semaphore, since the UI thread we // are waiting on could need to acquire it. QuitThread( &hThread, dwThreadId, TRUE ); SetLastError( ERROR_PRINT_CANCELLED ); rc = FALSE; goto Fail; } // // If there was no error, and the job was marked in an error // state, clear it. // if( rc && ( pIniJob->Status & (JOB_PAPEROUT | JOB_OFFLINE | JOB_ERROR ))){ ClearJobError( pIniJob ); } } LeaveSplSem(); // // If we failed and we have more bytes to write, then put // up the warning. Some monitors may return FALSE, but actually // write data to the port. Therefore we need to check both rc // and also cbBuf. // if (!rc && cbBuf) { // // Warning: We are sending in a stack variable. We need to be sure // the error UI thread is cleaned up before LocalWritePrinter() // returns! // if( PromptWriteError( pSpool, &hThread, &dwThreadId ) == IDCANCEL ) { // // In this case I know thread will die by itself // CloseHandle(hThread); hThread = NULL; EnterSplSem(); goto Fail; } } else { // We have started a message box and now the automatically // retry has succeeded, we need to kill the message box // and continue to print. QuitThread( &hThread, dwThreadId, FALSE ); } } rc = TRUE; EnterSplSem(); Fail: SplInSem(); // // Mark port handles to allow FlushPrinter to be called when WritePrinter fails. // if (!rc && (pSpool->TypeofHandle & PRINTER_HANDLE_PORT)) { pSpool->Status |= SPOOL_STATUS_FLUSH_PRINTER; } LeaveSplSem(); DBGMSG(DBG_TRACE, ("WritePrinter Written %d : %d\n", *pcWritten, rc)); SplOutSem(); SPLASSERT( hThread == NULL ); return rc; } BOOL WaitForSeekPrinter( PSPOOL pSpool, HANDLE hFile, LARGE_INTEGER liSeekFilePosition, DWORD dwMoveMethod ) /*++ Function Description: WaitForSeekPrinter waits till there is enough data written to the spool file before the file pointer can be moved. Parameters: pSpool - pointer to the SPOOL struct. hFile - handle to the file whose pointer is to be set. liDistanceToMove - Offset to move the file pointer. dwMoveMethod - position to take offset. FILE_BEGIN | FILE_CURRENT | FILE_END Return Values: TRUE for success FALSE otherwise --*/ { BOOL bWaitForWrite = FALSE, bReturn = FALSE; DWORD dwFileSizeHigh, dwFileSizeLow, dwWaitResult; LARGE_INTEGER liCurrentFilePosition; // For Print while spooling wait till sufficient number of bytes have been written. if ( pSpool->pIniJob->Status & JOB_SPOOLING ) { if ( dwMoveMethod == FILE_END ) { pSpool->pIniJob->bWaitForEnd = TRUE; bWaitForWrite = TRUE; } else { // Save the current file position. liCurrentFilePosition.QuadPart = 0; liCurrentFilePosition.u.LowPart = SetFilePointer( hFile, liCurrentFilePosition.u.LowPart, &liCurrentFilePosition.u.HighPart, FILE_CURRENT ); if ((liCurrentFilePosition.u.LowPart == 0xffffffff) && (GetLastError() != NO_ERROR)) { goto CleanUp; } // Get the current size of the file if (pSpool->pIniJob->Status & JOB_TYPE_OPTIMIZE) { dwFileSizeLow = pSpool->pIniJob->dwValidSize; dwFileSizeHigh = 0; } else { dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh); if ((dwFileSizeLow == 0xffffffff) && (GetLastError() != NO_ERROR)) { goto CleanUp; } } // Set the new file pointer. liSeekFilePosition.u.LowPart = SetFilePointer( hFile, liSeekFilePosition.u.LowPart, &liSeekFilePosition.u.HighPart, dwMoveMethod ); if ((liSeekFilePosition.u.LowPart == 0xffffffff) && (GetLastError() != NO_ERROR)) { goto CleanUp; } // Reset the file pointer using the saved current file position. liCurrentFilePosition.u.LowPart = SetFilePointer( hFile, liCurrentFilePosition.u.LowPart, &liCurrentFilePosition.u.HighPart, FILE_BEGIN ); if ((liCurrentFilePosition.u.LowPart == 0xffffffff) && (GetLastError() != NO_ERROR)) { goto CleanUp; } // Check new position of the file pointer with the current file size. if ((liSeekFilePosition.u.HighPart > (LONG)dwFileSizeHigh) || ( (liSeekFilePosition.u.HighPart == (LONG)dwFileSizeHigh) && (liSeekFilePosition.u.LowPart > dwFileSizeLow))) { // Set the fields in INIJOB. pSpool->pIniJob->liFileSeekPosn.QuadPart = liSeekFilePosition.QuadPart; bWaitForWrite = TRUE; } } if (bWaitForWrite) { // Create and wait on an event. Exit the Spooler semaphore. if (pSpool->pIniJob->WaitForSeek == NULL) { pSpool->pIniJob->WaitForSeek = CreateEvent( NULL, EVENT_RESET_AUTOMATIC, EVENT_INITIAL_STATE_NOT_SIGNALED, NULL ); } if (!pSpool->pIniJob->WaitForSeek || !ResetEvent(pSpool->pIniJob->WaitForSeek)) { pSpool->pIniJob->WaitForSeek = NULL; goto CleanUp; } pSpool->pIniJob->bWaitForSeek = TRUE; // Increment ref counts before leaving the semaphore pSpool->cRef++; INCJOBREF(pSpool->pIniJob); LeaveSplSem(); dwWaitResult = WaitForSingleObject(pSpool->pIniJob->WaitForSeek, INFINITE); EnterSplSem(); pSpool->cRef--; DECJOBREF(pSpool->pIniJob); // If wait failed or the handles are invalid fail the call if ((dwWaitResult == WAIT_FAILED) || (dwWaitResult == WAIT_TIMEOUT) || (pSpool->Status & SPOOL_STATUS_CANCELLED) || (pSpool->pIniJob->Status & (JOB_TIMEOUT | JOB_PENDING_DELETION | JOB_ABANDON | JOB_RESTART))) { goto CleanUp; } } } // Set the return value. bReturn = TRUE; CleanUp: pSpool->pIniJob->bWaitForSeek = FALSE; DeleteJobCheck(pSpool->pIniJob); return bReturn; } BOOL LocalSeekPrinter( HANDLE hPrinter, LARGE_INTEGER liDistanceToMove, PLARGE_INTEGER pliNewPointer, DWORD dwMoveMethod, BOOL bWritePrinter ) /*++ Routine Description: LocalSeekPrinter moves the file pointer in the spool file to the position indicated by liDistanceToMove. This call is synchronous and it waits if the job is being spooled and the required number of bytes have not been written as yet. Arguments: hPrinter - handle to the Printer. liDistanceToMove - offset to move the file pointer. pliNewPointer - pointer to a LARGE_INTEGER which will contain the new position of the file pointer. dwMoveMethod - position to take offset. FILE_BEGIN | FILE_CURRENT | FILE_END Return Value: TRUE if the file pointer can be moved to the required location FALSE otherwise. --*/ { PSPOOL pSpool = (PSPOOL)hPrinter; HANDLE hFile; BOOL bReturn = FALSE; DWORD dwFileSizeHigh, dwFileSizeLow; SplOutSem(); EnterSplSem(); // Check for handle validity if( !ValidateSpoolHandle( pSpool, PRINTER_HANDLE_SERVER )){ DBGMSG( DBG_WARNING, ("LocalSeekPrinter ERROR_INVALID_HANDLE\n")); goto CleanUp; } if( pSpool->Status & SPOOL_STATUS_CANCELLED ){ DBGMSG( DBG_WARNING, ("LocalSeekPrinter ERROR_PRINT_CANCELLED\n")); SetLastError( ERROR_PRINT_CANCELLED ); goto CleanUp; } if( !( pSpool->TypeofHandle & PRINTER_HANDLE_JOB ) || ( pSpool->TypeofHandle & PRINTER_HANDLE_DIRECT )){ DBGMSG( DBG_WARNING, ("LocalSeekPrinter: not a job handle, or direct\n" )); SetLastError( ERROR_NOT_SUPPORTED ); goto CleanUp; } // Avoid waiting for jobs that can't print. if( !pSpool->pIniJob || pSpool->pIniJob->Status & (JOB_TIMEOUT | JOB_ABANDON | JOB_PENDING_DELETION | JOB_RESTART) ) { DBGMSG( DBG_WARNING, ("LocalSeekPrinter ERROR_PRINT_CANCELLED\n")); SetLastError( ERROR_PRINT_CANCELLED ); goto CleanUp; } // Seek fails while writing to the spool file. if( bWritePrinter ) { goto CleanUp; } else { hFile = pSpool->hReadFile; } // Wait for data to be written, if necessary. if (!WaitForSeekPrinter( pSpool, hFile, liDistanceToMove, dwMoveMethod )) { goto CleanUp; } // Set the file pointer. pliNewPointer->u.LowPart = SetFilePointer( hFile, liDistanceToMove.u.LowPart, &liDistanceToMove.u.HighPart, dwMoveMethod ); if( pliNewPointer->u.LowPart == 0xffffffff && GetLastError() != NO_ERROR ){ goto CleanUp; } pliNewPointer->u.HighPart = liDistanceToMove.u.HighPart; // // Fail the call if the pointer is moved beyond the end of file. // Get the current size of the file // if (pSpool->pIniJob->Status & JOB_TYPE_OPTIMIZE) { dwFileSizeLow = pSpool->pIniJob->dwValidSize; dwFileSizeHigh = 0; } else { dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh); if ((dwFileSizeLow == 0xffffffff) && (GetLastError() != NO_ERROR)) { goto CleanUp; } } // // Check new position of the file pointer with the current file size. // if ((pliNewPointer->u.HighPart > (LONG)dwFileSizeHigh) || ( (pliNewPointer->u.HighPart == (LONG)dwFileSizeHigh) && (pliNewPointer->u.LowPart > dwFileSizeLow))) { SetLastError(ERROR_NO_MORE_ITEMS); goto CleanUp; } bReturn = TRUE; CleanUp: LeaveSplSem(); return bReturn; } BOOL LocalEndPagePrinter( HANDLE hPrinter ) { PSPOOL pSpool = (PSPOOL)hPrinter; HANDLE hFile = INVALID_HANDLE_VALUE; DWORD dwFileSize; if (!ValidateSpoolHandle(pSpool, PRINTER_HANDLE_SERVER )) { return(FALSE); } if (pSpool->Status & SPOOL_STATUS_CANCELLED) { SetLastError(ERROR_PRINT_CANCELLED); return FALSE; } if (pSpool->pIniJob != NULL) { if ( (pSpool->TypeofHandle & PRINTER_HANDLE_PORT) && ((pSpool->pIniJob->Status & JOB_PRINTING) || (pSpool->pIniJob->Status & JOB_DESPOOLING))) { // // Despooling ( RapidPrint ) // UpdateJobAttributes(pSpool->pIniJob); pSpool->pIniJob->cLogicalPagesPrinted++; if (pSpool->pIniJob->cLogicalPagesPrinted >= pSpool->pIniJob->dwDrvNumberOfPagesPerSide) { pSpool->pIniJob->cLogicalPagesPrinted = 0; pSpool->pIniJob->cPagesPrinted++; pSpool->pIniPrinter->cTotalPagesPrinted++; } } else { // // Spooling // if ( pSpool->pIniJob->Status & JOB_TYPE_ADDJOB ) { // // If the Job is being written on the client side // the size is not getting updated so do it now on // the start page // if ( pSpool->hReadFile != INVALID_HANDLE_VALUE ) { hFile = pSpool->hReadFile; } else { hFile = pSpool->pIniJob->hWriteFile; } if ( hFile != INVALID_HANDLE_VALUE ) { dwFileSize = GetFileSize(hFile, 0); if (pSpool->pIniJob->Size < dwFileSize) { DBGMSG( DBG_TRACE, ("EndPagePrinter adjusting size old %d new %d\n", pSpool->pIniJob->Size, dwFileSize)); pSpool->pIniJob->dwValidSize = pSpool->pIniJob->Size; pSpool->pIniJob->Size = dwFileSize; // Support for despooling whilst spooling // for Down Level jobs if (pSpool->pIniJob->WaitForWrite != NULL) SetEvent( pSpool->pIniJob->WaitForWrite ); } } CHECK_SCHEDULER(); } } } else { DBGMSG(DBG_TRACE, ("LocalEndPagePrinter issued with no Job\n")); } return TRUE; } BOOL LocalAbortPrinter( HANDLE hPrinter ) { PSPOOL pSpool=(PSPOOL)hPrinter; if (!ValidateSpoolHandle(pSpool, PRINTER_HANDLE_SERVER )) { DBGMSG( DBG_WARNING, ("ERROR in AbortPrinter: %x\n", ERROR_INVALID_HANDLE)); return FALSE; } if (!(pSpool->Status & SPOOL_STATUS_STARTDOC)) { SetLastError(ERROR_SPL_NO_STARTDOC); return(FALSE); } if (pSpool->pIniPort && !(pSpool->pIniPort->Status & PP_MONITOR)) { if (pSpool->Status & SPOOL_STATUS_PRINT_FILE) { if (!CloseHandle(pSpool->hFile)) { return(FALSE); } pSpool->Status &= ~SPOOL_STATUS_PRINT_FILE; pSpool->Status |= SPOOL_STATUS_CANCELLED; return(TRUE); } else { return AbortPrinter(pSpool->hPort); } } pSpool->Status |= SPOOL_STATUS_CANCELLED; if (pSpool->TypeofHandle & PRINTER_HANDLE_PRINTER) { EnterSplSem(); if (pSpool->pIniJob) { // // Reset JOB_RESTART flag, otherwise the job doesn't get deleted. // If AbortPrinter is called while the job is restarting, DeleteJob ignores the // job if JOB_RESTART is set and the Scheduler also ignores the job since it is // marked as JOB_PENDING_DELETION. The job would stay in Deleting-Restarting forever. // InterlockedOr((LONG*)&(pSpool->pIniJob->Status), JOB_PENDING_DELETION); InterlockedAnd((LONG*)&(pSpool->pIniJob->Status), ~JOB_RESTART); // // Release any thread waiting on LocalSetPort // SetPortErrorEvent(pSpool->pIniJob->pIniPort); } LeaveSplSem(); } // // fixes bug 2646, we need to clean up AbortPrinter // rewrite so that it doesn't fail on cases which EndDocPrinter should fail // get rid of comment when done // LocalEndDocPrinter(hPrinter); return TRUE; } BOOL SplReadPrinter( HANDLE hPrinter, LPBYTE *pBuf, DWORD cbBuf ) /*++ Function Description: SplReadPrinter is an internal ReadPrinter which uses a memory mapped spool file and returns the pointer to the required data in *pBuf. Parameters: hPrinter -- printer handle *pBuf -- pointer to the buffer (mapped view) cbBuf -- number of bytes to be read Return Value: TRUE if successful; FALSE otherwise --*/ { DWORD NoBytesRead; BOOL bReturn; // // Not used currently // bReturn = InternalReadPrinter(hPrinter, NULL, cbBuf, pBuf, &NoBytesRead, TRUE); if (!bReturn && (GetLastError() == ERROR_SUCCESS)) { // // Memory mapped ReadPrinter may fail without setting the last error // SetLastError(ERROR_NOT_SUPPORTED); } return bReturn; } BOOL LocalReadPrinter( HANDLE hPrinter, LPVOID pBuf, DWORD cbBuf, LPDWORD pNoBytesRead ) /*++ Routine Description: LocalReadPrinter reads the required number of bytes (or available) into the specified buffer. Arguments: hPrinter -- printer handle pBuf -- pointer to the buffer to store data cbBuf -- number of bytes to be read pNoBytesRead -- pointer to variable to return number of bytes read Return Value: TRUE if successful; FALSE otherwise --*/ { return InternalReadPrinter(hPrinter, pBuf, cbBuf, NULL, pNoBytesRead, FALSE); } BOOL InternalReadPrinter( HANDLE hPrinter, LPVOID pBuf, DWORD cbBuf, LPBYTE *pMapBuffer, LPDWORD pNoBytesRead, BOOL bReadMappedView ) /*++ Routine Description: InternalReadPrinter reads the required number of bytes(or available) into the specified buffer or returns a pointer to the mapped file view. Arguments: hPrinter -- printer handle pBuf -- pointer to the buffer to store data cbBuf -- number of bytes to be read *pMapBuffer -- pointer to the mapped file view pNoBytesRead -- pointer to variable to return number of bytes read bReadMappedView -- flag for using mapped spool file Return Value: TRUE if successful; FALSE otherwise --*/ { PSPOOL pSpool=(PSPOOL)hPrinter; DWORD Error=0, rc; HANDLE hWait; DWORD dwFileSize = 0, dwCurrentPosition, dwOldValidSize; DWORD ThisPortSecsToWait; DWORD cbReadSize = cbBuf; DWORD SizeInFile = 0; DWORD BytesAllowedToRead = 0; NOTIFYVECTOR NotifyVector; PINIMONITOR pIniMonitor; SplOutSem(); if (!ValidateSpoolHandle(pSpool, PRINTER_HANDLE_SERVER )) { DBGMSG( DBG_WARNING, ("LocalReadPrinter ERROR_INVALID_HANDLE\n")); return FALSE; } if (pSpool->Status & SPOOL_STATUS_CANCELLED) { DBGMSG( DBG_WARNING, ("LocalReadPrinter ERROR_PRINT_CANCELLED\n")); SetLastError(ERROR_PRINT_CANCELLED); return FALSE; } if ( pNoBytesRead != NULL ) { *pNoBytesRead = 0; } if (bReadMappedView) { // // Supported only for JOB handles that aren't DIRECT // if ( !(pSpool->TypeofHandle & PRINTER_HANDLE_JOB) || pSpool->TypeofHandle & PRINTER_HANDLE_DIRECT ) { SetLastError(ERROR_NOT_SUPPORTED); return FALSE; } } if (pSpool->TypeofHandle & PRINTER_HANDLE_JOB) { if (pSpool->pIniJob->Status & (JOB_PENDING_DELETION | JOB_RESTART)) { DBGMSG( DBG_WARNING, ("LocalReadPrinter Error IniJob->Status %x\n",pSpool->pIniJob->Status)); SetLastError(ERROR_PRINT_CANCELLED); return FALSE; } if (pSpool->TypeofHandle & PRINTER_HANDLE_DIRECT) { *pNoBytesRead = ReadFromPrinter(pSpool, pBuf, cbReadSize); SplOutSem(); return TRUE; } // // Check that the user has read access. // if( !AccessGranted( SPOOLER_OBJECT_DOCUMENT, JOB_READ, pSpool )){ SetLastError( ERROR_ACCESS_DENIED ); return FALSE; } SplOutSem(); EnterSplSem(); // // RapidPrint // // NOTE this while loop is ONLY in operation if during RapidPrint // ie when we are Printing the same job we are Spooling // while (( pSpool->pIniJob->WaitForWrite != NULL ) && ( pSpool->pIniJob->Status & JOB_SPOOLING )) { // // Get the current file position. // dwCurrentPosition = SetFilePointer( pSpool->hReadFile, 0, NULL, FILE_CURRENT ); if (dwCurrentPosition < pSpool->pIniJob->dwValidSize) { // // Wait is not required // break; } SplInSem(); // // We cannot rely on pIniJob->Size to be accurate since for // downlevel jobs or jobs that to AddJob they are writing // to a file without calling WritePrinter. // So we call the file system to get an accurate file size // dwFileSize = GetFileSize( pSpool->hReadFile, 0 ); if ( pSpool->pIniJob->Size != dwFileSize ) { DBGMSG( DBG_TRACE, ("LocalReadPrinter adjusting size old %d new %d\n", pSpool->pIniJob->Size, dwFileSize)); dwOldValidSize = pSpool->pIniJob->dwValidSize; // // Fix for print while spooling. If it was AddJobed, then // the valid size is going to be the previous size, since // we know the old data will be flushed by the time the new // one is extended. // if( pSpool->pIniJob->Status & JOB_TYPE_ADDJOB ){ // // The previous size becomes the next valid size. // pSpool->pIniJob->dwValidSize = pSpool->pIniJob->Size; } else if (!(pSpool->pIniJob->Status & JOB_TYPE_OPTIMIZE)) { // // The valid size is not necessary for non-AddJob // jobs, since the write has been committed. // pSpool->pIniJob->dwValidSize = dwFileSize; } pSpool->pIniJob->Size = dwFileSize; if (dwOldValidSize != pSpool->pIniJob->dwValidSize) { SetPrinterChange(pSpool->pIniPrinter, pSpool->pIniJob, NVSpoolJob, PRINTER_CHANGE_WRITE_JOB, pSpool->pIniSpooler); } continue; } if (pSpool->pIniJob->Status & (JOB_PENDING_DELETION | JOB_RESTART | JOB_ABANDON )) { SetLastError(ERROR_PRINT_CANCELLED); LeaveSplSem(); SplOutSem(); DBGMSG( DBG_WARNING, ("LocalReadPrinter Error 2 IniJob->Status %x\n",pSpool->pIniJob->Status)); return FALSE; } // // Wait until something is written to the file // hWait = pSpool->pIniJob->WaitForWrite; ResetEvent( hWait ); DBGMSG( DBG_TRACE, ("LocalReadPrinter Waiting for Data %d milliseconds\n",dwFastPrintWaitTimeout)); LeaveSplSem(); SplOutSem(); rc = WaitForSingleObjectEx(hWait, dwFastPrintWaitTimeout, FALSE); SplOutSem(); EnterSplSem(); DBGMSG( DBG_TRACE, ("LocalReadPrinter Returned from Waiting %x\n", rc)); SPLASSERT ( pSpool->pIniJob != NULL ); // // If we did NOT timeout then we may have some Data to read // if ( rc != WAIT_TIMEOUT ) continue; // // If there are any other jobs that could be printed on // this port give up waiting. // InterlockedOr((LONG*)&(pSpool->pIniJob->Status), JOB_TIMEOUT); // Set the event for SeekPrinter to fail rendering threads SeekPrinterSetEvent(pSpool->pIniJob, NULL, TRUE); if ( NULL == AssignFreeJobToFreePort(pSpool->pIniJob->pIniPort, &ThisPortSecsToWait) ) continue; // // There is another Job waiting // Freeze this job, the user can Restart it later // InterlockedOr((LONG*)&(pSpool->pIniJob->Status), JOB_ABANDON); CloseHandle( pSpool->pIniJob->WaitForWrite ); pSpool->pIniJob->WaitForWrite = NULL; // // Assign it our Error String // ReallocSplStr(&pSpool->pIniJob->pStatus, szFastPrintTimeout); SetPrinterChange(pSpool->pIniJob->pIniPrinter, pSpool->pIniJob, NVJobStatusAndString, PRINTER_CHANGE_SET_JOB, pSpool->pIniJob->pIniPrinter->pIniSpooler ); DBGMSG( DBG_WARNING, ("LocalReadPrinter Timeout on pIniJob %x %ws %ws\n", pSpool->pIniJob, pSpool->pIniJob->pUser, pSpool->pIniJob->pDocument)); LogJobInfo( pSpool->pIniSpooler, MSG_DOCUMENT_TIMEOUT, pSpool->pIniJob->JobId, pSpool->pIniJob->pDocument, pSpool->pIniJob->pUser, pSpool->pIniJob->pIniPrinter->pName, dwFastPrintWaitTimeout ); SetLastError(ERROR_SEM_TIMEOUT); LeaveSplSem(); SplOutSem(); return FALSE; } // END WHILE InterlockedAnd((LONG*)&(pSpool->pIniJob->Status), ~( JOB_TIMEOUT | JOB_ABANDON )); // // RapidPrint // // Some printers (like HP 4si with PSCRIPT) timeout if they // don't get data, so if we fall below a threshold of data // in the spoolfile then throttle back the Reads to 1 Byte // per second until we have more data to ship to the printer // if (( pSpool->pIniJob->WaitForWrite != NULL ) && ( pSpool->pIniJob->Status & JOB_SPOOLING )) { // Get the current file position. dwCurrentPosition = SetFilePointer( pSpool->hReadFile, 0, NULL, FILE_CURRENT ); SizeInFile = pSpool->pIniJob->Size - dwCurrentPosition; if ( dwFastPrintSlowDownThreshold >= SizeInFile ) { cbReadSize = 1; hWait = pSpool->pIniJob->WaitForWrite; ResetEvent( hWait ); DBGMSG( DBG_TRACE, ("LocalReadPrinter Throttling IOs waiting %d milliseconds SizeInFile %d\n", dwFastPrintThrottleTimeout,SizeInFile)); LeaveSplSem(); SplOutSem(); rc = WaitForSingleObjectEx( hWait, dwFastPrintThrottleTimeout, FALSE ); SplOutSem(); EnterSplSem(); DBGMSG( DBG_TRACE, ("LocalReadPrinter Returned from Waiting %x\n", rc)); SPLASSERT ( pSpool->pIniJob != NULL ); } else { BytesAllowedToRead = SizeInFile - dwFastPrintSlowDownThreshold; if ( cbReadSize > BytesAllowedToRead ) { cbReadSize = BytesAllowedToRead; } } } // // A client calls AddJob to get the spool filename and // ScheduleJob when the file is completed. According to the // api spec, the spooler should not look at the job until // ScheduleJob has been called. // // However, our print while spooling implementation tries // to read the job before ScheduleJob is called. We do this // by checking if the size of the file has changed. // // This causes a problem: the server service extends the // file then writes to it. The spooler's size detection // thread sees this extension and reads the file before // the data is written, which puts garbage (zeros) into the // data stream. // // The server always extends, writes, extends, writes, etc. // The spooler can exploit the fact that they are in order // and read a write only when the file is extended again, // or the file is complete. // // Note that the API is still broken, but this fixes it // for the server service (a client could extend, extend, // write, write, which breaks this fix). // if( pSpool->pIniJob->Status & JOB_SPOOLING ){ // Get the current file position. dwCurrentPosition = SetFilePointer( pSpool->hReadFile, 0, NULL, FILE_CURRENT ); SPLASSERT( dwCurrentPosition <= pSpool->pIniJob->dwValidSize ); // // Even though the file system will satisfy a large request, limit // it to the extent of the previous (not current) write. // BytesAllowedToRead = pSpool->pIniJob->dwValidSize - dwCurrentPosition; if( cbReadSize > BytesAllowedToRead ){ cbReadSize = BytesAllowedToRead; } } LeaveSplSem(); SplOutSem(); if (bReadMappedView) { // // Mapping partial views serves no purpose, since it can't be used incrementally. // if (cbBuf != cbReadSize) { rc = FALSE; } else { rc = SetMappingPointer(pSpool, pMapBuffer, cbReadSize); } if (rc) { *pNoBytesRead = cbReadSize; } } else { rc = ReadFile( pSpool->hReadFile, pBuf, cbReadSize, pNoBytesRead, NULL); } if (!bReadMappedView) { DBGMSG( DBG_TRACE, ("LocalReadPrinter rc %x hReadFile %x pBuf %x cbReadSize %d *pNoBytesRead %d\n", rc, pSpool->hReadFile, pBuf, cbReadSize, *pNoBytesRead)); } // // Provide Feedback so user can see printing progress // on despooling, the size is updated here and not in write // printer because the journal data is larger than raw // if ( ( pSpool->pIniJob->Status & JOB_PRINTING ) && ( *pNoBytesRead != 0 )) { SplOutSem(); EnterSplSem(); dwFileSize = GetFileSize( pSpool->hReadFile, 0 ); COPYNV(NotifyVector, NVWriteJob); if ( pSpool->pIniJob->Size < dwFileSize ) { DBGMSG( DBG_TRACE, ("LocalReadPrinter 2 adjusting size old %d new %d\n", pSpool->pIniJob->Size, dwFileSize)); pSpool->pIniJob->Size = dwFileSize; ADDNV(NotifyVector, NVSpoolJob); } pSpool->pIniJob->cbPrinted += *pNoBytesRead; // // HACK !!! Each time we read from the spool file we add the // number of bytes read to pIniJob->cbPrinted. GDI will read twice certain // parts of the spool file. The spooler cannot know what is read twice, so it adds // to cbPrinted the number of bytes read at each call of this function. // This causes cbPrinted to be larger than the actual size of the spool file. // // Don't let the ratio cbPrinted/cbSize get above 1 // if (pSpool->pIniJob->cbPrinted > pSpool->pIniJob->Size) { pSpool->pIniJob->cbPrinted = pSpool->pIniJob->Size; } // // Provide Feedback to Printman that data has been // written. Note the size written is not used to // update the IniJob->cbPrinted becuase there is a // difference in size between journal data (in the // spool file) and the size of RAW bytes written to // the printer. // SetPrinterChange(pSpool->pIniPrinter, pSpool->pIniJob, NotifyVector, PRINTER_CHANGE_WRITE_JOB, pSpool->pIniSpooler); LeaveSplSem(); SplOutSem(); } } else if ( pSpool->TypeofHandle & PRINTER_HANDLE_PORT ) { // // Check if caller has read access to the currently printing job. // if (pSpool->pIniJob && !ValidateObjectAccess(SPOOLER_OBJECT_DOCUMENT, JOB_READ, pSpool->pIniJob, NULL, pSpool->pIniSpooler)) { SetLastError(ERROR_ACCESS_DENIED); return FALSE; } if (pSpool->pIniPort->Status & PP_FILE) rc = ReadFile( pSpool->hReadFile, pBuf, cbReadSize, pNoBytesRead, NULL); else if ( pSpool->pIniPort->Status & PP_MONITOR ) { HANDLE hMonitor = GetMonitorHandle(pSpool->pIniPort); pIniMonitor = GetOpenedMonitor(pSpool->pIniPort); if (pIniMonitor && hMonitor) { rc = (*pIniMonitor->Monitor2.pfnReadPort)( hMonitor, pBuf, cbReadSize, pNoBytesRead ); } else { SetLastError(ERROR_INVALID_HANDLE); rc = FALSE; } } else { rc = ReadPrinter(pSpool->hPort, pBuf, cbReadSize, pNoBytesRead); } } else { SetLastError(ERROR_INVALID_HANDLE); rc = FALSE; } SplOutSem(); DBGMSG( DBG_TRACE, ("LocalReadPrinter returns hPrinter %x pIniJob %x rc %x pNoBytesRead %d\n",hPrinter, pSpool->pIniJob, rc, *pNoBytesRead)); return rc; } LPBYTE SearchForExistingView( PSPOOL pSpool, DWORD dwRequired) /*++ Function Description -- Searches for an existing mapped view of the spool file which has the required number of bytes. Parameters -- pSpool -- Pointer to a SPOOL structure dwRequired -- Number of bytes to be mapped from the start of the page Return Value -- pointer to the start of the mapped view; NULL if the call fails. --*/ { LPBYTE pReturn = NULL; PSPLMAPVIEW pSplMapView; for (pSplMapView = pSpool->pSplMapView; pSplMapView; pSplMapView = pSplMapView->pNext) { if (dwRequired <= pSplMapView->dwMapSize) { pReturn = pSplMapView->pStartMapView; break; } } return pReturn; } LPBYTE CreateNewMapView( PSPOOL pSpool, DWORD dwRequired) /*++ Function Description -- Creates a new mapping view of the required segment of the spool file Parameters -- pSpool -- Pointer to a SPOOL structure dwRequired -- Number of bytes to be mapped from the start of the page Return Value -- pointer to the start of the mapped view; NULL if the call fails. --*/ { HANDLE hMapSpoolFile; LPBYTE pStartMapView; DWORD dwMapSize, dwFileSize; LPBYTE pReturn = NULL; PSPLMAPVIEW pSplMapView; pSplMapView = (PSPLMAPVIEW) AllocSplMem(sizeof(SPLMAPVIEW)); if (!pSplMapView) { // Allocation failed. goto CleanUp; } dwFileSize = GetFileSize(pSpool->hReadFile, NULL); pSplMapView->dwMapSize = (dwFileSize <= MAX_SPL_MAPVIEW_SIZE) ? dwFileSize : dwRequired; pSplMapView->hMapSpoolFile = NULL; pSplMapView->pStartMapView = NULL; pSplMapView->pNext = NULL; // Create file mapping pSplMapView->hMapSpoolFile = CreateFileMapping(pSpool->hReadFile, NULL, PAGE_READONLY, 0, pSplMapView->dwMapSize, NULL); if (!pSplMapView->hMapSpoolFile) { goto CleanUp; } // Map a view of the file pSplMapView->pStartMapView = (LPBYTE) MapViewOfFile(pSplMapView->hMapSpoolFile, FILE_MAP_READ, 0, 0, pSplMapView->dwMapSize); pReturn = pSplMapView->pStartMapView; CleanUp: if (!pReturn && pSplMapView) { // Free any allocated resources if (pSplMapView->pStartMapView) { UnmapViewOfFile( (LPVOID) pSplMapView->pStartMapView); } if (pSplMapView->hMapSpoolFile) { CloseHandle(pSplMapView->hMapSpoolFile); } FreeSplMem(pSplMapView); } if (pReturn) { pSplMapView->pNext = pSpool->pSplMapView; pSpool->pSplMapView = pSplMapView; } return pReturn; } BOOL SetMappingPointer( PSPOOL pSpool, LPBYTE *pMapBuffer, DWORD cbReadSize ) /*++ Function Description: SetMappingPointer creates a file mapping object and a mapped view (if necessary). If the required number of bytes are present in the view, the pointer to the data is returned in the buffer (pMappedBuffer) else the call fails. The current offset is taken from pSpool->hReadFile and if the buffer is available, the offset of hReadFile is changed correspondingly. This ensures that SplReadPrinter and ReadPrinter can be used alternately. ****Modifications required for 64 bit architecture**** Parameters: pSpool -- pointer to the SPOOL structure *pMapBuffer -- pointer to the mapped file view cbReadView -- number of bytes to be read Return Values: TRUE if successful; FALSE otherwise --*/ { BOOL bReturn = FALSE; DWORD dwCurrentPosition, dwNewPosition, dwRequired; LPBYTE pStartMapView; dwCurrentPosition = SetFilePointer(pSpool->hReadFile, 0, NULL, FILE_CURRENT); if (dwCurrentPosition == 0xffffffff && GetLastError() != NO_ERROR) { goto CleanUp; } dwRequired = dwCurrentPosition + cbReadSize; if (dwRequired > MAX_SPL_MAPVIEW_SIZE) { // Map size is insufficient; fail the call SetLastError(ERROR_NOT_SUPPORTED); goto CleanUp; } pStartMapView = SearchForExistingView(pSpool, dwRequired); if (!pStartMapView) { pStartMapView = CreateNewMapView(pSpool, dwRequired); } if (!pStartMapView) { // Required view not created goto CleanUp; } // Check for DWORD alignment if ((((ULONG_PTR) pStartMapView + dwCurrentPosition) & 3) != 0) { // Fails unaligned reads SetLastError(ERROR_MAPPED_ALIGNMENT); goto CleanUp; } dwNewPosition = SetFilePointer(pSpool->hReadFile, cbReadSize, NULL, FILE_CURRENT); if (dwNewPosition == 0xffffffff && GetLastError() != NO_ERROR) { goto CleanUp; } if (pMapBuffer) { *pMapBuffer = (LPBYTE) ((ULONG_PTR) pStartMapView + dwCurrentPosition); } bReturn = TRUE; CleanUp: // All the handles and associated resources will be freed along with pSpool return bReturn; } BOOL LocalEndDocPrinter( HANDLE hPrinter ) /*++ Routine Description: By Default the routine is in critical section. The reference counts for any object we are working on (pSpool and pIniJob) are incremented, so that when we leave critical section for lengthy operations these objects are not deleted. Arguments: Return Value: --*/ { PSPOOL pSpool=(PSPOOL)hPrinter; BOOL bReturn = TRUE; DWORD rc; PINIMONITOR pIniMonitor = NULL; HANDLE hMonitor = NULL; DWORD Position = 0; DBGMSG(DBG_TRACE, ("Entering LocalEndDocPrinter with %x\n", hPrinter)); SplOutSem(); EnterSplSem(); if (pSpool && pSpool->pIniJob && !(pSpool->TypeofHandle & PRINTER_HANDLE_PORT)) { INCJOBREF(pSpool->pIniJob); LeaveSplSem(); LogWmiTraceEvent(pSpool->pIniJob->JobId, EVENT_TRACE_TYPE_SPL_TRACKTHREAD, NULL); EnterSplSem(); DECJOBREF(pSpool->pIniJob); } if (!ValidateSpoolHandle(pSpool, PRINTER_HANDLE_SERVER )) { LeaveSplSem(); return(FALSE); } if (!(pSpool->Status & SPOOL_STATUS_STARTDOC)) { SetLastError(ERROR_SPL_NO_STARTDOC); LeaveSplSem(); return(FALSE); } if (pSpool->Status & SPOOL_STATUS_ADDJOB) { SetLastError(ERROR_SPL_NO_STARTDOC); LeaveSplSem(); return(FALSE); } if ( pSpool->pIniJob ) { pSpool->pIniJob->dwAlert |= JOB_ENDDOC_CALL; } pSpool->Status &= ~SPOOL_STATUS_STARTDOC; // // Case-1 Printer Handle is PRINTER_HANDLE_PORT // Note - there are two cases to keep in mind here // // A] The first case is the despooling thread calling // a port with a monitor - LPT1:/COM1: or any port // created by the monitor // // B] The second case is when the application thread is // doing an EndDocPrinter to a port which has no monitor // This is the local printer masquerading as a remote printer // case. Remember for this case there is no IniJob created // on the local printer at all. We just pass the call // straight to the remote printer. // if (pSpool->TypeofHandle & PRINTER_HANDLE_PORT) { SPLASSERT(!(pSpool->TypeofHandle & PRINTER_HANDLE_PRINTER)); // // Now check if this pSpool object's port has // a monitor // if ( pSpool->pIniPort->Status & PP_MONITOR ) { //Case A] // // Check if our job is really around // if (!pSpool->pIniJob) { SetLastError(ERROR_CAN_NOT_COMPLETE); bReturn = FALSE; goto CleanUp; } // // Originally we had a call to UpdateJobAttributes, but // we do not believe it is needed since it is also called during // LocalStartDocPrinter. Note that you can't change the DevMode // in SetLocalJob, so once we calculate the information, we // don't have to worry about it changing. // if (pSpool->pIniJob->cLogicalPagesPrinted) { pSpool->pIniJob->cLogicalPagesPrinted = 0; pSpool->pIniJob->cPagesPrinted++; pSpool->pIniPrinter->cTotalPagesPrinted++; SetPrinterChange(pSpool->pIniPrinter, pSpool->pIniJob, NVWriteJob, PRINTER_CHANGE_WRITE_JOB, pSpool->pIniSpooler); } // // We need to leave the spooler critical section // because we're going call into the Monitor. // so bump up ref count on pSpool and pIniJob // pSpool->cRef++; INCJOBREF(pSpool->pIniJob); hMonitor = GetMonitorHandle(pSpool->pIniPort); pIniMonitor = GetOpenedMonitor(pSpool->pIniPort); if (pIniMonitor && hMonitor) { if (pIniMonitor == pSpool->pIniPort->pIniLangMonitor) { // // If job is printing thru a language monitor we will get // SetJob with JOB_CONTROL_LAST_PAGE_EJECTED in addition to // JOB_CONTROL_SENT_TO_PRINTER // pSpool->pIniJob->dwJobControlsPending += 2; } else { pSpool->pIniJob->dwJobControlsPending++; } // // LocalEndDocPrinter can be called because a normal termination of // a job or because of a delete/restart operation. // We need to know this when the monitor sends JOB_CONTROL_LAST_PAGE_EJECTED // to make the distinction if this is a real TEOJ or not. // Since JOB_PENDING_DELETION and JOB_RESTART are cleared later on, // we'll set this special flag. // JOB_INTERRUPTED means that LocalEndDocPrinter was issued by a // was a cancel/restart action. // if (pSpool->pIniJob->Status & (JOB_PENDING_DELETION | JOB_RESTART)) { InterlockedOr((LONG*)&(pSpool->pIniJob->Status), JOB_INTERRUPTED); } LeaveSplSem(); SPLASSERT(pIniMonitor); SPLASSERT(pSpool->pIniPort->Status & PP_STARTDOC); SplOutSem(); bReturn = (*pIniMonitor->Monitor2.pfnEndDocPort)(hMonitor); EnterSplSem(); pSpool->pIniPort->Status &= ~PP_STARTDOC; pSpool->cRef--; ReleaseMonitorPort(pSpool->pIniPort); DECJOBREF(pSpool->pIniJob); } else { SetLastError(ERROR_INVALID_HANDLE); bReturn = FALSE; } goto CleanUp; } else { // Case B] // // We leave critical section here so bump pSpool object only // Note ----THERE IS NO INIJOB HERE AT ALL---Note // this call is synchronous; we will call into the router // who will then call the appropriate network print providor // e.g win32spl.dll // pSpool->cRef++; LeaveSplSem(); if (pSpool->Status & SPOOL_STATUS_PRINT_FILE) { if (!CloseHandle(pSpool->hFile)) { DBGMSG(DBG_TRACE, ("LocalEndDocPrinter: Printing to File, CloseHandle failed\n")); bReturn = FALSE; } else { DBGMSG(DBG_TRACE, ("LocalEndDocPrinter: Printing to File, CloseHandle succeeded\n")); pSpool->Status &= ~SPOOL_STATUS_PRINT_FILE; bReturn = TRUE; } } else { bReturn = (BOOL) EndDocPrinter(pSpool->hPort); } EnterSplSem(); pSpool->cRef--; goto CleanUp; } } SplInSem(); // // Case-2 Printer Handle is Direct // // // and the else clause is // // // Case-3 Printer Handle is Spooled // if (!pSpool->pIniJob) { SetLastError(ERROR_CAN_NOT_COMPLETE); bReturn = FALSE; goto CleanUp; } if (pSpool->TypeofHandle & PRINTER_HANDLE_DIRECT) { HANDLE WaitForRead = pSpool->pIniJob->WaitForRead; PINIPORT pIniPort1 = pSpool->pIniJob->pIniPort; // // Port may have been deleted by another EndDocPrinter // if (pIniPort1) { SPLASSERT(!(pSpool->TypeofHandle & PRINTER_HANDLE_PORT)); // // Printer Handle is Direct // pSpool->cRef++; INCJOBREF(pSpool->pIniJob); pIniPort1->cRef++; // // If the job was canceled by the user, there is no need to wait // for the write and read events. In certain cases when the job is // direct and canceled before LocalEndDocPrinter, those events // will never be signaled again. So waiting on them will be infinite // if (!(pSpool->pIniJob->Status & JOB_PENDING_DELETION)) { LeaveSplSem(); if( (WaitForRead != NULL) ){ WaitForSingleObject(WaitForRead, INFINITE); } pSpool->pIniJob->cbBuffer = 0; SetEvent(pSpool->pIniJob->WaitForWrite); WaitForSingleObject(pIniPort1->hPortThreadRunning, INFINITE); SplOutSem(); EnterSplSem(); } else { // // If the job is canceled, there are no more Write operations coming in. // Unlock the port thread which is waiting on this event inside ReadPrinter. // SetEvent(pSpool->pIniJob->WaitForWrite); // // Set cbBuffer on 0, since there are no more Read/Write oparatins expected. // pSpool->pIniJob->cbBuffer = 0; } pSpool->cRef--; pIniPort1->cRef--; DECJOBREF(pSpool->pIniJob); if ((pIniPort1->Status & PP_DELETING) && !pIniPort1->cRef) DeletePortEntry(pIniPort1); } } else { // // Printer Handle is Spooled // SPLASSERT(!(pSpool->TypeofHandle & PRINTER_HANDLE_PORT)); SPLASSERT(!(pSpool->TypeofHandle & PRINTER_HANDLE_DIRECT)); // // Update page count // LeaveSplSem(); UpdateJobAttributes(pSpool->pIniJob); EnterSplSem(); if (pSpool->pIniJob->cLogicalPages) { pSpool->pIniJob->cLogicalPages = 0; pSpool->pIniJob->cPages++; // // Notify the change in the page count // SetPrinterChange(pSpool->pIniPrinter, pSpool->pIniJob, NVSpoolJob, PRINTER_CHANGE_WRITE_JOB, pSpool->pIniSpooler); } if (pSpool->pIniJob->hFileItem != INVALID_HANDLE_VALUE) { // If this job is a keeper or the job is greater than 200KB and not // already printing close the write file, so that the memory from // the file can be reclaimed by the system. Without this the // spooler can eat up a lot of memory. File pooling doesn't // significantly help speed up printing of the larger files anyway. // If the printer is stopped or the job is big and not already // despooling or printing then close files, which closes the memory // mappings (buffered I/O). // Not necessarily bad, this also is true if the printer is paused. if ((PrinterStatusBad(pSpool->pIniJob->pIniPrinter->Status) || (pSpool->pIniJob->pIniPrinter->Attributes & PRINTER_ATTRIBUTE_WORK_OFFLINE)) && !(pSpool->pIniJob->Status & JOB_PRINTING) && !(pSpool->pIniJob->Status & JOB_DESPOOLING)) { LeaveSplSem(); CloseFiles(pSpool->pIniJob->hFileItem, TRUE); EnterSplSem(); } else if (!(pSpool->pIniJob->Status & JOB_PRINTING) && (pSpool->pIniJob->Size > FP_LARGE_SIZE)) { LeaveSplSem(); CloseFiles(pSpool->pIniJob->hFileItem, TRUE); EnterSplSem(); } FinishedWriting(pSpool->pIniJob->hFileItem, TRUE); pSpool->pIniJob->hWriteFile = INVALID_HANDLE_VALUE; } else if (!CloseHandle(pSpool->pIniJob->hWriteFile)) { DBGMSG(DBG_WARNING, ("CloseHandle failed %d %d\n", pSpool->pIniJob->hWriteFile, GetLastError())); } else { DBGMSG(DBG_TRACE, ("LocalEndDocPrinter: ClosedHandle Success hWriteFile\n" )); pSpool->pIniJob->hWriteFile = INVALID_HANDLE_VALUE; } // Despooling whilst spooling requires us to wake the writing // thread if it is waiting. if ( pSpool->pIniJob->WaitForWrite != NULL ) SetEvent(pSpool->pIniJob->WaitForWrite); // Set the event for SeekPrinter SeekPrinterSetEvent(pSpool->pIniJob, NULL, TRUE); } SPLASSERT(pSpool); SPLASSERT(pSpool->pIniJob); // Case 2 - (Direct) and Case 3 - (Spooled) will both execute // this block of code because both direct and spooled handles // are first and foremost PRINTER_HANDLE_PRINTER handles if (pSpool->TypeofHandle & PRINTER_HANDLE_PRINTER) { SPLASSERT(!(pSpool->TypeofHandle & PRINTER_HANDLE_PORT)); // WARNING // If pIniJob->Status has JOB_SPOOLING removed and we leave // the critical section then the scheduler thread will // Start the job printing. This could cause a problem // in that the job could be completed and deleted // before the shadow job is complete. This would lead // to access violations. SPLASSERT(pSpool); SPLASSERT(pSpool->pIniJob); if (pSpool->pIniJob->Status & JOB_SPOOLING) { InterlockedAnd((LONG*)&(pSpool->pIniJob->Status), ~JOB_SPOOLING); pSpool->pIniJob->pIniPrinter->cSpooling--; } // It looks like the ref count on the job is != 0, so the job should not // get deleted while this shadow is being written. // Having this operation in the critsec is preventing us from getting // good CPU utilization. Icecap is showing many (100+ in some cases) // other spool threads waiting on this when I push the server. WriteShadowJob(pSpool->pIniJob, TRUE); SplInSem(); // // This line of code is crucial; for timing reasons it // has been moved from the Direct (Case 2) and the // Spooled (Case 3) clauses. This decrement is for the // initial // SPLASSERT(pSpool->pIniJob->cRef != 0); DECJOBREF(pSpool->pIniJob); if (pSpool->pIniJob->Status & JOB_PENDING_DELETION) { DBGMSG(DBG_TRACE, ("EndDocPrinter: Deleting Pending Deletion Job\n")); DeleteJob(pSpool->pIniJob,BROADCAST); pSpool->pIniJob = NULL; } else { if ( pSpool->pIniJob->Status & JOB_TIMEOUT ) { InterlockedAnd((LONG*)&(pSpool->pIniJob->Status), ~( JOB_TIMEOUT | JOB_ABANDON )); FreeSplStr(pSpool->pIniJob->pStatus); pSpool->pIniJob->pStatus = NULL; } DBGMSG(DBG_TRACE, ("EndDocPrinter:PRINTER:cRef = %d\n", pSpool->pIniJob->cRef)); CHECK_SCHEDULER(); } } if (pSpool->pIniJob) { SetPrinterChange(pSpool->pIniPrinter, pSpool->pIniJob, NVJobStatus, PRINTER_CHANGE_SET_JOB, pSpool->pIniSpooler); } CleanUp: if (pSpool->pIniJob) { pSpool->pIniJob->dwAlert &= ~JOB_ENDDOC_CALL; // // WMI Trace Events // if (((pSpool->pIniJob->Status & JOB_PAUSED) || (pSpool->pIniJob->pIniPrinter->Status & PRINTER_PAUSED)) && (!((pSpool->pIniJob->Status & JOB_PRINTING) || (pSpool->pIniJob->Status & JOB_PRINTED)))) { INCJOBREF(pSpool->pIniJob); LeaveSplSem(); LogWmiTraceEvent(pSpool->pIniJob->JobId, EVENT_TRACE_TYPE_SPL_PAUSE, NULL); EnterSplSem(); DECJOBREF(pSpool->pIniJob); } DeleteJobCheck(pSpool->pIniJob); } LeaveSplSem(); SplOutSem(); return bReturn; } BOOL PrintingDirectlyToPort( PSPOOL pSpool, DWORD Level, LPBYTE pDocInfo, LPDWORD pJobId ) { PDOC_INFO_1 pDocInfo1=(PDOC_INFO_1)pDocInfo; BOOL rc = FALSE; DWORD Error; BOOL bPrinttoFile = FALSE; BOOL bErrorOccurred = FALSE; PINIMONITOR pIniMonitor = NULL, pIniLangMonitor = NULL; LPWSTR pszPrinter; HANDLE hThread = NULL; DWORD dwThreadId = 0; TCHAR szFullPrinter[MAX_UNC_PRINTER_NAME]; DWORD PortError = ERROR_SUCCESS; // // Some monitors rely on having the non-qualified name, so only // use the fully qualified name for clustered spoolers. // // This means that anyone writing a cluster aware monitor will // need to handle both types of names. // if( pSpool->pIniPrinter->pIniSpooler->SpoolerFlags & SPL_TYPE_CLUSTER ){ // // Must use fully qualified name. // StringCchPrintf(szFullPrinter, COUNTOF(szFullPrinter), L"%ws\\%ws", pSpool->pIniPrinter->pIniSpooler->pMachineName, pSpool->pIniPrinter->pName); pszPrinter = szFullPrinter; } else { // // Local name is sufficient. // pszPrinter = pSpool->pIniPrinter->pName; } DBGMSG( DBG_TRACE, ( "PrintingDirectlyToPort: Printing document %ws direct to port\n", DBGSTR( pDocInfo1->pDocName ))); if (pDocInfo1 && pDocInfo1->pDatatype && !ValidRawDatatype(pDocInfo1->pDatatype)) { // // We want to skip the error if this flags is on and there // is no monitor. // if (!(pSpool->pIniSpooler->SpoolerFlags & SPL_NON_RAW_TO_MASQ_PRINTERS && !(pSpool->pIniPort->Status & PP_MONITOR))){ DBGMSG(DBG_WARNING, ("Datatype is not RAW\n")); SetLastError(ERROR_INVALID_DATATYPE); rc = FALSE; goto CleanUp; } } if (pSpool->pIniPort->Status & PP_MONITOR) { if ( !(pSpool->pIniPort->Status & PP_FILE) && (pSpool->pIniPrinter->Attributes & PRINTER_ATTRIBUTE_ENABLE_BIDI) ) pIniLangMonitor = pSpool->pIniPrinter->pIniDriver->pIniLangMonitor; // // WARNING!! // // We should never leave this loop unless we check for the presence of the UI // thread (hThread) and make sure it has been terminated. // // do { // // This fixes Intergraph's problem -- of wanting to print // to file but their 3.1 print-processor does not pass // thru the file name. // if (pSpool->pIniJob->Status & JOB_PRINT_TO_FILE) { if ( pDocInfo1 && !pDocInfo1->pOutputFile ) { pDocInfo1->pOutputFile = pSpool->pIniJob->pOutputFile; } } // // Some monitors (LPRMON) may fail to initialize at startup // because a driver they are dependent on. // SplOutSem(); EnterSplSem(); // // Check if being deleted. // if( pSpool->pIniJob->Status & JOB_PENDING_DELETION ){ LeaveSplSem(); SetLastError(ERROR_PRINT_CANCELLED); if( hThread ) { // // See if the thread is still running or // dismissed by user. // if( WAIT_TIMEOUT == WaitForSingleObject( hThread, 0 )) { MyPostThreadMessage(hThread, dwThreadId, WM_QUIT, IDRETRY, 0); WaitForSingleObject( hThread, INFINITE ); } CloseHandle( hThread ); hThread = NULL; } rc = FALSE; goto CleanUp; } PortError = StatusFromHResult(OpenMonitorPort(pSpool->pIniPort, pIniLangMonitor, pszPrinter)); // // Chances are the port monitor doesn't work with the lang monitor, // but we still want to print. // However, if the LM says that it's busy, then don't bother to // try the monito, since that is busy as well. // if (PortError != ERROR_SUCCESS && PortError != ERROR_BUSY && pIniLangMonitor) { // // Close the monitor handle on EndDoc. // PortError = StatusFromHResult(OpenMonitorPort(pSpool->pIniPort, NULL, pszPrinter)); if (PortError == ERROR_SUCCESS) { pIniLangMonitor = NULL; } } if (PortError == ERROR_SUCCESS) { pIniMonitor = GetOpenedMonitor(pSpool->pIniPort); } LeaveSplSem(); SplOutSem(); if (PortError == ERROR_SUCCESS) { rc = (*pIniMonitor->Monitor2.pfnStartDocPort)(GetMonitorHandle(pSpool->pIniPort), pszPrinter, pSpool->pIniJob->JobId, Level, pDocInfo); if ( rc ) { pSpool->pIniPort->Status |= PP_STARTDOC; // // StartDoc successful. // if ( hThread ) { // // We have started a message box and now the // automatically retry has succeeded, we need to // kill the message box and continue to print. // // See if the thread is still running or dismissed // by user. // if ( WAIT_TIMEOUT == WaitForSingleObject( hThread, 0 )) { MyPostThreadMessage(hThread, dwThreadId, WM_QUIT, IDRETRY, 0 ); WaitForSingleObject( hThread, INFINITE ); } CloseHandle( hThread ); hThread = NULL; } } else { Error = GetLastError(); SplOutSem(); EnterSplSem(); ReleaseMonitorPort(pSpool->pIniPort); LeaveSplSem(); SplOutSem(); // // Check for pending deletion first, which prevents the // dialog from coming up if the user hits Del. // if ( (pSpool->pIniJob->Status & (JOB_PENDING_DELETION | JOB_RESTART)) || (PromptWriteError( pSpool, &hThread, &dwThreadId ) == IDCANCEL)) { if ( hThread ) { // // See if the thread is still running or // dismissed by user. // if ( WAIT_TIMEOUT == WaitForSingleObject( hThread, 0 )) { MyPostThreadMessage(hThread, dwThreadId, WM_QUIT, IDRETRY, 0 ); WaitForSingleObject( hThread, INFINITE ); } CloseHandle( hThread ); hThread = NULL; } pSpool->pIniJob->StartDocError = Error; SetLastError(ERROR_PRINT_CANCELLED); rc = FALSE; goto CleanUp; } bErrorOccurred = TRUE; } } } while (!rc); // // If an error occurred, we set some error bits in the job // status field. Clear them now since the StartDoc succeeded. // if( bErrorOccurred ){ EnterSplSem(); ClearJobError( pSpool->pIniJob ); LeaveSplSem(); } pSpool->Status |= SPOOL_STATUS_STARTDOC; if ( pIniLangMonitor ) { InterlockedOr((LONG*)&(pSpool->pIniJob->Status), JOB_TRUE_EOJ); } if ( pSpool->pIniJob->pIniPrinter->pSepFile && *pSpool->pIniJob->pIniPrinter->pSepFile) { DoSeparator(pSpool); } // Let the application's thread return from PrintingDirect: DBGMSG(DBG_PORT, ("PrintingDirectlyToPort: Calling SetEvent( %x )\n", pSpool->pIniJob->StartDocComplete)); if(pSpool->pIniJob->StartDocComplete) { if ( !SetEvent( pSpool->pIniJob->StartDocComplete ) ) { DBGMSG( DBG_WARNING, ("SetEvent( %x ) failed: Error %d\n", pSpool->pIniJob->StartDocComplete, GetLastError() )); } } } else { DBGMSG(DBG_TRACE, ("Port has no monitor: Calling StartDocPrinter or maybe printing to file\n")); EnterSplSem(); bPrinttoFile = (pDocInfo1 && IsGoingToFile(pDocInfo1->pOutputFile, pSpool->pIniSpooler)); LeaveSplSem(); if (bPrinttoFile) { HANDLE hFile = INVALID_HANDLE_VALUE; DBGMSG(DBG_TRACE, ("Port has no monitor: Printing to File %ws\n", pDocInfo1->pOutputFile)); // // This is OK since we are impersonating the user at this point. // hFile = CreateFile( pDocInfo1->pOutputFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL ); if (hFile == INVALID_HANDLE_VALUE) { DBGMSG(DBG_TRACE, ("Port has no monitor: File open failed\n")); rc = FALSE; } else { DBGMSG(DBG_TRACE, ("Port has no monitor: File open succeeded\n")); SetEndOfFile(hFile); pSpool->hFile = hFile; pSpool->Status |= SPOOL_STATUS_PRINT_FILE; // // Have to set JobId to some non zero value otherwise // StartDocPrinter expects the JobId to be off the pSpool->pIniJob // We have none so we'll access violate!! // *pJobId = TRUE; rc = TRUE; } } else { DBGMSG(DBG_TRACE, ("Port has no monitor: Calling StartDocPrinter\n")); *pJobId = StartDocPrinter(pSpool->hPort, Level, pDocInfo); rc = *pJobId != 0; } if (!rc) { DBGMSG(DBG_WARNING, ("StartDocPrinter failed: Error %d\n", GetLastError())); } } SPLASSERT( hThread == NULL ); CleanUp: return rc; } DWORD WriteToPrinter( PSPOOL pSpool, LPBYTE pByte, DWORD cbBuf ) { if( pSpool->pIniJob->WaitForRead != NULL ) { WaitForSingleObject(pSpool->pIniJob->WaitForRead, INFINITE); cbBuf = pSpool->pIniJob->cbBuffer = min(cbBuf, pSpool->pIniJob->cbBuffer); memcpy(pSpool->pIniJob->pBuffer, pByte, cbBuf); } else { pSpool->pIniJob->cbBuffer = cbBuf = 0; } SetEvent(pSpool->pIniJob->WaitForWrite); return cbBuf; } DWORD ReadFromPrinter( PSPOOL pSpool, LPBYTE pBuf, DWORD cbBuf ) { pSpool->pIniJob->pBuffer = pBuf; pSpool->pIniJob->cbBuffer = cbBuf; SetEvent(pSpool->pIniJob->WaitForRead); WaitForSingleObject(pSpool->pIniJob->WaitForWrite, INFINITE); return pSpool->pIniJob->cbBuffer; } BOOL ValidRawDatatype( LPWSTR pszDatatype) { if (!pszDatatype || _wcsnicmp(pszDatatype, szRaw, 3)) return FALSE; return TRUE; }