windows-nt/Source/XPSP1/NT/admin/netui/macprint/monitor/job.c
2020-09-26 16:20:57 +08:00

811 lines
18 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*****************************************************************/
/** Copyright(c) 1989 Microsoft Corporation. **/
/*****************************************************************/
//***
//
// Filename: job.c
//
// Description: This module contains the entry points for the AppleTalk
// monitor that manipulate jobs.
//
// The following are the functions contained in this module.
// All these functions are exported.
//
// StartDocPort
// ReadPort
// WritePort
// EndDocPort
// History:
//
// Aug 26,1992 frankb Initial version
// June 11,1993. NarenG Bug fixes/clean up
//
#include <windows.h>
#include <winspool.h>
#include <winsplp.h>
#include <winsock.h>
#include <atalkwsh.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <lmcons.h>
#include <prtdefs.h>
#include "atalkmon.h"
#include "atmonmsg.h"
#include <bltrc.h>
#include "dialogs.h"
//**
//
// Call: StartDocPort
//
// Returns: TRUE - Success
// FALSE - Failure
//
// Description:
// This routine is called by the print manager to
// mark the beginning of a job to be sent to the printer on
// this port. Any performance monitoring counts are cleared,
// a check is made to insure that the printer is still open,
//
// open issues:
//
// In order to allow for the stack to be shutdown when printing is not
// happening, the first access to the AppleTalk stack happens in this
// call. A socket is created and bound to a dynamic address, and an
// attempt to connect to the NBP name of the port is made here. If
// the connection succeeds, this routine returns TRUE. If it fails, the
// socket is cleaned up and the routine returns FALSE. It is assumed that
// Winsockets will set the appropriate Win32 failure codes.
//
// Do we want to do any performance stuff? If so, what?
//
BOOL
StartDocPort(
IN HANDLE hPort,
IN LPWSTR pPrinterName,
IN DWORD JobId,
IN DWORD Level,
IN LPBYTE pDocInfo
)
{
PATALKPORT pWalker;
PATALKPORT pPort;
DWORD dwRetCode;
DBGPRINT(("Entering StartDocPort\n")) ;
pPort = (PATALKPORT)hPort;
if (pPort == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return(FALSE);
}
//
// Make sure the job is valid and not marked for deletion
//
dwRetCode = ERROR_UNKNOWN_PORT;
WaitForSingleObject(hmutexPortList, INFINITE);
for (pWalker = pPortList; pWalker != NULL; pWalker = pWalker->pNext)
{
if (pWalker == pPort)
{
if (pWalker->fPortFlags & SFM_PORT_IN_USE)
dwRetCode = ERROR_DEVICE_IN_USE;
else
{
dwRetCode = NO_ERROR;
pWalker->fPortFlags |= SFM_PORT_IN_USE;
}
break;
}
}
ReleaseMutex(hmutexPortList);
if (dwRetCode != NO_ERROR)
{
SetLastError(dwRetCode);
return(FALSE);
}
do
{
//
// get a handle to the printer. Used to delete job and
// update job status
//
if (!OpenPrinter(pPrinterName, &(pWalker->hPrinter), NULL))
{
dwRetCode = GetLastError();
break;
}
pWalker->dwJobId = JobId;
pWalker->fJobFlags |= (SFM_JOB_FIRST_WRITE | SFM_JOB_OPEN_PENDING);
//
// open and bind status socket
//
dwRetCode = OpenAndBindAppleTalkSocket(&(pWalker->sockStatus));
if (dwRetCode != NO_ERROR)
{
ReportEvent(
hEventLog,
EVENTLOG_WARNING_TYPE,
EVENT_CATEGORY_USAGE,
EVENT_ATALKMON_STACK_NOT_STARTED,
NULL,
0,
0,
NULL,
NULL) ;
break;
}
//
// get a socket for I/O
//
dwRetCode = OpenAndBindAppleTalkSocket(&(pWalker->sockIo));
if (dwRetCode != NO_ERROR)
{
ReportEvent(
hEventLog,
EVENTLOG_WARNING_TYPE,
EVENT_CATEGORY_USAGE,
EVENT_ATALKMON_STACK_NOT_STARTED,
NULL,
0,
0,
NULL,
NULL);
break;
}
} while(FALSE);
if (dwRetCode != NO_ERROR)
{
if (pWalker->hPrinter != INVALID_HANDLE_VALUE)
ClosePrinter(pWalker->hPrinter);
if (pWalker->sockStatus != INVALID_SOCKET)
closesocket(pWalker->sockStatus);
if (pWalker->sockIo != INVALID_SOCKET)
closesocket(pWalker->sockIo);
pWalker->hPrinter = INVALID_HANDLE_VALUE;
pWalker->dwJobId = 0;
pWalker->fJobFlags = 0;
WaitForSingleObject(hmutexPortList, INFINITE);
pWalker->fPortFlags &= ~SFM_PORT_IN_USE;
ReleaseMutex(hmutexPortList);
SetLastError(dwRetCode);
return(FALSE);
}
return(TRUE);
}
//**
//
// Call: ReadPort
//
// Returns: TRUE - Success
// FALSE - Failure
//
// Description:
// Synchronously reads data from the printer.
//
// open issues:
// the DLC implementation does not implement reads.
// The local implementation implements reads with generic ReadFile
// semantics. It's not clear from the winhelp file if ReadPort
// should return an error if there is no data to read from
// the printer. Also, since PAP is read driven, there will be no
// data waiting until a read is posted. Should we pre-post a
// read on StartDocPort?
//
BOOL
ReadPort(
IN HANDLE hPort,
IN LPBYTE pBuffer,
IN DWORD cbBuffer,
IN LPDWORD pcbRead
){
DBGPRINT(("Entering ReadPort\n")) ;
//
// if data not available, wait up to a few seconds for a read to complete
//
//
// copy requested amount of data to caller's buffer
//
//
// if all data copied, post another read
//
return(TRUE);
}
//**
//
// Call: WritePort
//
// Returns: TRUE - Success
// FALSE - Failure
//
// Description:
// Synchronously writes data to the printer.
//
BOOL
WritePort(
IN HANDLE hPort,
IN LPBYTE pBuffer,
IN DWORD cbBuffer,
IN LPDWORD pcbWritten
)
{
LPBYTE pchTemp;
PATALKPORT pPort;
DWORD dwIndex;
DWORD dwRetCode;
INT wsErr;
fd_set writefds;
fd_set readfds;
struct timeval timeout;
INT Flags = 0;
LPBYTE pBufToSend;
DWORD cbTotalBytesToSend;
BOOLEAN fJobCameFromMac;
BOOLEAN fPostScriptJob;
pPort = (PATALKPORT)hPort;
// Set this to zero. We add incrementally later.
*pcbWritten = 0;
if (pPort == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return(FALSE);
}
pBufToSend = pBuffer;
cbTotalBytesToSend = cbBuffer;
//
// Maximum number of bytes we can write in one send is 4K. This is the
// limit in the AppleTalk (PAP) protocol.
//
if (cbTotalBytesToSend > 4096)
{
cbTotalBytesToSend = 4096;
}
// If we have not connected to the printer yet.
if (pPort->fJobFlags & SFM_JOB_OPEN_PENDING)
{
// Make sure that the capture thread is done with this job.
WaitForSingleObject(pPort->hmutexPort, INFINITE);
ReleaseMutex(pPort->hmutexPort);
// set status to connecting
DBGPRINT(("no connection yet, retry connect\n")) ;
dwRetCode = ConnectToPrinter(pPort, ATALKMON_DEFAULT_TIMEOUT);
if (dwRetCode != NO_ERROR)
{
DBGPRINT(("Connect returns %d\n", dwRetCode)) ;
//
// Wait 15 seconds before trying to reconnect. Each
// ConnectToPrinter does an expensive NBPLookup
//
Sleep(ATALKMON_DEFAULT_TIMEOUT*3);
*pcbWritten = 0;
return(TRUE);
}
else
{
pPort->fJobFlags &= ~SFM_JOB_OPEN_PENDING;
WaitForSingleObject(hmutexPortList, INFINITE);
pPort->fPortFlags |= SFM_PORT_POST_READ;
ReleaseMutex(hmutexPortList);
SetEvent(hevPrimeRead);
SetPrinterStatus(pPort, wchPrinting);
}
}
// if first write, determine filter control. We filter
// CTRL-D from non-mac jobs, and leave them in from Macintosh
// originated jobs
if (pPort->fJobFlags & SFM_JOB_FIRST_WRITE)
{
DBGPRINT(("first write for this job. Do filter test\n")) ;
fJobCameFromMac = IsJobFromMac(pPort);
// Consume the FILTERCONTROL string
//
// the older spoolers will put this string in: go ahead and leave
// this code in so if this job came from an older SFM spooler, we
// strip that line!
//
if ((cbTotalBytesToSend >= SIZE_FC) &&
(strncmp(pBufToSend, FILTERCONTROL, SIZE_FC) == 0))
{
*pcbWritten += SIZE_FC;
pBufToSend += SIZE_FC;
cbTotalBytesToSend -= SIZE_FC;
fJobCameFromMac = TRUE;
}
else if ((cbTotalBytesToSend >= SIZE_FCOLD) &&
strncmp(pBufToSend, FILTERCONTROL_OLD, SIZE_FCOLD) == 0)
{
*pcbWritten += SIZE_FCOLD;
pBufToSend += SIZE_FCOLD;
cbTotalBytesToSend -= SIZE_FCOLD;
fJobCameFromMac = TRUE;
}
//
// Need for hack: there are two reasons:
// 1) control characters (most commonly ctrl-d, but ctrl-c, etc. too)
// cause postscript printers to choke. we need to "filter" them out
// 2) if we're printing to a dual-mode HP printer then it's
// driver puts in a bunch of PJL commands that causes printer to go to
// postscript mode etc. It works great if this goes over lpt or com port
// but if it goes over appletalk (which is what we do) then the printer
// expects *only* postscript and seeing the PJL commands, it chokes!
// The output that goes out to the printer looks like this:
//
// <....separator page data....>
//
// $%-12345X@PJL JOB
// @PJL SET RESOLUTION=600
// @PJL ENTER LANGUAGE = POSTSCRIPT
// %!PS-Adobe-3.0
//
// <.... Postscript data....>
//
// $%-12345X@PJL EOJ
//
// (The escape character is denoted by the '$' sign above.)
// The first 3 lines and the last line are the ones that cause problem
//
// Since it's a pain in the neck to parse all of the data and try and
// remove the unwanted characters, we just prepend a few postscript
// commands to the data that tell the printer to ignore ctrl-d,
// ctrl-c etc. characters, and to ignore any line(s) starting with @PJL.
//
//
// Begin filtering hack
//
//
// make sure the string doesn't already exist (it can if the job goes
// monitor->spooler->monitor->printer instead of monitor->printer)
//
// Again, older SFM monitors would prepend this string: since we got a
// chance here, strip that out!
//
if ((cbTotalBytesToSend >= SIZE_PS_HEADER) &&
strncmp(pBufToSend, PS_HEADER, SIZE_PS_HEADER) == 0)
{
*pcbWritten += SIZE_PS_HEADER;
pBufToSend += SIZE_PS_HEADER;
cbTotalBytesToSend -= SIZE_PS_HEADER;
}
//
// WfW starts its job with a CTRL_D. Replace it with a space
//
if (pBufToSend[0] == CTRL_D)
{
*pcbWritten += 1;
pBufToSend += 1;
cbTotalBytesToSend -= 1;
}
//
// see if this job has a hdr that looks like a conventional postscript hdr
//
fPostScriptJob = TRUE;
if (cbTotalBytesToSend > 2)
{
if (pBufToSend[0] == '%' && pBufToSend[1] == '!')
{
fPostScriptJob = TRUE;
}
else
{
fPostScriptJob = FALSE;
}
}
//
// Mac always sends a postscript job. Also, we peeked at the data to
// see if we recognize a postscript hdr. If the job came from a non-Mac
// client and doesn't look like a conventional postscript job, send a
// control string telling the printer to ignore the PJL commands.
//
if (!fJobCameFromMac && !fPostScriptJob)
{
//
// Now send the PS header
//
FD_ZERO(&writefds);
FD_SET(pPort->sockIo, &writefds);
//
// can I send?
//
timeout.tv_sec = ATALKMON_DEFAULT_TIMEOUT_SEC;
timeout.tv_usec = 0;
wsErr = select(0, NULL, &writefds, NULL, &timeout);
if (wsErr == 1)
{
// can send, send the data & set return count
wsErr = send(pPort->sockIo,
PS_HEADER,
SIZE_PS_HEADER,
MSG_PARTIAL);
}
}
//
// End filtering hack
//
pPort->fJobFlags &= ~SFM_JOB_FIRST_WRITE;
}
// many postscript jobs from pc's end with a ctrl-d which we don't want to send.
// Since we are given only 1 byte and it is ctrl-d, we assume (FOR NOW) that it's the
// last byte of the job. So lie to the spooler that we sent it.
//
if (cbTotalBytesToSend == 1)
{
if (pBufToSend[0] == CTRL_D)
{
*pcbWritten = 1;
pPort->OnlyOneByteAsCtrlD++;
return(TRUE);
}
else
{
cbTotalBytesToSend += 1; // we subtract 1 in the next line, so adjust here
}
}
//
// if this job is for dual-mode printer, there is that $%-12345X@PJL EOJ command
// at the end. There is a ctrl-d just before that (which is really the end
// of the actual job).
//
if (cbTotalBytesToSend > PJL_ENDING_COMMAND_LEN)
{
if (strncmp(&pBufToSend[cbTotalBytesToSend - PJL_ENDING_COMMAND_LEN],
PJL_ENDING_COMMAND,
PJL_ENDING_COMMAND_LEN) == 0)
{
if (pBufToSend[cbTotalBytesToSend-PJL_ENDING_COMMAND_LEN-1] == CTRL_D)
{
pBufToSend[cbTotalBytesToSend-PJL_ENDING_COMMAND_LEN-1] = CR;
}
}
}
//
// send 1 less byte so eventually we'll catch the last byte (and see if it's ctrl-D)
//
cbTotalBytesToSend -= 1;
//
// Earlier we may have got just 1 byte which was ctrl-D but was not really the last byte!
// This is a very rare case, but in theory possible. If that's what happened, send
// that one ctrl-D byte now, and continue on with the rest of the job
// (Actually being paranoid here and making provision for the spooler handing us a series
// of ctrl-D bytes, 1 at a time!!)
//
if (pPort->OnlyOneByteAsCtrlD != 0)
{
BYTE TmpArray[20];
DWORD i;
i=0;
while (i < pPort->OnlyOneByteAsCtrlD)
{
TmpArray[i++] = CTRL_D;
}
FD_ZERO(&writefds);
FD_SET(pPort->sockIo, &writefds);
timeout.tv_sec = ATALKMON_DEFAULT_TIMEOUT_SEC;
timeout.tv_usec = 0;
wsErr = select(0, NULL, &writefds, NULL, &timeout);
if (wsErr == 1)
{
TmpArray[0] = CTRL_D;
wsErr = send(pPort->sockIo,
TmpArray,
pPort->OnlyOneByteAsCtrlD,
MSG_PARTIAL);
}
pPort->OnlyOneByteAsCtrlD = 0;
}
//
// can I send?
//
FD_ZERO(&writefds);
FD_SET(pPort->sockIo, &writefds);
timeout.tv_sec = ATALKMON_DEFAULT_TIMEOUT_SEC;
timeout.tv_usec = 0;
wsErr = select(0, NULL, &writefds, NULL, &timeout);
if (wsErr == 1)
{
// can send, send the data & set return count
wsErr = send(pPort->sockIo,
pBufToSend,
cbTotalBytesToSend,
MSG_PARTIAL);
if (wsErr != SOCKET_ERROR)
{
*pcbWritten += cbTotalBytesToSend;
if (pPort->fJobFlags & SFM_JOB_ERROR)
{
pPort->fJobFlags &= ~SFM_JOB_ERROR;
SetPrinterStatus(pPort, wchPrinting);
}
}
}
//
// can I read? - check for disconnect
//
FD_ZERO(&readfds);
FD_SET(pPort->sockIo, &readfds);
timeout.tv_sec = 0;
timeout.tv_usec = 0;
wsErr = select(0, &readfds, NULL, NULL, &timeout);
if (wsErr == 1)
{
wsErr = WSARecvEx(pPort->sockIo,
pPort->pReadBuffer,
PAP_DEFAULT_BUFFER,
&Flags);
if (wsErr == SOCKET_ERROR)
{
dwRetCode = GetLastError();
DBGPRINT(("recv returns %d\n", dwRetCode));
if ((dwRetCode == WSAEDISCON) || (dwRetCode == WSAENOTCONN))
{
pPort->fJobFlags |= SFM_JOB_DISCONNECTED;
//
// Try to restart the job
//
SetJob(pPort->hPrinter,
pPort->dwJobId,
0,
NULL,
JOB_CONTROL_RESTART);
SetLastError(ERROR_DEV_NOT_EXIST);
return(FALSE);
}
}
else
{
if (wsErr < PAP_DEFAULT_BUFFER)
pPort->pReadBuffer[wsErr] = '\0';
else pPort->pReadBuffer[PAP_DEFAULT_BUFFER-1] = '\0';
DBGPRINT(("recv returns %s\n", pPort->pReadBuffer));
pPort->fJobFlags |= SFM_JOB_ERROR;
ParseAndSetPrinterStatus(pPort);
}
WaitForSingleObject(hmutexPortList, INFINITE);
pPort->fPortFlags |= SFM_PORT_POST_READ;
ReleaseMutex(hmutexPortList);
SetEvent(hevPrimeRead);
}
return(TRUE);
}
//**
//
// Call: EndDocPort
//
// Returns: TRUE - Success
// FALSE - Failure
//
// Description:
// This routine is called to mark the end of the
// print job. The spool file for the job is deleted by
// this routine.
//
// open issues:
// Do we want to do performance stuff? If so, now's the time
// to save off any performance counts.
//
BOOL
EndDocPort(
IN HANDLE hPort
){
PATALKPORT pPort;
fd_set writefds;
fd_set readfds;
struct timeval timeout;
INT wsErr;
INT Flags = 0;
DBGPRINT(("Entering EndDocPort\n")) ;
pPort = (PATALKPORT)hPort;
if (pPort == NULL)
{
SetLastError(ERROR_INVALID_HANDLE);
return(FALSE);
}
//
// send the last write
//
FD_ZERO(&writefds);
FD_SET(pPort->sockIo, &writefds);
//
// If the job was not able to connect to the printer.
if ((pPort->fJobFlags & (SFM_JOB_OPEN_PENDING | SFM_JOB_DISCONNECTED)) == 0)
{
timeout.tv_sec = 90;
timeout.tv_usec = 0;
wsErr = select(0, NULL, &writefds, NULL, &timeout);
if (wsErr == 1)
{
//
// Send EOF
//
send(pPort->sockIo, NULL, 0, 0);
}
//
// Our socket is non-blocking. If we close down the socket, we could potentially
// abort the last page. A good thing to do is to wait for a reasonable amount of
// time out for the printer to send EOF, or request for more data.
//
FD_ZERO(&writefds);
FD_SET(pPort->sockIo, &writefds);
FD_ZERO(&readfds);
FD_SET(pPort->sockIo, &readfds);
timeout.tv_sec = 30;
timeout.tv_usec = 0;
wsErr = select(0, &readfds, &writefds, NULL, &timeout);
if (wsErr == 1 && FD_ISSET(pPort->sockIo, &readfds))
{
// read printer's EOF. We don't care about an error here
wsErr = WSARecvEx(pPort->sockIo, pPort->pReadBuffer, PAP_DEFAULT_BUFFER, &Flags);
}
}
//
// delete the print job
//
if (pPort->hPrinter != INVALID_HANDLE_VALUE)
{
if (!SetJob(pPort->hPrinter,
pPort->dwJobId,
0,
NULL,
JOB_CONTROL_SENT_TO_PRINTER))
DBGPRINT(("fail to setjob for delete with %d\n", GetLastError())) ;
ClosePrinter(pPort->hPrinter);
pPort->hPrinter = INVALID_HANDLE_VALUE;
}
//
// close the PAP connections
//
if (pPort->sockStatus != INVALID_SOCKET)
{
closesocket(pPort->sockStatus);
pPort->sockStatus = INVALID_SOCKET;
}
if (pPort->sockIo != INVALID_SOCKET)
{
closesocket(pPort->sockIo);
pPort->sockIo = INVALID_SOCKET;
}
pPort->dwJobId = 0;
pPort->fJobFlags = 0;
pPort->OnlyOneByteAsCtrlD = 0;
WaitForSingleObject(hmutexPortList, INFINITE);
pPort->fPortFlags &= ~SFM_PORT_IN_USE;
ReleaseMutex(hmutexPortList);
return(TRUE);
}