/*++ Copyright (c) 1999 Microsoft Corporation All Rights Reserved Module Name: Usbmon.c Abstract: USBMON core port monitor routines Author: Revision History: --*/ #include "precomp.h" #include "ntddpar.h" #define LPT_NOT_ERROR 0x8 #define LPT_SELECT 0x10 #define LPT_PAPER_EMPTY 0x20 #define LPT_BENIGN_STATUS LPT_NOT_ERROR | LPT_SELECT #define MAX_TIMEOUT 300000 //5 minutes #define JOB_ABORTCHECK_TIMEOUT 5000 const TCHAR cszCFGMGR32[]=TEXT("cfgmgr32.dll"); const CHAR cszReenumFunc[]="CM_Reenumerate_DevNode_Ex"; #ifdef UNICODE const CHAR cszLocalFunc[]="CM_Locate_DevNode_ExW"; #else const CHAR cszLocalFunc[]="CM_Locate_DevNode_ExA"; #endif BOOL GetLptStatus(HANDLE hDeviceHandle,BYTE *Return); DWORD UsbmonDebug; BOOL APIENTRY DllMain( HANDLE hModule, DWORD dwReason, LPVOID lpRes ) { if ( dwReason == DLL_PROCESS_ATTACH ) DisableThreadLibraryCalls(hModule); return TRUE; } BOOL AbortThisJob(PUSBMON_PORT_INFO pPortInfo) /*++ Tells if the job should be aborted. A job should be aborted if it has been deleted or it needs to be restarted. --*/ { BOOL bRet = FALSE; DWORD dwNeeded; LPJOB_INFO_1 pJobInfo = NULL; dwNeeded = 0; GetJob(pPortInfo->hPrinter, pPortInfo->dwJobId, 1, NULL, 0, &dwNeeded); if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER ) goto Done; if ( !(pJobInfo = (LPJOB_INFO_1) AllocSplMem(dwNeeded)) || !GetJob(pPortInfo->hPrinter, pPortInfo->dwJobId, 1, (LPBYTE)pJobInfo, dwNeeded, &dwNeeded) ) goto Done; bRet = (pJobInfo->Status & JOB_STATUS_DELETING) || (pJobInfo->Status & JOB_STATUS_DELETED) || (pJobInfo->Status & JOB_STATUS_RESTART); Done: FreeSplMem(pJobInfo); return bRet; } BOOL WINAPI USBMON_OpenPort( LPTSTR pszPortName, LPHANDLE pHandle ) { PUSBMON_PORT_INFO pPortInfo, pPrev; pPortInfo = FindPort(&gUsbmonInfo, pszPortName, &pPrev); if ( pPortInfo ) { *pHandle = (LPHANDLE)pPortInfo; InitializeCriticalSection(&pPortInfo->CriticalSection); return TRUE; } else { SetLastError(ERROR_PATH_NOT_FOUND); return FALSE; } } BOOL WINAPI USBMON_ClosePort( HANDLE hPort ) { PUSBMON_PORT_INFO pPortInfo = (PUSBMON_PORT_INFO)hPort; DeleteCriticalSection(&pPortInfo->CriticalSection); return TRUE; } // // Dot4Pnp - test whether we need to force a dot4 pnp event if the dot4 stack doesn't exist. // BOOL Dot4Pnp( PUSBMON_PORT_INFO pPortInfo ) { BOOL bRet = FALSE; HANDLE hToken; DEVINST hLPTDevInst; TCHAR szPnpEntry[]=TEXT("Root\\ParallelClass\\0000"); // The pnp node to reenumerate. TCHAR cszDot4[]=TEXT("DOT4"); // This relates to the array size below. TCHAR szPort[5]; // 4 chars for "DOT4" and the ending 0. UINT uOldErrorMode; HINSTANCE hCfgMgr32 = 0; // Library instance. // Pointers to pnp functions... pfCM_Locate_DevNode_Ex pfnLocateDevNode; pfCM_Reenumerate_DevNode_Ex pfnReenumDevNode; // // Make a copy of the first 4 chars of the port name - to compare against Dot4. // Copy length of 4 chars + 1 for null. lstrcpyn( szPort, pPortInfo->szPortName, lstrlen(cszDot4)+1 ); szPort[lstrlen(cszDot4)]=0; if( lstrcmpi( szPort, cszDot4) != 0) { // // If this is not a dot4 port and we failed to open it - fail. // goto Done; } // // If it is a dot4 device we need to force a pnp event on the parallel port to get the // dot4 stack rebuilt. // If any of these fail, fail the call just as if the port couldn't be opened. // // Load the pnp dll. // uOldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); hCfgMgr32 = LoadLibrary( cszCFGMGR32 ); if(!hCfgMgr32) { SetErrorMode(uOldErrorMode); goto Done; } SetErrorMode(uOldErrorMode); // // Get the Addressed of pnp functions we want to call... // pfnLocateDevNode = (pfCM_Locate_DevNode_Ex)GetProcAddress( hCfgMgr32, cszLocalFunc ); pfnReenumDevNode = (pfCM_Reenumerate_DevNode_Ex)GetProcAddress( hCfgMgr32, cszReenumFunc ); if( !pfnLocateDevNode || !pfnReenumDevNode ) goto Done; // // We need to revert to system context here as otherwise the pnp call will fail if the user // is anything other than an admin as this requires admin rights. // If this fails, the pnp will fail anyway, so we don't need to test the return value. // hToken = RevertToPrinterSelf(); // // Reenumerate from the root of the devnode tree // if( ( pfnLocateDevNode( &hLPTDevInst, szPnpEntry, CM_LOCATE_DEVNODE_NORMAL, NULL ) != CR_SUCCESS) || ( pfnReenumDevNode( hLPTDevInst, CM_REENUMERATE_NORMAL, NULL ) != CR_SUCCESS) ) { // // Revert back to the user's context in case we failed for another reason other than // ACCESS DENIED (not admin) // ImpersonatePrinterClient(hToken); goto Done; } // // Revert back to the user's context. // ImpersonatePrinterClient(hToken); // // Try and open the port again. // If we fail, then the device must not be there any more or still switched off - fail as usual. // pPortInfo->hDeviceHandle = CreateFile(pPortInfo->szDevicePath, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if ( pPortInfo->hDeviceHandle == INVALID_HANDLE_VALUE ) goto Done; bRet = TRUE; Done: if(hCfgMgr32) FreeLibrary(hCfgMgr32); return bRet; } BOOL LocalOpenPort( PUSBMON_PORT_INFO pPortInfo ) { BOOL bRet = FALSE; EnterCriticalSection(&pPortInfo->CriticalSection); if ( pPortInfo->hDeviceHandle == INVALID_HANDLE_VALUE ) { // // If we have an invalid handle and refcount is non-zero we want the // job to fail and restart to accept writes. In other words if the // handle got closed prematurely, because of failing writes, then we // need the ref count to go down to 0 before calling CreateFile again // if ( pPortInfo->cRef ) goto Done; pPortInfo->hDeviceHandle = CreateFile(pPortInfo->szDevicePath, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); // // If we failed to open the port - test to see if it is a Dot4 port. // if ( pPortInfo->hDeviceHandle == INVALID_HANDLE_VALUE ) { // // ERROR_FILE_NOT_FOUND -> Error code for port not there. // if( ERROR_FILE_NOT_FOUND != GetLastError() || !Dot4Pnp(pPortInfo) ) goto Done; } pPortInfo->Ov.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); if ( pPortInfo->Ov.hEvent == NULL ) { CloseHandle(pPortInfo->hDeviceHandle); pPortInfo->hDeviceHandle = INVALID_HANDLE_VALUE; goto Done; } } ++(pPortInfo->cRef); bRet = TRUE; Done: LeaveCriticalSection(&pPortInfo->CriticalSection); return bRet; } BOOL LocalClosePort( PUSBMON_PORT_INFO pPortInfo ) { BOOL bRet = TRUE; BOOL bJobCanceled=FALSE; EnterCriticalSection(&pPortInfo->CriticalSection); --(pPortInfo->cRef); if ( pPortInfo->cRef != 0 ) goto Done; bRet = CloseHandle(pPortInfo->hDeviceHandle); CloseHandle(pPortInfo->Ov.hEvent); pPortInfo->hDeviceHandle = INVALID_HANDLE_VALUE; Done: LeaveCriticalSection(&pPortInfo->CriticalSection); return bRet; } VOID FreeWriteBuffer( PUSBMON_PORT_INFO pPortInfo ) { FreeSplMem(pPortInfo->pWriteBuffer); pPortInfo->pWriteBuffer=NULL; pPortInfo->dwBufferSize = pPortInfo->dwDataSize = pPortInfo->dwDataCompleted = pPortInfo->dwDataScheduled = 0; } BOOL WINAPI USBMON_StartDocPort( HANDLE hPort, LPTSTR pPrinterName, DWORD dwJobId, DWORD dwLevel, LPBYTE pDocInfo ) { BOOL bRet = FALSE; PUSBMON_PORT_INFO pPortInfo = (PUSBMON_PORT_INFO)hPort; SPLASSERT(pPortInfo->pWriteBuffer == NULL && pPortInfo->dwBufferSize == 0 && pPortInfo->dwDataSize == 0 && pPortInfo->dwDataCompleted == 0 && pPortInfo->dwDataScheduled == 0); if ( !OpenPrinter(pPrinterName, &pPortInfo->hPrinter, NULL) ) return FALSE; pPortInfo->dwJobId = dwJobId; bRet = LocalOpenPort(pPortInfo); if ( !bRet ) { if ( pPortInfo->hPrinter ) { ClosePrinter(pPortInfo->hPrinter); pPortInfo->hPrinter = NULL; } } else pPortInfo->dwFlags |= USBMON_STARTDOC; return bRet; } BOOL NeedToResubmitJob( DWORD dwLastError ) { // // I used winerror -s ntstatus to map KM error codes to user mode errors // // 5 ERROR_ACCESS_DENIED <--> c0000056 STATUS_DELETE_PENDING // 6 ERROR_INVALID_HANDLE <--> c0000008 STATUS_INVALID_HANDLE // 23 ERROR_CRC <--> 0xc000003e STATUS_DATA_ERROR // 23 ERROR_CRC <--> 0xc000003f STATUS_CRC_ERROR // 23 ERROR_CRC <--> 0xc000009c STATUS_DEVICE_DATA_ERROR // 55 ERROR_DEV_NOT_EXIST <--> c00000c0 STATUS_DEVICE_DOES_NOT_EXIST // return dwLastError == ERROR_ACCESS_DENIED || dwLastError == ERROR_INVALID_HANDLE || dwLastError == ERROR_CRC || dwLastError == ERROR_DEV_NOT_EXIST; } VOID InvalidatePortHandle( PUSBMON_PORT_INFO pPortInfo ) { SPLASSERT(pPortInfo->hDeviceHandle != INVALID_HANDLE_VALUE); CloseHandle(pPortInfo->hDeviceHandle); pPortInfo->hDeviceHandle = INVALID_HANDLE_VALUE; CloseHandle(pPortInfo->Ov.hEvent); pPortInfo->Ov.hEvent = NULL; FreeWriteBuffer(pPortInfo); } DWORD ScheduleWrite( PUSBMON_PORT_INFO pPortInfo ) /*++ Routine Description: Arguments: Return Value: ERROR_SUCCESS : Write got succesfully scheduled (may or may not have completed on return) pPortInfo->dwScheduledData is the amount that got scheduled Others: Write failed, return code is the Win32 error --*/ { DWORD dwLastError = ERROR_SUCCESS, dwDontCare; // // When a sheduled write is pending we should not try to send data // any more // SPLASSERT(pPortInfo->dwDataScheduled == 0); // // Send all the data that is not confirmed // SPLASSERT(pPortInfo->dwDataSize >= pPortInfo->dwDataCompleted); pPortInfo->dwDataScheduled = pPortInfo->dwDataSize - pPortInfo->dwDataCompleted; if ( !WriteFile(pPortInfo->hDeviceHandle, pPortInfo->pWriteBuffer + pPortInfo->dwDataCompleted, pPortInfo->dwDataScheduled, &dwDontCare, &pPortInfo->Ov) ) { if ( (dwLastError = GetLastError()) == ERROR_SUCCESS ) dwLastError = STG_E_UNKNOWN; else if ( dwLastError == ERROR_IO_PENDING ) dwLastError = ERROR_SUCCESS; } // // If scheduling of the write failed then no data is pending // if ( dwLastError != ERROR_SUCCESS ) pPortInfo->dwDataScheduled = 0; return dwLastError; } DWORD ScheduledWriteStatus( PUSBMON_PORT_INFO pPortInfo, DWORD dwTimeout ) /*++ Routine Description: Arguments: Return Value: ERROR_SUCCESS : Write got done succesfully ERROR_TIMEOUT : Timeout occured Others : Write completed with a failure --*/ { DWORD dwLastError = ERROR_SUCCESS; DWORD dwWritten = 0; SPLASSERT(pPortInfo->dwDataScheduled > 0); if ( WAIT_TIMEOUT == WaitForSingleObject(pPortInfo->Ov.hEvent, dwTimeout) ) { dwLastError = ERROR_TIMEOUT; goto Done; } if ( !GetOverlappedResult(pPortInfo->hDeviceHandle, &pPortInfo->Ov, &dwWritten, FALSE) ) { if ( (dwLastError = GetLastError()) == ERROR_SUCCESS ) dwLastError = STG_E_UNKNOWN; } ResetEvent(pPortInfo->Ov.hEvent); // // We are here because either a write completed succesfully, // or failed but the error is not serious enough to resubmit job // if ( dwWritten <= pPortInfo->dwDataScheduled ) pPortInfo->dwDataCompleted += dwWritten; else SPLASSERT(dwWritten <= pPortInfo->dwDataScheduled); pPortInfo->dwDataScheduled = 0; Done: // // Either we timed out, or write sheduled completed (success of failure) // SPLASSERT(dwLastError == ERROR_TIMEOUT || pPortInfo->dwDataScheduled == 0); return dwLastError; } BOOL WINAPI USBMON_EndDocPort( HANDLE hPort ) { PUSBMON_PORT_INFO pPortInfo = (PUSBMON_PORT_INFO)hPort; DWORD dwLastError = ERROR_SUCCESS; // // Wait for any outstanding write to complete // while ( pPortInfo->dwDataSize > pPortInfo->dwDataCompleted ) { // // If job needs to be aborted ask KM driver to cancel the I/O // if ( AbortThisJob(pPortInfo) ) { if ( pPortInfo->dwDataScheduled ) { CancelIo(pPortInfo->hDeviceHandle); dwLastError = ScheduledWriteStatus(pPortInfo, INFINITE); } goto Done; } if ( pPortInfo->dwDataScheduled ) dwLastError = ScheduledWriteStatus(pPortInfo, JOB_ABORTCHECK_TIMEOUT); else { // // If for some reason KM is failing to complete all write do not // send data in a busy loop. Use 1 sec between Writes // if ( dwLastError != ERROR_SUCCESS ) Sleep(1*1000); dwLastError = ScheduleWrite(pPortInfo); } // // Check if we can use the same handle and continue // if ( NeedToResubmitJob(dwLastError) ) { InvalidatePortHandle(pPortInfo); SetJob(pPortInfo->hPrinter, pPortInfo->dwJobId, 0, NULL, JOB_CONTROL_RESTART); goto Done; } } Done: FreeWriteBuffer(pPortInfo); pPortInfo->dwFlags &= ~USBMON_STARTDOC; LocalClosePort(pPortInfo); SetJob(pPortInfo->hPrinter, pPortInfo->dwJobId, 0, NULL, JOB_CONTROL_SENT_TO_PRINTER); ClosePrinter(pPortInfo->hPrinter); pPortInfo->hPrinter = NULL; return TRUE; } BOOL WINAPI USBMON_GetPrinterDataFromPort( HANDLE hPort, DWORD dwControlID, LPWSTR pValueName, LPWSTR lpInBuffer, DWORD cbInBuffer, LPWSTR lpOutBuffer, DWORD cbOutBuffer, LPDWORD lpcbReturned ) { BOOL bRet = FALSE; PUSBMON_PORT_INFO pPortInfo = (PUSBMON_PORT_INFO)hPort; OVERLAPPED Ov; HANDLE hDeviceHandle; DWORD dwWaitResult; *lpcbReturned = 0; if ( dwControlID == 0 ) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } ZeroMemory(&Ov, sizeof(Ov)); if ( !(Ov.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL)) ) return FALSE; if ( !LocalOpenPort(pPortInfo) ) { CloseHandle(Ov.hEvent); return FALSE; } if(dwControlID==IOCTL_PAR_QUERY_DEVICE_ID) { hDeviceHandle=CreateFile(pPortInfo->szDevicePath, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if(hDeviceHandle==INVALID_HANDLE_VALUE) goto Done; if ( !DeviceIoControl(pPortInfo->hDeviceHandle, dwControlID,lpInBuffer, cbInBuffer,lpOutBuffer, cbOutBuffer, lpcbReturned, &Ov) && GetLastError() != ERROR_IO_PENDING ) { CloseHandle(hDeviceHandle); goto Done; } if(WaitForSingleObject(Ov.hEvent,PAR_QUERY_TIMEOUT)!=WAIT_OBJECT_0) CancelIo(hDeviceHandle); bRet = GetOverlappedResult(pPortInfo->hDeviceHandle, &Ov,lpcbReturned,TRUE); CloseHandle(hDeviceHandle); } else { if ( !DeviceIoControl(pPortInfo->hDeviceHandle, dwControlID, lpInBuffer, cbInBuffer, lpOutBuffer, cbOutBuffer, lpcbReturned, &Ov) && GetLastError() != ERROR_IO_PENDING ) goto Done; bRet = GetOverlappedResult(pPortInfo->hDeviceHandle, &Ov, lpcbReturned, TRUE); } Done: CloseHandle(Ov.hEvent); LocalClosePort(pPortInfo); return bRet; } BOOL WINAPI USBMON_ReadPort( HANDLE hPort, LPBYTE pBuffer, DWORD cbBuffer, LPDWORD pcbRead ) { DWORD dwLastError = ERROR_SUCCESS; DWORD dwTimeout; HANDLE hReadHandle; OVERLAPPED Ov; PUSBMON_PORT_INFO pPortInfo = (PUSBMON_PORT_INFO)hPort; // // Create separate read handle since we have to cancel reads which do // not complete within the specified timeout without cancelling writes // hReadHandle = CreateFile(pPortInfo->szDevicePath, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if ( hReadHandle == INVALID_HANDLE_VALUE ) return FALSE; ZeroMemory(&Ov, sizeof(Ov)); if ( !(Ov.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL)) ) goto Done; if ( !ReadFile(hReadHandle, pBuffer, cbBuffer, pcbRead, &Ov) && (dwLastError = GetLastError()) != ERROR_IO_PENDING ) goto Done; dwTimeout = pPortInfo->ReadTimeoutConstant + pPortInfo->ReadTimeoutMultiplier * cbBuffer; if ( dwTimeout == 0 ) dwTimeout=MAX_TIMEOUT; if( WaitForSingleObject(Ov.hEvent, dwTimeout) == WAIT_TIMEOUT ) { CancelIo(hReadHandle); WaitForSingleObject(Ov.hEvent, INFINITE); } if( !GetOverlappedResult(hReadHandle, &Ov, pcbRead, FALSE) ) { *pcbRead = 0; dwLastError = GetLastError(); } else dwLastError = ERROR_SUCCESS; Done: if ( Ov.hEvent ) CloseHandle(Ov.hEvent); CloseHandle(hReadHandle); if ( dwLastError ) SetLastError(dwLastError); return dwLastError == ERROR_SUCCESS; } DWORD dwGetTimeLeft(DWORD dwStartTime,DWORD dwTimeout) { DWORD dwCurrentTime; DWORD dwTimeLeft; if(dwTimeout==MAX_TIMEOUT) return MAX_TIMEOUT; dwCurrentTime=GetTickCount(); if(dwTimeout<(dwCurrentTime-dwStartTime)) dwTimeLeft=0; else dwTimeLeft=dwTimeout-(dwCurrentTime-dwStartTime); return dwTimeLeft; } BOOL WINAPI USBMON_WritePort( HANDLE hPort, LPBYTE pBuffer, DWORD cbBuffer, LPDWORD pcbWritten ) { DWORD dwLastError = ERROR_SUCCESS; DWORD dwBytesLeft, dwBytesSent; DWORD dwStartTime, dwTimeLeft, dwTimeout; PUSBMON_PORT_INFO pPortInfo = (PUSBMON_PORT_INFO)hPort; BOOL bStartDoc = (pPortInfo->dwFlags & USBMON_STARTDOC) != 0; BYTE bPrinterStatus; *pcbWritten = 0; dwStartTime = GetTickCount(); dwTimeout = pPortInfo->WriteTimeoutConstant + pPortInfo->WriteTimeoutMultiplier * cbBuffer; if ( dwTimeout == 0 ) dwTimeout = MAX_TIMEOUT; // // Usbprint currently can't handle write greater than 4K. // For Win2K we will make a fix here, later usbprint will be fixed // // It is ok to change the size here since spooler will resubmit the rest // later // if ( cbBuffer > 0x1000 && !lstrncmpi(pPortInfo->szPortName, TEXT("USB"), lstrlen(TEXT("USB"))) ) cbBuffer = 0x1000; // // For writes outside startdoc/enddoc we do not carry them across WritePort // calls. These are typically from language monitors (i.e. not job data) // SPLASSERT(bStartDoc || pPortInfo->pWriteBuffer == NULL); if ( pPortInfo->hDeviceHandle == INVALID_HANDLE_VALUE ) { SetJob(pPortInfo->hPrinter, pPortInfo->dwJobId, 0, NULL, JOB_CONTROL_RESTART); SetLastError(ERROR_CANCELLED); return FALSE; } if ( !LocalOpenPort(pPortInfo) ) return FALSE; // // First complete any data from previous WritePort call // while ( pPortInfo->dwDataSize > pPortInfo->dwDataCompleted ) { if ( pPortInfo->dwDataScheduled ) { dwTimeLeft = dwGetTimeLeft(dwStartTime, dwTimeout); dwLastError = ScheduledWriteStatus(pPortInfo, dwTimeLeft); } else dwLastError = ScheduleWrite(pPortInfo); if ( dwLastError != ERROR_SUCCESS ) goto Done; } SPLASSERT(pPortInfo->dwDataSize == pPortInfo->dwDataCompleted && pPortInfo->dwDataScheduled == 0 && dwLastError == ERROR_SUCCESS); // // Copy the data to our own buffer // if ( pPortInfo->dwBufferSize < cbBuffer ) { FreeWriteBuffer(pPortInfo); if ( pPortInfo->pWriteBuffer = AllocSplMem(cbBuffer) ) pPortInfo->dwBufferSize = cbBuffer; else { dwLastError = ERROR_OUTOFMEMORY; goto Done; } } else { pPortInfo->dwDataCompleted = pPortInfo->dwDataScheduled = 0; } CopyMemory(pPortInfo->pWriteBuffer, pBuffer, cbBuffer); pPortInfo->dwDataSize = cbBuffer; // // Now do the write for the data for this WritePort call // while ( pPortInfo->dwDataSize > pPortInfo->dwDataCompleted ) { if ( pPortInfo->dwDataScheduled ) { dwTimeLeft = dwGetTimeLeft(dwStartTime, dwTimeout); dwLastError = ScheduledWriteStatus(pPortInfo, dwTimeLeft); } else dwLastError = ScheduleWrite(pPortInfo); if ( dwLastError != ERROR_SUCCESS ) break; } // // For writes outside startdoc/enddoc, which are from language monitors, // do not carry pending writes to next WritePort. // if ( !bStartDoc && pPortInfo->dwDataSize > pPortInfo->dwDataCompleted ) { CancelIo(pPortInfo->hDeviceHandle); dwLastError = ScheduledWriteStatus(pPortInfo, INFINITE); *pcbWritten = pPortInfo->dwDataCompleted; FreeWriteBuffer(pPortInfo); } // // We will tell spooler we wrote all the data if some data got scheduled // (or scheduled and completed) // if ( pPortInfo->dwDataCompleted > 0 || pPortInfo->dwDataScheduled != 0 ) *pcbWritten = cbBuffer; else FreeWriteBuffer(pPortInfo); Done: if ( NeedToResubmitJob(dwLastError) ) InvalidatePortHandle(pPortInfo); else if ( dwLastError == ERROR_TIMEOUT ) { GetLptStatus(pPortInfo->hDeviceHandle, &bPrinterStatus); if ( bPrinterStatus & LPT_PAPER_EMPTY ) dwLastError=ERROR_OUT_OF_PAPER; } LocalClosePort(pPortInfo); SetLastError(dwLastError); return dwLastError == ERROR_SUCCESS; } BOOL WINAPI USBMON_SetPortTimeOuts( HANDLE hPort, LPCOMMTIMEOUTS lpCTO, DWORD reserved ) { PUSBMON_PORT_INFO pPortInfo = (PUSBMON_PORT_INFO)hPort; pPortInfo->ReadTimeoutMultiplier = lpCTO->ReadTotalTimeoutMultiplier; pPortInfo->ReadTimeoutConstant = lpCTO->ReadTotalTimeoutConstant; pPortInfo->WriteTimeoutMultiplier = lpCTO->WriteTotalTimeoutMultiplier; pPortInfo->WriteTimeoutConstant = lpCTO->WriteTotalTimeoutConstant; return TRUE; } BOOL GetLptStatus(HANDLE hDeviceHandle,BYTE *Status) { BYTE StatusByte; OVERLAPPED Ov; BOOL bResult; DWORD dwBytesReturned; DWORD dwLastError; Ov.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); bResult=DeviceIoControl(hDeviceHandle,IOCTL_USBPRINT_GET_LPT_STATUS,NULL,0,&StatusByte,1,&dwBytesReturned,&Ov); dwLastError=GetLastError(); if((bResult)||(dwLastError==ERROR_IO_PENDING)) bResult=GetOverlappedResult(hDeviceHandle,&Ov,&dwBytesReturned,TRUE); if(bResult) { *Status=StatusByte; } else { *Status=LPT_BENIGN_STATUS; //benign printer status... 0 would indicate a particular error status from the printer } CloseHandle(Ov.hEvent); return bResult; } MONITOREX MonitorEx = { sizeof(MONITOR), { USBMON_EnumPorts, USBMON_OpenPort, NULL, // OpenPortEx not supported USBMON_StartDocPort, USBMON_WritePort, USBMON_ReadPort, USBMON_EndDocPort, USBMON_ClosePort, NULL, // AddPort not supported NULL, // AddPortEx not supported NULL, // ConfigurePort not supported NULL, // DeletePort not supported USBMON_GetPrinterDataFromPort, USBMON_SetPortTimeOuts, NULL, // XcvOpenPort not supported NULL, // XcvDataPort not supported NULL // XcvClosePort not supported } }; USBMON_MONITOR_INFO gUsbmonInfo; LPMONITOREX WINAPI InitializePrintMonitor( LPTSTR pszRegistryRoot ) { ZeroMemory(&gUsbmonInfo, sizeof(gUsbmonInfo)); InitializeCriticalSection(&gUsbmonInfo.EnumPortsCS); InitializeCriticalSection(&gUsbmonInfo.BackThreadCS); return &MonitorEx; }