/*****************************************************************/ /** 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 #include #include #include #include #include #include #include #include #include #include "atalkmon.h" #include "atmonmsg.h" #include #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); }