/*++ Copyright (c) 1996 Microsoft Corporation Module Name: job.c Abstract: This module implements the job creation and deletion. Also included in the file are the queue management functions and thread management. Author: Wesley Witt (wesw) 24-Jan-1996 Revision History: --*/ #include "faxsvc.h" #include "faxreg.h" #pragma hdrstop #include #include using namespace std; // Globals LIST_ENTRY g_JobListHead; //List of currently running jobs (for which FaxDevStartJob was called). CFaxCriticalSection g_CsJob; HANDLE g_StatusCompletionPortHandle; HINSTANCE g_hResource; DWORD g_dwFaxSendRetries; DWORD g_dwFaxSendRetryDelay; DWORD g_dwFaxDirtyDays; BOOL g_fFaxUseDeviceTsid; BOOL g_fFaxUseBranding; BOOL g_fServerCp; FAX_TIME g_StartCheapTime; FAX_TIME g_StopCheapTime; DWORD g_dwNextJobId; #define JOB_GROUP_FILE_EXTENSION TEXT("FSP") static BOOL SendJobReceipt (BOOL bPositive, JOB_QUEUE * lpJobQueue, LPCTSTR lpctstrAttachment); static BOOL CheckForJobRetry (PJOB_QUEUE lpJobQueue); static DWORD TranslateCanonicalNumber( LPTSTR lptstrCanonicalFaxNumber, DWORD dwDeviceID, LPTSTR lptstrDialableAddress, DWORD dwDialableAddressCount, LPTSTR lptstrDisplayableAddress, DWORD dwDisplayableAddressCount ); static PJOB_ENTRY StartLegacySendJob( PJOB_QUEUE lpJobQueue, PLINE_INFO lpLineInfo ); static PJOB_ENTRY CreateJobEntry(PJOB_QUEUE lpJobQueue, LINE_INFO * lpLineInfo, BOOL bTranslateNumber); BOOL FreeJobEntry(PJOB_ENTRY lpJobEntry , BOOL bDestroy); static BOOL UpdatePerfCounters(const JOB_QUEUE * lpcJobQueue); static BOOL CreateCoverpageTiffFile( IN short Resolution, IN const FAX_COVERPAGE_INFOW2 *CoverpageInfo, IN LPCWSTR lpcwstrExtension, OUT LPWSTR lpwstrCovTiffFile, IN DWORD dwCovTiffFileCount ); static LPWSTR GetFaxPrinterName( VOID ); DWORD BrandFax(LPCTSTR lpctstrFileName, LPCFSPI_BRAND_INFO pcBrandInfo) { #define MAX_BRANDING_LEN 115 #define BRANDING_HEIGHT 22 // in scan lines. // // We allocate fixed size arrays on the stack to avoid many small allocs on the heap. // LPTSTR lptstrBranding = NULL; DWORD lenBranding =0; TCHAR szBrandingEnd[MAX_BRANDING_LEN+1]; DWORD lenBrandingEnd = 0; LPTSTR lptstrCallerNumberPlusCompanyName = NULL; DWORD lenCallerNumberPlusCompanyName = 0; DWORD delta =0 ; DWORD ec = ERROR_SUCCESS; LPTSTR lptstrDate = NULL; LPTSTR lptstrTime = NULL; LPTSTR lptstrDateTime = NULL; int lenDate =0 ; int lenTime =0; LPDWORD MsgPtr[6]; HRESULT hr; DWORD dwDateTimeLength = 0; LPTSTR lptstrSenderTsid; LPTSTR lptstrRecipientPhoneNumber; LPTSTR lptstrSenderCompany; DWORD dwSenderTsidLen; DWORD dwSenderCompanyLen; DEBUG_FUNCTION_NAME(TEXT("BrandFax")); Assert(lpctstrFileName); Assert(pcBrandInfo); lptstrSenderTsid = pcBrandInfo->lptstrSenderTsid ? pcBrandInfo->lptstrSenderTsid : TEXT(""); lptstrRecipientPhoneNumber = pcBrandInfo->lptstrRecipientPhoneNumber ? pcBrandInfo->lptstrRecipientPhoneNumber : TEXT(""); lptstrSenderCompany = pcBrandInfo->lptstrSenderCompany ? pcBrandInfo->lptstrSenderCompany : TEXT(""); dwSenderTsidLen = lptstrSenderTsid ? _tcslen(lptstrSenderTsid) : 0; dwSenderCompanyLen = lptstrSenderCompany ? _tcslen(lptstrSenderCompany) : 0; lenDate = GetY2KCompliantDate( LOCALE_SYSTEM_DEFAULT, 0, &pcBrandInfo->tmDateTime, NULL, NULL); if ( ! lenDate ) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("GetY2KCompliantDate() failed (ec: %ld)"), ec ); goto Error; } lptstrDate = (LPTSTR) MemAlloc(lenDate * sizeof(TCHAR)); // lenDate includes terminating NULL if (!lptstrDate) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("Failed to allocate date buffer of size %ld (ec: %ld)"), lenDate * sizeof(TCHAR), ec); goto Error; } if (!GetY2KCompliantDate( LOCALE_SYSTEM_DEFAULT, 0, &pcBrandInfo->tmDateTime, lptstrDate, lenDate)) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("GetY2KCompliantDate() failed (ec: %ld)"), ec ); goto Error; } lenTime = FaxTimeFormat( LOCALE_SYSTEM_DEFAULT, TIME_NOSECONDS, &pcBrandInfo->tmDateTime, NULL, NULL, 0 ); if ( !lenTime ) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("FaxTimeFormat() failed (ec: %ld)"), ec ); goto Error; } lptstrTime = (LPTSTR) MemAlloc(lenTime * sizeof(TCHAR)); // lenTime includes terminating NULL if (!lptstrTime) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("Failed to allocate time buffer of size %ld (ec: %ld)"), lenTime * sizeof(TCHAR), ec); goto Error; } if ( ! FaxTimeFormat( LOCALE_SYSTEM_DEFAULT, TIME_NOSECONDS, &pcBrandInfo->tmDateTime, NULL, // use locale format lptstrTime, lenTime) ) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("FaxTimeFormat() failed (ec: %ld)"), ec ); goto Error; } // // Concatenate date and time // dwDateTimeLength = lenDate + lenTime; // should be enough, lenDate and lentime both include '\0', and we add only one ' ' between the date and time. lptstrDateTime = (LPTSTR) MemAlloc (dwDateTimeLength * sizeof(TCHAR)); if (!lptstrDateTime) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("Failed to allocate DateTime buffer of size %ld (ec: %ld)"), dwDateTimeLength, ec); goto Error; } hr = StringCchPrintf(lptstrDateTime, dwDateTimeLength, TEXT("%s %s"), lptstrDate, lptstrTime); if (FAILED(hr)) { // // Should never happen, we just allocated large enough buffer. // ASSERT_FALSE; } // // Create lpCallerNumberPlusCompanyName // if (lptstrSenderCompany) { DWORD dwCallerNumberPlusCompanyNameCount = dwSenderTsidLen + dwSenderCompanyLen +2; // we add 2 chars, 1 for '\0' and one for the ' '. lptstrCallerNumberPlusCompanyName = (LPTSTR) MemAlloc( dwCallerNumberPlusCompanyNameCount * sizeof(TCHAR) ); if (!lptstrCallerNumberPlusCompanyName) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("Failed to allocate CallerNumberPlusCompanyName buffer of size %ld (ec: %ld)"), dwCallerNumberPlusCompanyNameCount, ec); goto Error; } hr = StringCchPrintf(lptstrCallerNumberPlusCompanyName, dwCallerNumberPlusCompanyNameCount, TEXT("%s %s"), lptstrSenderTsid, lptstrSenderCompany); if (FAILED(hr)) { // // Should never happen, we just allocated large enough buffer. // ASSERT_FALSE; } } else { lptstrCallerNumberPlusCompanyName = (LPTSTR) MemAlloc( (dwSenderTsidLen + 1) * sizeof(TCHAR)); if (!lptstrCallerNumberPlusCompanyName) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("Failed to allocate CallerNumberPlusCompanyName buffer of size %ld (ec: %ld)"), (dwSenderTsidLen + 1) * sizeof(TCHAR), ec); goto Error; } hr = StringCchCopy( lptstrCallerNumberPlusCompanyName, dwSenderTsidLen + 1, lptstrSenderTsid); if (FAILED(hr)) { // // Should never happen, we just allocated large enough buffer. // ASSERT_FALSE; } } // // Try to create a banner of the following format: // FROM: TO: lptstrRecipientPhoneNumber> PAGE: X OF Y // If it does not fit we will start chopping it off. // MsgPtr[0] = (LPDWORD) lptstrDateTime; MsgPtr[1] = (LPDWORD) lptstrCallerNumberPlusCompanyName; MsgPtr[2] = (LPDWORD) lptstrRecipientPhoneNumber; MsgPtr[3] = NULL; lenBranding = FormatMessage( FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_ALLOCATE_BUFFER, g_hResource, MSG_BRANDING_FULL, 0, (LPTSTR)&lptstrBranding, 0, (va_list *) MsgPtr ); if ( ! lenBranding ) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("FormatMessage of MSG_BRANDING_FULL failed (ec: %ld)"), ec); goto Error; } Assert(lptstrBranding); lenBrandingEnd = FormatMessage( FORMAT_MESSAGE_FROM_HMODULE , g_hResource, MSG_BRANDING_END, 0, szBrandingEnd, sizeof(szBrandingEnd)/sizeof(TCHAR), NULL ); if ( !lenBrandingEnd) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("FormatMessage of MSG_BRANDING_END failed (ec: %ld)"), ec); goto Error; } // // Make sure we can fit everything. // if (lenBranding + lenBrandingEnd + 8 <= MAX_BRANDING_LEN) { // // It fits. Proceed with branding. // goto lDoBranding; } // // It did not fit. Try a message of the format: // FROM: PAGE: X OF Y // This skips the ReceiverNumber. The important part is the CallerNumberPlusCompanyName. // MsgPtr[0] = (LPDWORD) lptstrDateTime; MsgPtr[1] = (LPDWORD) lptstrCallerNumberPlusCompanyName; MsgPtr[2] = NULL; // // Free the previous attempt branding string // Assert(lptstrBranding); LocalFree(lptstrBranding); lptstrBranding = NULL; lenBranding = FormatMessage( FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_ALLOCATE_BUFFER, g_hResource, MSG_BRANDING_SHORT, 0, (LPTSTR)&lptstrBranding, 0, (va_list *) MsgPtr ); if ( !lenBranding ) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("FormatMessage() failed for MSG_BRANDING_SHORT (ec: %ld)"), ec); goto Error; } Assert(lptstrBranding); if (lenBranding + lenBrandingEnd + 8 <= MAX_BRANDING_LEN) { goto lDoBranding; } // // It did not fit. // We have to truncate the caller number so it fits. // delta = how many chars of the company name we need to chop off. // delta = lenBranding + lenBrandingEnd + 8 - MAX_BRANDING_LEN; lenCallerNumberPlusCompanyName = _tcslen (lptstrCallerNumberPlusCompanyName); if (lenCallerNumberPlusCompanyName <= delta) { DebugPrintEx( DEBUG_ERR, TEXT("Can not truncate CallerNumberPlusCompanyName to fit brand limit.") TEXT(" Delta[%ld] >= lenCallerNumberPlusCompanyName[%ld]"), delta, lenCallerNumberPlusCompanyName); ec = ERROR_BAD_FORMAT; goto Error; } lptstrCallerNumberPlusCompanyName[ lenCallerNumberPlusCompanyName - delta] = TEXT('\0'); MsgPtr[0] = (LPDWORD) lptstrDateTime; MsgPtr[1] = (LPDWORD) lptstrCallerNumberPlusCompanyName; MsgPtr[2] = NULL; // // Free the previous attempt branding string // Assert(lptstrBranding); LocalFree(lptstrBranding); lptstrBranding = NULL; lenBranding = FormatMessage( FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_ALLOCATE_BUFFER, g_hResource, MSG_BRANDING_SHORT, 0, (LPTSTR)&lptstrBranding, 0, (va_list *) MsgPtr ); if ( !lenBranding ) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("FormatMessage() failed (ec: %ld). MSG_BRANDING_SHORT 2nd attempt"), ec); goto Error; } Assert(lptstrBranding); // // If it did noo fit now then we have a bug. // Assert(lenBranding + lenBrandingEnd + 8 <= MAX_BRANDING_LEN); lDoBranding: if (!MmrAddBranding(lpctstrFileName, lptstrBranding, szBrandingEnd, BRANDING_HEIGHT) ) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("MmrAddBranding() failed (ec: %ld)") TEXT(" File: [%s]") TEXT(" Branding: [%s]") TEXT(" Branding End: [%s]") TEXT(" Branding Height: [%d]"), ec, lpctstrFileName, lptstrBranding, szBrandingEnd, BRANDING_HEIGHT); goto Error; } Assert( ERROR_SUCCESS == ec); goto Exit; Error: Assert (ERROR_SUCCESS != ec); Exit: if (lptstrBranding) { LocalFree(lptstrBranding); lptstrBranding = NULL; } MemFree(lptstrDate); lptstrDate = NULL; MemFree(lptstrTime); lptstrTime = NULL; MemFree(lptstrDateTime); lptstrDateTime = NULL; MemFree(lptstrCallerNumberPlusCompanyName); lptstrCallerNumberPlusCompanyName = NULL; return ec; } HRESULT WINAPI FaxBrandDocument( LPCTSTR lpctsrtFile, LPCFSPI_BRAND_INFO lpcBrandInfo) { DEBUG_FUNCTION_NAME(TEXT("FaxBrandDocument")); DWORD ec = ERROR_SUCCESS; if (!lpctsrtFile) { DebugPrintEx(DEBUG_ERR, TEXT("NULL target file name")); ec = ERROR_INVALID_PARAMETER; goto Error; } if (!lpcBrandInfo) { DebugPrintEx(DEBUG_ERR, TEXT("NULL branding info")); ec = ERROR_INVALID_PARAMETER; goto Error; } if (lpcBrandInfo->dwSizeOfStruct != sizeof(FSPI_BRAND_INFO)) { DebugPrintEx(DEBUG_ERR, TEXT("Bad cover page info parameter, dwSizeOfStruct = %d"), lpcBrandInfo->dwSizeOfStruct); ec = ERROR_INVALID_PARAMETER; goto Error; } ec = BrandFax(lpctsrtFile, lpcBrandInfo); if (ERROR_SUCCESS != ec) { DebugPrintEx( DEBUG_ERR, TEXT("BrandFax() for file %s has failed (ec: %ld)"), lpctsrtFile, ec); goto Error; } Assert (ERROR_SUCCESS == ec); goto Exit; Error: Assert (ERROR_SUCCESS != ec); Exit: return HRESULT_FROM_WIN32(ec); } PJOB_ENTRY FindJob( IN HANDLE FaxHandle ) /*++ Routine Description: This fuction locates a FAX job by matching the FAX handle value. Arguments: FaxHandle - FAX handle returned from startjob Return Value: NULL for failure. Valid pointer to a JOB_ENTRY on success. --*/ { PLIST_ENTRY Next; PJOB_ENTRY JobEntry; EnterCriticalSection( &g_CsJob ); Next = g_JobListHead.Flink; if (Next == NULL) { LeaveCriticalSection( &g_CsJob ); return NULL; } while ((ULONG_PTR)Next != (ULONG_PTR)&g_JobListHead) { JobEntry = CONTAINING_RECORD( Next, JOB_ENTRY, ListEntry ); if ((ULONG_PTR)JobEntry->InstanceData == (ULONG_PTR)FaxHandle) { LeaveCriticalSection( &g_CsJob ); return JobEntry; } Next = JobEntry->ListEntry.Flink; } LeaveCriticalSection( &g_CsJob ); return NULL; } BOOL FindJobByJob( IN PJOB_ENTRY JobEntryToFind ) /*++ Routine Description: This fuction check whether a FAX job exist in g_JobListHead (Job's list) Arguments: JobEntryToFind - PJOB_ENTRY from StartJob() Return Value: TRUE - if the job was found FALSE - otherwise --*/ { PLIST_ENTRY Next; PJOB_ENTRY JobEntry; Assert(JobEntryToFind); EnterCriticalSection( &g_CsJob ); Next = g_JobListHead.Flink; if (Next == NULL) { LeaveCriticalSection( &g_CsJob ); return FALSE; } while ((ULONG_PTR)Next != (ULONG_PTR)&g_JobListHead) { JobEntry = CONTAINING_RECORD( Next, JOB_ENTRY, ListEntry ); if (JobEntry == JobEntryToFind) { LeaveCriticalSection( &g_CsJob ); return TRUE; } Next = JobEntry->ListEntry.Flink; } LeaveCriticalSection( &g_CsJob ); return FALSE; } BOOL FaxSendCallback( IN HANDLE FaxHandle, IN HCALL CallHandle, IN DWORD Reserved1, IN DWORD Reserved2 ) /*++ Routine Description: This fuction is called asychronously by a FAX device provider after a call is established. The sole purpose of the callback is to communicate the call handle from the device provider to the FAX service. Arguments: FaxHandle - FAX handle returned from startjob CallHandle - Call handle for newly initiated call Reserved1 - Always zero. Reserved2 - Always zero. Return Value: TRUE for success, FAX operation continues. FALSE for failure, FAX operation is terminated. --*/ { PJOB_ENTRY JobEntry = NULL; BOOL bRes = FALSE; EnterCriticalSection(&g_CsJob); JobEntry = FindJob( FaxHandle ); if (JobEntry) { if (NULL == JobEntry->CallHandle) { JobEntry->CallHandle = CallHandle; } bRes = (JobEntry->CallHandle == CallHandle) ? TRUE : FALSE; } LeaveCriticalSection(&g_CsJob); if (FALSE == bRes) { SetLastError(ERROR_INVALID_PARAMETER); } return bRes; } //********************************************************************************* //* Name: CreateCoverpageTiffFileEx() //* Author: Ronen Barenboim //* Date: March 24, 1999 //********************************************************************************* //* DESCRIPTION: //* Generates cover page TIFF file from the specified cover page template //* and new Client API parameters. //* The function returns the name of the generated file. //* PARAMETERS: //* Resolution [IN] //* //* dwPageCount [IN] //* //* lpcCoverpageEx [IN] //* //* lpcRecipient [IN] //* //* lpcSender [IN] //* //* lpcwstrExtension [IN] - File extension (optional). //* //* lpwstrCovTiffFile [OUT] //* A pointer to Unicode string buffer where the function will place //* the full path to the generated cover page TIFF file. //* //* dwCovTiffFileCount [IN] - size of the buffer pointed by lpwstrCovTiffFile. //* //* RETURN VALUE: //* TRUE //* If the operation succeeded. //* FALSE //* Otherwise. Use GetLastError() to figure out why it failed. //* //* REMARKS: //* The function does not allocate any memory. //********************************************************************************* BOOL CreateCoverpageTiffFileEx( IN short Resolution, IN DWORD dwPageCount, IN LPCFAX_COVERPAGE_INFO_EXW lpcCoverpageEx, IN LPCFAX_PERSONAL_PROFILEW lpcRecipient, IN LPCFAX_PERSONAL_PROFILEW lpcSender, IN LPCWSTR lpcwstrExtension, OUT LPWSTR lpwstrCovTiffFile, IN DWORD dwCovTiffFileCount) { FAX_COVERPAGE_INFOW2 covLegacy; BOOL bRes = TRUE; DEBUG_FUNCTION_NAME(TEXT("CreateCoverpageTiffFileEx")); Assert(lpcCoverpageEx); Assert(lpcRecipient); Assert(lpcSender); Assert(lpwstrCovTiffFile); // // Prepare a legacy FAX_COVERPAGE_INFO from the new cover page info // memset(&covLegacy,0,sizeof(covLegacy)); covLegacy.SizeOfStruct=sizeof(covLegacy); covLegacy.CoverPageName=lpcCoverpageEx->lptstrCoverPageFileName; covLegacy.UseServerCoverPage=lpcCoverpageEx->bServerBased; covLegacy.RecCity=lpcRecipient->lptstrCity; covLegacy.RecCompany=lpcRecipient->lptstrCompany; covLegacy.RecCountry=lpcRecipient->lptstrCountry; covLegacy.RecDepartment=lpcRecipient->lptstrDepartment; covLegacy.RecFaxNumber=lpcRecipient->lptstrFaxNumber; covLegacy.RecHomePhone=lpcRecipient->lptstrHomePhone; covLegacy.RecName=lpcRecipient->lptstrName; covLegacy.RecOfficeLocation=lpcRecipient->lptstrOfficeLocation; covLegacy.RecOfficePhone=lpcRecipient->lptstrOfficePhone; covLegacy.RecState=lpcRecipient->lptstrState; covLegacy.RecStreetAddress=lpcRecipient->lptstrStreetAddress; covLegacy.RecTitle=lpcRecipient->lptstrTitle; covLegacy.RecZip=lpcRecipient->lptstrZip; covLegacy.SdrName=lpcSender->lptstrName; covLegacy.SdrFaxNumber=lpcSender->lptstrFaxNumber; covLegacy.SdrCompany=lpcSender->lptstrCompany; covLegacy.SdrTitle=lpcSender->lptstrTitle; covLegacy.SdrDepartment=lpcSender->lptstrDepartment; covLegacy.SdrOfficeLocation=lpcSender->lptstrOfficeLocation; covLegacy.SdrHomePhone=lpcSender->lptstrHomePhone; covLegacy.SdrAddress=lpcSender->lptstrStreetAddress; covLegacy.SdrOfficePhone=lpcSender->lptstrOfficePhone; covLegacy.SdrEmail=lpcSender->lptstrEmail; covLegacy.Note=lpcCoverpageEx->lptstrNote; covLegacy.Subject=lpcCoverpageEx->lptstrSubject; covLegacy.PageCount=dwPageCount; // // Note covLegacy.TimeSent is not set. This field's value is // generated by FaxPrintCoverPageW(). // // // Now call the legacy CreateCoverPageTiffFile() to generate the cover page file // if (!CreateCoverpageTiffFile(Resolution, &covLegacy, lpcwstrExtension, lpwstrCovTiffFile, dwCovTiffFileCount)) { DebugPrintEx( DEBUG_ERR, TEXT("Failed to generate cover page file for recipient %s@%s. (ec: %ld)"), lpcRecipient->lptstrName, lpcRecipient->lptstrFaxNumber, GetLastError() ); bRes = FALSE; } return bRes; } LPWSTR GetFaxPrinterName( VOID ) { PPRINTER_INFO_2 PrinterInfo; DWORD i; DWORD Count; PrinterInfo = (PPRINTER_INFO_2) MyEnumPrinters( NULL, 2, &Count, 0 ); if (PrinterInfo == NULL) { if (ERROR_SUCCESS == GetLastError()) { // // No printers are installed // SetLastError(ERROR_INVALID_PRINTER_NAME); } return NULL; } for (i=0; iRecName)) + (i * sizeof(LPTSTR))); MemFree (*lpptstrString) ; } } BOOL FillCoverPageFields( IN const FAX_COVERPAGE_INFOW2* pFaxCovInfo, OUT PCOVERPAGEFIELDS pCPFields) /*++ Author: Oded Sacher 27-June-2001 Routine Description: Fills a COVERPAGEFIELDS structure from the content of a FAX_COVERPAGE_INFO structure. Used to prepare a COVERPAGEFIELDS structure for cover page rendering before rendering cover page. Arguments: [IN] pFaxCovInfo - Pointer to a FAX_COVERPAGE_INFO that holds the information to be extracted. [OUT] pCPFields - Pointer to a COVERPAGEFIELDS structure that gets filled with the information from FAX_COVERPAGE_INFO. Return Value: BOOL Comments: The function allocates memory. Call FreeCoverPageFields to free resources. --*/ { DWORD dwDateTimeLen; DWORD cch; LPTSTR s; DWORD ec = 0; LPCTSTR *src; LPCTSTR *dst; DWORD i; TCHAR szTimeBuffer[MAX_PATH] = {0}; TCHAR szNumberOfPages[12] = {0}; Assert(pFaxCovInfo); Assert(pCPFields); memset(pCPFields,0,sizeof(COVERPAGEFIELDS)); pCPFields->ThisStructSize = sizeof(COVERPAGEFIELDS); pCPFields->RecName = StringDup(pFaxCovInfo->RecName); pCPFields->RecFaxNumber = StringDup(pFaxCovInfo->RecFaxNumber); pCPFields->Subject = StringDup(pFaxCovInfo->Subject); pCPFields->Note = StringDup(pFaxCovInfo->Note); pCPFields->NumberOfPages = StringDup(_itot( pFaxCovInfo->PageCount, szNumberOfPages, 10 )); for (i = 0; i <= ((LPBYTE)&pFaxCovInfo->SdrEmail - (LPBYTE)&pFaxCovInfo->RecCompany)/sizeof(LPCTSTR); i++) { src = (LPCTSTR *) ((LPBYTE)(&pFaxCovInfo->RecCompany) + (i*sizeof(LPCTSTR))); dst = (LPCTSTR *) ((LPBYTE)(&(pCPFields->RecCompany)) + (i*sizeof(LPCTSTR))); if (*dst) { MemFree ( (LPBYTE) *dst ) ; } *dst = (LPCTSTR) StringDup( *src ); } // // the time the fax was sent // GetLocalTime((LPSYSTEMTIME)&pFaxCovInfo->TimeSent); // // dwDataTimeLen is the size of s in characters // dwDateTimeLen = ARR_SIZE(szTimeBuffer); s = szTimeBuffer; // // Get date into s // GetY2KCompliantDate( LOCALE_USER_DEFAULT, 0, &pFaxCovInfo->TimeSent, s, dwDateTimeLen ); // // Advance s past the date string and attempt to append time // cch = _tcslen( s ); s += cch; if (++cch < dwDateTimeLen) { *s++ = ' '; // // DateTimeLen is the decreased by the size of s in characters // dwDateTimeLen -= cch; // // Get the time here // FaxTimeFormat( LOCALE_USER_DEFAULT, 0, &pFaxCovInfo->TimeSent, NULL, s, dwDateTimeLen ); } pCPFields->TimeSent = StringDup( szTimeBuffer ); return TRUE; } //***************************************************************************** //* Name: CreateCoverpageTiffFile //* Author: //***************************************************************************** //* DESCRIPTION: //* Renders the specified coverpage into a temp TIFF file and returns the name //* of the temp TIFF file. //* PARAMETERS: //* [IN] IN short Resolution: //* 196 for 200x200 resolution. //* 98 for 200x100 resolution. //* [IN] FAX_COVERPAGE_INFOW *CoverpageInfo: //* A pointer to a FAX_COVERPAGE_INFOW structure that contains the cover page //* template information (see SDK help). //* [IM] LPCWSTR lpcwstrExtension - File extension (".TIF" if NULL) //* //* [OUT] LPWSTR lpwstrCovTiffFile: //* A pointer to a buffer where the function returns the name of the temp file //* that contains the rendered cover page TIFF file. //* //* [IN] DWORD dwCovTiffFileCount: //* Size in TCHARs of the buffer pointed by lpwstrCovTiffFile. //* RETURN VALUE: //* FALSE if the operation failed. //* TRUE is succeeded. //* Comments: //* If the operation failes the function takes care of deleting any temp files. //***************************************************************************** BOOL CreateCoverpageTiffFile( IN short Resolution, IN const FAX_COVERPAGE_INFOW2 *CoverpageInfo, IN LPCWSTR lpcwstrExtension, OUT LPWSTR lpwstrCovTiffFile, IN DWORD dwCovTiffFileCount ) { WCHAR TempFile[MAX_PATH]; WCHAR wszCpName[MAX_PATH]; LPWSTR FaxPrinter = NULL; BOOL Rslt = TRUE; COVDOCINFO covDocInfo; short Orientation = DMORIENT_PORTRAIT; DWORD ec = ERROR_SUCCESS; COVERPAGEFIELDS CpFields = {0}; HRESULT hr; DEBUG_FUNCTION_NAME(TEXT("CreateCoverpageTiffFile()")); LPCWSTR lpcwstrFileExt = lpcwstrExtension ? lpcwstrExtension : FAX_TIF_FILE_EXT; TempFile[0] = L'\0'; // // Validate the cover page and resolve the full path // if (!ValidateCoverpage((LPWSTR)CoverpageInfo->CoverPageName, NULL, CoverpageInfo->UseServerCoverPage, wszCpName, ARR_SIZE(wszCpName))) { ec = GetLastError(); DebugPrintEx(DEBUG_ERR, TEXT("ValidateCoverpage failed. ec = %ld"), ec); Rslt=FALSE; goto Exit; } // // Collect the cover page fields // FillCoverPageFields( CoverpageInfo, &CpFields); FaxPrinter = GetFaxPrinterName(); if (FaxPrinter == NULL) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("GetFaxPrinterName failed. ec = %ld"), ec); Rslt=FALSE; goto Exit; } // // Get the cover page orientation // ec = PrintCoverPage(NULL, NULL, wszCpName, &covDocInfo); if (ERROR_SUCCESS != ec) { DebugPrintEx( DEBUG_ERR, TEXT("PrintCoverPage for coverpage %s failed (ec: %ld)"), CoverpageInfo->CoverPageName, ec); Rslt=FALSE; goto Exit; } if (!GenerateUniqueFileName( g_wszFaxQueueDir, (LPWSTR)lpcwstrFileExt, TempFile, sizeof(TempFile)/sizeof(WCHAR) )) { ec = GetLastError(); DebugPrintEx(DEBUG_ERR,TEXT("Failed to generate unique file name for merged TIFF file (ec: %ld)."), ec); Rslt=FALSE; goto Exit; } // // Change the default orientation if needed // if (covDocInfo.Orientation == DMORIENT_LANDSCAPE) { Orientation = DMORIENT_LANDSCAPE; } // // Render the cover page to a file // ec = PrintCoverPageToFile( wszCpName, TempFile, FaxPrinter, Orientation, Resolution, &CpFields); if (ERROR_SUCCESS != ec) { DebugPrintEx( DEBUG_ERR, TEXT("PrintCoverPageToFile for coverpage %s failed (ec: %ld)"), CoverpageInfo->CoverPageName, ec); Rslt=FALSE; if (!DeleteFile( TempFile )) { DebugPrintEx( DEBUG_ERR, TEXT("DeleteFile for file %s failed (ec: %ld)"), TempFile, GetLastError()); } goto Exit; } hr = StringCchCopy( lpwstrCovTiffFile, dwCovTiffFileCount, TempFile); if (FAILED(hr)) { DebugPrintEx( DEBUG_ERR, TEXT("StringCchCopy for coverpage %s failed (ec: %ld)"), CoverpageInfo->CoverPageName, hr); Rslt=FALSE; ec = HRESULT_CODE(hr); if (!DeleteFile( TempFile )) { DebugPrintEx( DEBUG_ERR, TEXT("DeleteFile for file %s failed (ec: %ld)"), TempFile, GetLastError()); } goto Exit; } Rslt = TRUE; Exit: MemFree(FaxPrinter); FreeCpFields(&CpFields); if (FALSE == Rslt) { ec = (ERROR_SUCCESS != ec) ? ec : ERROR_GEN_FAILURE; SetLastError(ec); } return Rslt; } //***************************************************************************** //* Name: GetBodyTiffResolution //* Author: //***************************************************************************** //* DESCRIPTION: //* Returns the body tiff file resolution. (200x200 or 200x100) //* The resolution is determined by the first page only!! //* PARAMETERS: //* //* [IN] LPCWSTR lpcwstrBodyFile - Body tiff file //* //* [OUT] short* pResolution: //* A pointer to a short where the function returns the tiff resolution. //* TRUE is 200x200. FALSE is 200x100 //* RETURN VALUE: //* FALSE if the operation failed. //* TRUE is succeeded. //* Comments: //***************************************************************************** BOOL GetBodyTiffResolution( IN LPCWSTR lpcwstrBodyFile, OUT short* pResolution ) { DEBUG_FUNCTION_NAME(TEXT("GetBodyTiffResolution")); TIFF_INFO TiffInfo; HANDLE hTiff = NULL; BOOL RetVal = TRUE; Assert (lpcwstrBodyFile && pResolution); // // open the tiff file // hTiff = TiffOpen( lpcwstrBodyFile, &TiffInfo, TRUE, FILLORDER_MSB2LSB ); if (hTiff == NULL) { DebugPrintEx( DEBUG_ERR, TEXT("TiffOpen() failed. Tiff file: %s"), lpcwstrBodyFile); RetVal = FALSE; goto exit; } if (TiffInfo.YResolution != 98 && TiffInfo.YResolution != 196) { DebugPrintEx( DEBUG_ERR, TEXT("Invalid Tiff Resolutoin. Tiff file: %s, YRes: %ld."), lpcwstrBodyFile, TiffInfo.YResolution); RetVal = FALSE; goto exit; } *pResolution = TiffInfo.YResolution; Assert (TRUE == RetVal); exit: if (NULL != hTiff) { if (!TiffClose(hTiff)) { DebugPrintEx( DEBUG_ERR, TEXT("TiffClose() failed. Tiff file: %s"), lpcwstrBodyFile); } } return RetVal; } //********************************************************************************* //* Name: CreateTiffFile () //* Author: Ronen Barenboim //* Date: March 24, 1999 //********************************************************************************* //* DESCRIPTION: //* Creates the TIFF file for a job queue. //* //* The function deals with generating the cover page file and merging it //* with the body file (if a body exists). //* It returns the name of the TIFF file it generated. The caller must delete //* this file when it is no longer needed. //* PARAMETERS: //* PJOB_QUEUE lpJob //* A pointer to a JOB_QUEUE structure that holds the recipient or routing job //* information. //* LPCWSTR lpcwstrFileExt - The new file extension (Null will create the default "*.TIF" //* //* LPWSTR lpwstrFullPath - Pointer to a buffer to receive the full path to the new file //* //* DWORD dwFullPathCount - size in TCHARs of the buffer pointed by lpwstrFullPath. //* //* RETURN VALUE: //* TRUE if successful. //* FALSE otherwise. Set last erorr on failure //********************************************************************************* BOOL CreateTiffFile ( PJOB_QUEUE lpJob, LPCWSTR lpcwstrFileExt, LPWSTR lpwstrFullPath, DWORD dwFullPathCount ) { DEBUG_FUNCTION_NAME(TEXT("CreateTiffFile")); Assert(lpJob && lpwstrFullPath); Assert(JT_SEND == lpJob->JobType || JT_ROUTING == lpJob->JobType); PJOB_QUEUE lpParentJob = NULL; WCHAR szCoverPageTiffFile[MAX_PATH] = {0}; LPCWSTR lpcwstrCoverPageFileName; LPCWSTR lpcwstrBodyFileName; short Resolution = 0; // Default resolution BOOL bRes = FALSE; HRESULT hr; if (JT_SEND == lpJob->JobType) { lpParentJob = lpJob->lpParentJob; Assert(lpParentJob); } lpcwstrCoverPageFileName = lpParentJob ? lpParentJob->CoverPageEx.lptstrCoverPageFileName : NULL; lpcwstrBodyFileName = lpParentJob ? lpParentJob->FileName : lpJob->FileName; if (!lpcwstrCoverPageFileName) { // // No cover page specified. // The TIFF to send is the body only. // Copy the body for each recipient // Assert(lpcwstrBodyFileName); // must have a body in this case. LPCWSTR lpcwstrExt = lpcwstrFileExt ? lpcwstrFileExt : FAX_TIF_FILE_EXT; if (!GenerateUniqueFileName( g_wszFaxQueueDir, (LPWSTR)lpcwstrExt, szCoverPageTiffFile, sizeof(szCoverPageTiffFile)/sizeof(WCHAR) )) { DebugPrintEx( DEBUG_ERR, TEXT("GenerateUniqueFileName() failed (ec: %ld)."), GetLastError()); goto Exit; } if (!CopyFile (lpcwstrBodyFileName, szCoverPageTiffFile, FALSE)) // FALSE - File already exist { DebugPrintEx(DEBUG_ERR, TEXT("CopyFile Failed with %ld "), GetLastError()); DeleteFile(szCoverPageTiffFile); goto Exit; } hr = StringCchCopy( lpwstrFullPath, dwFullPathCount, szCoverPageTiffFile); if (FAILED(hr)) { DebugPrintEx(DEBUG_ERR, TEXT("StringCchCopy Failed with %ld "), hr); DeleteFile(szCoverPageTiffFile); SetLastError(HRESULT_CODE(hr)); } else { bRes = TRUE; } goto Exit; } // // There is a cover page so the tiff is either just the cover page or the cover page // merged with the body. // if (lpParentJob->FileName) { if (!GetBodyTiffResolution(lpParentJob->FileName, &Resolution)) { DebugPrintEx( DEBUG_ERR, TEXT("GetBodyTiffResolution() failed (ec: %ld)."), GetLastError()); goto Exit; } } Assert (Resolution == 0 || Resolution == 98 || Resolution == 196); // // First create the cover page (This generates a file and returns its name). // if (!CreateCoverpageTiffFileEx( Resolution, lpJob->PageCount, &lpParentJob->CoverPageEx, &lpJob->RecipientProfile, &lpParentJob->SenderProfile, lpcwstrFileExt, szCoverPageTiffFile, ARR_SIZE(szCoverPageTiffFile))) { DebugPrintEx(DEBUG_ERR, TEXT("[JobId: %ld] Failed to render cover page template %s"), lpJob->JobId, lpParentJob->CoverPageEx.lptstrCoverPageFileName); goto Exit; } if (lpParentJob->FileName) { // // There is a body file specified so merge the body and the cover page into // the file specified in szCoverPageTiffFile. // if (!MergeTiffFiles( szCoverPageTiffFile, lpParentJob->FileName)) { DebugPrintEx(DEBUG_ERR, TEXT("[JobId: %ld] Failed to merge cover (%ws) and body (%ws). (ec: %ld)"), lpJob->JobId, szCoverPageTiffFile, lpParentJob->FileName, GetLastError()); // // Get rid of the coverpage TIFF we generated. // if (!DeleteFile(szCoverPageTiffFile)) { DebugPrintEx(DEBUG_ERR, TEXT("[JobId: %ld] Failed to delete cover page TIFF file %ws. (ec: %ld)"), lpJob->JobId, szCoverPageTiffFile, GetLastError()); } goto Exit; } } hr = StringCchCopy( lpwstrFullPath, dwFullPathCount, szCoverPageTiffFile); if (FAILED(hr)) { DebugPrintEx(DEBUG_ERR, TEXT("StringCchCopy Failed with %ld "), hr); DeleteFile(szCoverPageTiffFile); SetLastError(HRESULT_CODE(hr)); goto Exit; } bRes = TRUE; Exit: if (FALSE == bRes) { // // Make sure we set last error // if (ERROR_SUCCESS == GetLastError()) { SetLastError (ERROR_GEN_FAILURE); } } return bRes; } // CreateTiffFile BOOL CreateTiffFileForJob ( PJOB_QUEUE lpRecpJob ) { DEBUG_FUNCTION_NAME(TEXT("CreateTiffFileForJob")); WCHAR wszFullPath[MAX_PATH] = {0}; Assert(lpRecpJob); if (!CreateTiffFile (lpRecpJob, TEXT("FRT"), wszFullPath, ARR_SIZE(wszFullPath))) { DebugPrintEx(DEBUG_ERR, TEXT("CreateTiffFile failed. (ec: %ld)"), GetLastError()); return FALSE; } if (NULL == (lpRecpJob->FileName = StringDup(wszFullPath))) { DWORD dwErr = GetLastError(); DebugPrintEx(DEBUG_ERR, TEXT("StringDup failed. (ec: %ld)"), dwErr); if (!DeleteFile(wszFullPath)) { DebugPrintEx(DEBUG_ERR, TEXT("[JobId: %ld] Failed to delete TIFF file %ws. (ec: %ld)"), lpRecpJob->JobId, wszFullPath, GetLastError()); } SetLastError(dwErr); return FALSE; } return TRUE; } BOOL CreateTiffFileForPreview ( PJOB_QUEUE lpRecpJob ) { DEBUG_FUNCTION_NAME(TEXT("CreateTiffFileForPreview")); WCHAR wszFullPath[MAX_PATH] = {0}; Assert(lpRecpJob); if (lpRecpJob->PreviewFileName) { return TRUE; } if (!CreateTiffFile (lpRecpJob, TEXT("PRV"), wszFullPath, ARR_SIZE(wszFullPath))) { DebugPrintEx(DEBUG_ERR, TEXT("CreateTiffFile failed. (ec: %ld)"), GetLastError()); return FALSE; } if (NULL == (lpRecpJob->PreviewFileName = StringDup(wszFullPath))) { DWORD dwErr = GetLastError(); DebugPrintEx(DEBUG_ERR, TEXT("StringDup failed. (ec: %ld)"), dwErr); if (!DeleteFile(wszFullPath)) { DebugPrintEx(DEBUG_ERR, TEXT("[JobId: %ld] Failed to delete TIFF file %ws. (ec: %ld)"), lpRecpJob->JobId, wszFullPath, GetLastError()); } SetLastError(dwErr); return FALSE; } return TRUE; } DWORD FaxRouteThread( PJOB_QUEUE lpJobQueueEntry ) /*++ Routine Description: This fuction runs asychronously as a separate thread to route an incoming job. Arguments: lpJobQueueEntry - A pointer to the job for which the routing operation is to be performed. Return Value: Always zero. --*/ { BOOL Routed = TRUE; DWORD i; DWORD dwRes; DWORD CountFailureInfo = 0; DEBUG_FUNCTION_NAME(TEXT("FaxRouteThread")); EnterCriticalSectionJobAndQueue; CountFailureInfo = lpJobQueueEntry->CountFailureInfo; LeaveCriticalSectionJobAndQueue; for (i = 0; i < lpJobQueueEntry->CountFailureInfo; i++) { BOOL fRouteSucceed; fRouteSucceed = FaxRouteRetry( lpJobQueueEntry->FaxRoute, &lpJobQueueEntry->pRouteFailureInfo[i] ); if (FALSE == fRouteSucceed) { PROUTING_METHOD pRoutingMethod = FindRoutingMethodByGuid( (lpJobQueueEntry->pRouteFailureInfo[i]).GuidString ); if (pRoutingMethod) { WCHAR TmpStr[20] = {0}; HRESULT hr = StringCchPrintf( TmpStr, ARR_SIZE(TmpStr), TEXT("0x%016I64x"), lpJobQueueEntry->UniqueId); if (FAILED(hr)) { // // Should never happen, we use large enough buffer. // ASSERT_FALSE; } FaxLog(FAXLOG_CATEGORY_INBOUND, FAXLOG_LEVEL_MIN, 6, MSG_FAX_ROUTE_METHOD_FAILED, TmpStr, lpJobQueueEntry->FaxRoute->DeviceName, lpJobQueueEntry->FaxRoute->Tsid, lpJobQueueEntry->FileName, pRoutingMethod->RoutingExtension->FriendlyName, pRoutingMethod->FriendlyName ); } } Routed &= fRouteSucceed; } EnterCriticalSectionJobAndQueue; lpJobQueueEntry->dwLastJobExtendedStatus = 0; lpJobQueueEntry->ExStatusString[0] = TEXT('\0'); if ( Routed ) { lpJobQueueEntry->JobStatus = JS_DELETING; DecreaseJobRefCount (lpJobQueueEntry, TRUE); } else { // // We failed to execute the routing method. // reschedule the job. // DWORD dwMaxRetries; EnterCriticalSection (&g_CsConfig); dwMaxRetries = g_dwFaxSendRetries; LeaveCriticalSection (&g_CsConfig); lpJobQueueEntry->SendRetries++; if (lpJobQueueEntry->SendRetries <= dwMaxRetries) { lpJobQueueEntry->JobStatus = JS_RETRYING; RescheduleJobQueueEntry( lpJobQueueEntry ); } else { // // retries exceeded, mark job as expired // MarkJobAsExpired(lpJobQueueEntry); WCHAR TmpStr[20] = {0}; HRESULT hr = StringCchPrintf( TmpStr, ARR_SIZE(TmpStr), TEXT("0x%016I64x"), lpJobQueueEntry->UniqueId); if (FAILED(hr)) { // // Should never happen, we use large enough buffer. // ASSERT_FALSE; } FaxLog(FAXLOG_CATEGORY_INBOUND, FAXLOG_LEVEL_MIN, 3, MSG_FAX_ROUTE_FAILED, TmpStr, lpJobQueueEntry->FaxRoute->DeviceName, lpJobQueueEntry->FaxRoute->Tsid ); } // // Create Fax EventEx // dwRes = CreateQueueEvent ( FAX_JOB_EVENT_TYPE_STATUS, lpJobQueueEntry ); if (ERROR_SUCCESS != dwRes) { DebugPrintEx( DEBUG_ERR, _T("CreateQueueEvent(FAX_JOB_EVENT_TYPE_STATUS) ") _T("failed for job id %ld (ec: %lc)"), lpJobQueueEntry->UniqueId, dwRes); } if (!UpdatePersistentJobStatus(lpJobQueueEntry)) { DebugPrintEx( DEBUG_ERR, _T("Failed to update persistent job status to 0x%08x"), lpJobQueueEntry->JobStatus); } } LeaveCriticalSectionJobAndQueue; if (!DecreaseServiceThreadsCount()) { DebugPrintEx( DEBUG_ERR, TEXT("DecreaseServiceThreadsCount() failed (ec: %ld)"), GetLastError()); } return ERROR_SUCCESS; } DWORD FaxSendThread( PFAX_SEND_ITEM FaxSendItem ) /*++ Routine Description: This fuction runs asychronously as a separate thread to send a FAX document. There is one send thread per outstanding FAX send operation. The thread ends when the document is either successfuly sent or the operation is aborted. Arguments: FaxSendItem - pointer to a FAX send item packet that describes the requested FAX send operation. Return Value: Always zero. --*/ { FAX_SEND FaxSend; // This structure is passed to FaxDevSend() BOOL Rslt = FALSE; BOOL Retrying = FALSE; BOOL bFakeJobStatus = FALSE; FSPI_JOB_STATUS FakedJobStatus = {0}; DWORD PageCount = 0; BOOL bRemoveParentJob = FALSE; // TRUE if at the end of the send the parent job and all // recipients need to be removed. PJOB_QUEUE lpJobQueue = NULL ; // Points to the Queue entry attached to the running job. LPFSPI_JOB_STATUS lpFSPStatus = NULL; LPFSPI_JOB_STATUS pOrigFaxStatus = NULL; DWORD dwSttRes; BOOL bBranding; DWORD dwJobId; BOOL bCreateTiffFailed = FALSE; BOOL fSetSystemIdleTimer = TRUE; DEBUG_FUNCTION_NAME(TEXT("FaxSendThread")); Assert (FaxSendItem && FaxSendItem->JobEntry && FaxSendItem->JobEntry->LineInfo && FaxSendItem->JobEntry->LineInfo->Provider); // // Don't let the system go to sleep in the middle of the fax transmission. // if (NULL == SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_CONTINUOUS)) { fSetSystemIdleTimer = FALSE; DebugPrintEx(DEBUG_ERR, TEXT("SetThreadExecutionState() failed")); } lpJobQueue=FaxSendItem->JobEntry->lpJobQueueEntry; Assert(lpJobQueue); // // Set the information to be sent to FaxDevSend() // Note: // The caller number is the sender TSID ! (we have no other indication of the sender phone number) // This means that the FSP will get the sender TSID which might contain text as well (not just a number) // FaxSend.SizeOfStruct = sizeof(FAX_SEND); FaxSend.CallerName = FaxSendItem->SenderName; FaxSend.CallerNumber = FaxSendItem->Tsid; FaxSend.ReceiverName = FaxSendItem->RecipientName; FaxSend.ReceiverNumber = FaxSendItem->PhoneNumber; FaxSend.CallHandle = 0; // filled in later via TapiStatusThread, if appropriate FaxSend.Reserved[0] = 0; FaxSend.Reserved[1] = 0; FaxSend.Reserved[2] = 0; // // Successfully created a new send job on a device. Update counter. // (VOID)UpdateDeviceJobsCounter ( FaxSendItem->JobEntry->LineInfo, // Device to update TRUE, // Sending 1, // Number of new jobs TRUE); // Enable events if (!lpJobQueue->FileName) { // // We did not generate a body for this recipient yet. This is the // time to do so. // // // Set the right body for this job. // This is either the body specified at the parent or a merge of the body // with the cover page specified in the parent. // DebugPrintEx( DEBUG_MSG, TEXT("[JobId: %ld] Generating body for recipient job."), lpJobQueue->JobId ); if (!CreateTiffFileForJob(lpJobQueue)) { DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] CreateTiffFileForJob failed. (ec: %ld)"), lpJobQueue->JobId, GetLastError() ); bCreateTiffFailed = TRUE; } } else { // // We already generated a body for this recipient. // somthing is wrong // DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] Using cached body in %s."), lpJobQueue->JobId, lpJobQueue->FileName ); Assert(FALSE); } if (bCreateTiffFailed || NULL == (FaxSendItem->FileName = StringDup(lpJobQueue->FileName))) { DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] CreateTiffFileForJob or StringDup failed"), FaxSendItem->JobEntry->lpJobQueueEntry->JobId, GetLastError()); // // Simulate an FSP returning a FS_FATAL_ERROR code. // EnterCriticalSection(&g_CsJob); FreeFSPIJobStatus(&FaxSendItem->JobEntry->FSPIJobStatus, FALSE); FaxSendItem->JobEntry->FSPIJobStatus.dwJobStatus = FSPI_JS_FAILED; FaxSendItem->JobEntry->FSPIJobStatus.dwExtendedStatus = FSPI_ES_FATAL_ERROR; if (!HandleFailedSendJob(FaxSendItem->JobEntry)) { DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] HandleFailedSendJob() failed (ec: %ld)."), FaxSendItem->JobEntry->lpJobQueueEntry->JobId, GetLastError()); } LeaveCriticalSection(&g_CsJob); goto Exit; } FaxSend.FileName = FaxSendItem->FileName; // // Add branding banner (the line at the top of each page) to the fax if necessary. // // // Our service takes care of branding so notify FSP not to brand // FaxSend.Branding = FALSE; EnterCriticalSection (&g_CsConfig); bBranding = g_fFaxUseBranding; LeaveCriticalSection (&g_CsConfig); if (bBranding) { FSPI_BRAND_INFO brandInfo; HRESULT hr; memset(&brandInfo,0,sizeof(FSPI_BRAND_INFO)); brandInfo.dwSizeOfStruct=sizeof(FSPI_BRAND_INFO); brandInfo.lptstrRecipientPhoneNumber = FaxSendItem->JobEntry->lpJobQueueEntry->RecipientProfile.lptstrFaxNumber; brandInfo.lptstrSenderCompany = FaxSendItem->SenderCompany; brandInfo.lptstrSenderTsid = FaxSendItem->Tsid; GetLocalTime( &brandInfo.tmDateTime); // can't fail hr = FaxBrandDocument(FaxSendItem->FileName,&brandInfo); if (FAILED(hr)) { DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] FaxBrandDocument() failed. (hr: 0x%08X)"), lpJobQueue->JobId, hr); // // But we go on since it is better to send the fax without the branding // then lose it altogether. // } } FaxSendItem->JobEntry->LineInfo->State = FPS_INITIALIZING; DebugPrintEx( DEBUG_MSG, TEXT("[JobId: %ld] Calling FaxDevSend().\n\t File: %s\n\tNumber [%s]\n\thLine = 0x%08X\n\tCallHandle = 0x%08X"), lpJobQueue->JobId, FaxSend.FileName, FaxSendItem->JobEntry->DialablePhoneNumber, FaxSendItem->JobEntry->LineInfo->hLine, FaxSend.CallHandle ); __try { // // Send the fax (This call is blocking) // Rslt = FaxSendItem->JobEntry->LineInfo->Provider->FaxDevSend( (HANDLE) FaxSendItem->JobEntry->InstanceData, &FaxSend, FaxSendCallback ); if (!Rslt) { DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] FaxDevSend() failed (ec: 0x%0X)"), lpJobQueue->JobId, GetLastError()); } } __except (HandleFaxExtensionFault(EXCEPTION_SOURCE_FSP, FaxSendItem->JobEntry->LineInfo->Provider->FriendlyName, GetExceptionCode())) { ASSERT_FALSE; } // // Get the final status of the job. // dwSttRes = GetDevStatus((HANDLE) FaxSendItem->JobEntry->InstanceData, FaxSendItem->JobEntry->LineInfo, &lpFSPStatus); if (ERROR_SUCCESS != dwSttRes) { // // Couldn't retrieve device status. // Fake one. // bFakeJobStatus = TRUE; DebugPrintEx(DEBUG_ERR, TEXT("[Job: %ld] GetDevStatus failed - %d"), FaxSendItem->JobEntry->lpJobQueueEntry->JobId, dwSttRes); } else if ((FSPI_JS_COMPLETED != lpFSPStatus->dwJobStatus) && (FSPI_JS_ABORTED != lpFSPStatus->dwJobStatus) && (FSPI_JS_FAILED != lpFSPStatus->dwJobStatus) && (FSPI_JS_DELETED != lpFSPStatus->dwJobStatus) && (FSPI_JS_FAILED_NO_RETRY != lpFSPStatus->dwJobStatus)) { // // Status returned is unacceptable - fake one. // bFakeJobStatus = TRUE; DebugPrintEx(DEBUG_WRN, TEXT("[Job: %ld] GetDevStatus return unacceptable status - %d. Faking the status"), FaxSendItem->JobEntry->lpJobQueueEntry->JobId, lpFSPStatus->dwJobStatus); pOrigFaxStatus = lpFSPStatus; memcpy (&FakedJobStatus, lpFSPStatus, sizeof (FakedJobStatus)); if (lpFSPStatus->fAvailableStatusInfo & FSPI_JOB_STATUS_INFO_FSP_PRIVATE_STATUS_CODE) { // // The FSP returned proprietary status. // FakedJobStatus.dwExtendedStatus = lpFSPStatus->dwExtendedStatus; FakedJobStatus.dwExtendedStatusStringId = lpFSPStatus->dwExtendedStatusStringId; } lpFSPStatus = NULL; } // // Enter critical section to block out FaxStatusThread // EnterCriticalSection( &g_CsJob ); if (bFakeJobStatus) { // // Fake a job status // lpFSPStatus = &FakedJobStatus; FakedJobStatus.dwSizeOfStruct = sizeof (FakedJobStatus); if (Rslt) { // // Fake success // FakedJobStatus.dwJobStatus = FSPI_JS_COMPLETED; if (0 == FakedJobStatus.dwExtendedStatus) { // // The FSP did not report proprietary status // FakedJobStatus.dwExtendedStatus = FSPI_ES_CALL_COMPLETED; } } else { // // Fake failure // FakedJobStatus.dwJobStatus = FSPI_JS_FAILED; if (0 == FakedJobStatus.dwExtendedStatus) { // // The FSP did not report proprietary status // FakedJobStatus.dwExtendedStatus = FSPI_ES_FATAL_ERROR; } } } if (!UpdateJobStatus(FaxSendItem->JobEntry, lpFSPStatus)) { DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] UpdateJobStatus() failed (ec: %ld)."), FaxSendItem->JobEntry->lpJobQueueEntry->JobId, GetLastError()); // // Fake a status (we must have some valid status in job entry) // FreeFSPIJobStatus(&FaxSendItem->JobEntry->FSPIJobStatus, FALSE); if (Rslt) { FaxSendItem->JobEntry->FSPIJobStatus.dwJobStatus = FSPI_JS_COMPLETED; FaxSendItem->JobEntry->FSPIJobStatus.dwExtendedStatus = FSPI_ES_CALL_COMPLETED; } else { FaxSendItem->JobEntry->FSPIJobStatus.dwJobStatus = FSPI_JS_FAILED; FaxSendItem->JobEntry->FSPIJobStatus.dwExtendedStatus = FSPI_ES_FATAL_ERROR; } } if (!bFakeJobStatus) { // // Note: The FSPI_JOB_STATUS that is returned by GetDevStatus() is // to be freed as one block. // MemFree(lpFSPStatus); lpFSPStatus = NULL; } else { // // This is a faked job status - pointing to a structure on the stack. // if (pOrigFaxStatus) { // // The FSP reported some status but we faked it. // This is a good time to also free it // MemFree (pOrigFaxStatus); pOrigFaxStatus = NULL; } } // // Block FaxStatusThread from changing this status // FaxSendItem->JobEntry->fStopUpdateStatus = TRUE; LeaveCriticalSection( &g_CsJob ); if (!Rslt) { if (!HandleFailedSendJob(FaxSendItem->JobEntry)) { DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] HandleFailedSendJob() failed (ec: %ld)."), FaxSendItem->JobEntry->lpJobQueueEntry->JobId, GetLastError()); } } else { // // cache the job id since we need id to create the FEI_COMPLETED event // and when it is generated the job may alrady be gone // dwJobId = FaxSendItem->JobEntry->lpJobQueueEntry->JobId; if (!HandleCompletedSendJob(FaxSendItem->JobEntry)) { DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] HandleCompletedSendJob() failed (ec: %ld)."), FaxSendItem->JobEntry->lpJobQueueEntry->JobId, GetLastError()); } // // The send job is completed. For W2K backward compatibility we should notify // FEI_DELETED since the job was allways removed when completed. // if (!CreateFaxEvent(0, FEI_DELETED, dwJobId)) { DebugPrintEx( DEBUG_ERR, TEXT("CreateFaxEvent() failed. Event: 0x%08X JobId: %ld DeviceId: (ec: %ld)"), FEI_DELETED, dwJobId, 0, GetLastError()); } } Exit: MemFree( FaxSendItem->FileName ); MemFree( FaxSendItem->PhoneNumber ); MemFree( FaxSendItem->Tsid ); MemFree( FaxSendItem->RecipientName ); MemFree( FaxSendItem->SenderName ); MemFree( FaxSendItem->SenderDept ); MemFree( FaxSendItem->SenderCompany ); MemFree( FaxSendItem->BillingCode ); MemFree( FaxSendItem->DocumentName ); MemFree( FaxSendItem ); // // Let the system go back to sleep. Set the system idle timer. // if (TRUE == fSetSystemIdleTimer) { if (NULL == SetThreadExecutionState(ES_CONTINUOUS)) { DebugPrintEx(DEBUG_ERR, TEXT("SetThreadExecutionState() failed")); } } if (!DecreaseServiceThreadsCount()) { DebugPrintEx( DEBUG_ERR, TEXT("DecreaseServiceThreadsCount() failed (ec: %ld)"), GetLastError()); } return 0; } //********************************************************************************* //* Name: IsSendJobReadyForDeleting() //* Author: Ronen Barenboim //* Date: April 19, 1999 //********************************************************************************* //* DESCRIPTION: //* Determines if an outgoing job is ready for deleting. //* A job is ready for deleting when all of the recipients //* are in the canceled state or or in the completed state. //* PARAMETERS: //* [IN] PJOB_QUEUE lpRecipientJob //* //* RETURN VALUE: //* TRUE //* If the job is ready for deleting. //* FALSE //* If the job is not ready for deleting. //********************************************************************************* BOOL IsSendJobReadyForDeleting(PJOB_QUEUE lpRecipientJob) { DEBUG_FUNCTION_NAME(TEXT("IsSendJobReadyForDeleting")); Assert (lpRecipientJob); Assert (lpRecipientJob->JobType == JT_SEND); PJOB_QUEUE lpParentJob = lpRecipientJob->lpParentJob; Assert(lpParentJob); // must have a parent job Assert(lpParentJob->dwRecipientJobsCount>0); Assert(lpParentJob->dwCompletedRecipientJobsCount + lpParentJob->dwCanceledRecipientJobsCount + lpParentJob->dwFailedRecipientJobsCount <= lpParentJob->dwRecipientJobsCount); DebugPrintEx( DEBUG_MSG, TEXT("[JobId: %ld] [Total Rec = %ld] [Canceled Rec = %ld] [Completed Rec = %ld] [Failed Rec = %ld] [RefCount = %ld]"), lpParentJob->JobId, lpParentJob->dwRecipientJobsCount, lpParentJob->dwCanceledRecipientJobsCount, lpParentJob->dwCompletedRecipientJobsCount, lpParentJob->dwFailedRecipientJobsCount, lpParentJob->RefCount); if ( (lpParentJob->dwCompletedRecipientJobsCount + lpParentJob->dwCanceledRecipientJobsCount + lpParentJob->dwFailedRecipientJobsCount) == lpParentJob->dwRecipientJobsCount ) { return TRUE; } return FALSE; } BOOL FreeJobEntry(PJOB_ENTRY lpJobEntry , BOOL bDestroy) { DEBUG_FUNCTION_NAME(TEXT("FreeJobEntry")); Assert(lpJobEntry); DWORD ec = ERROR_SUCCESS; DWORD dwJobID = lpJobEntry->lpJobQueueEntry ? lpJobEntry->lpJobQueueEntry->JobId : 0xffffffff; // 0xffffffff for invalid job ID EnterCriticalSection(&g_CsJob); // // Since CreateJobEntry() called OpenTapiLine() for TAPI lines // we need to close it here. // Note that the line might alrady be released since ReleaseJob() // releases the line but does not free the job entry. // if (!lpJobEntry->Released) { if (lpJobEntry->LineInfo->State != FPS_NOT_FAX_CALL) { DebugPrintEx( DEBUG_MSG, TEXT("[Job Id: %ld] Before Releasing tapi line hCall=0x%08X hLine=0x%08X"), dwJobID, lpJobEntry->CallHandle, lpJobEntry->LineInfo->hLine ); ReleaseTapiLine( lpJobEntry->LineInfo, lpJobEntry->CallHandle ); lpJobEntry->CallHandle = 0; lpJobEntry->Released = TRUE; } } // // Remove the job from the running job list // RemoveEntryList( &lpJobEntry->ListEntry ); // // Cut the link between the line and the job // EnterCriticalSection( &g_CsLine ); lpJobEntry->LineInfo->JobEntry = NULL; LeaveCriticalSection( &g_CsLine ); if (!FreeFSPIJobStatus(&lpJobEntry->FSPIJobStatus, FALSE)) { DebugPrintEx( DEBUG_ERR, TEXT("[Job Id: %ld] FreeFSPIJobStatus() failed (ec: %ld)"), dwJobID, GetLastError); } MemFree(lpJobEntry->lpwstrJobTsid); lpJobEntry->lpwstrJobTsid = NULL; if (bDestroy) { MemFree(lpJobEntry); } LeaveCriticalSection(&g_CsJob); return TRUE; } BOOL EndJob( IN PJOB_ENTRY JobEntry ) /*++ Routine Description: This fuction calls the device provider's EndJob function. Arguments: None. Return Value: Error code. --*/ { BOOL rVal = TRUE; PJOB_INFO_1 JobInfo = NULL; DEBUG_FUNCTION_NAME(TEXT("End Job")); Assert(JobEntry); DWORD dwJobID = JobEntry->lpJobQueueEntry ? JobEntry->lpJobQueueEntry->JobId : 0xffffffff; // 0xffffffff for invalid job ID EnterCriticalSection( &g_CsJob ); if (!FindJobByJob( JobEntry )) { // // if we get here then it means we hit a race // condition where the FaxSendThread called EndJob // at the same time that a client app did. // DebugPrintEx(DEBUG_WRN,TEXT("EndJob() could not find the Job"), dwJobID); LeaveCriticalSection( &g_CsJob ); return ERROR_SUCCESS; } if (JobEntry->bFSPJobInProgress) { // // If FaxDevEndJob was not yet called for the job then do it now. // ( The case in which the line is already released occcurs in a // receive job where we first ReleaseJob() to release the line but // continue to do the inbound routing and only then call EndJob()). // __try { DebugPrintEx( DEBUG_MSG, TEXT("[Job Id: %ld] Legacy FSP job is in progress. Calling FaxDevEndJob()"), dwJobID); rVal = JobEntry->LineInfo->Provider->FaxDevEndJob( (HANDLE) JobEntry->InstanceData ); if (!rVal) { DebugPrintEx( DEBUG_ERR, TEXT("[Job Id: %ld] FaxDevEndJob() failed"), dwJobID); } else { DebugPrintEx( DEBUG_MSG, TEXT("[Job Id: %ld] FaxDevEndJob() succeeded."), dwJobID); JobEntry->bFSPJobInProgress = FALSE; } } __except (HandleFaxExtensionFault(EXCEPTION_SOURCE_FSP, JobEntry->LineInfo->Provider->FriendlyName, GetExceptionCode())) { ASSERT_FALSE; } } else { DebugPrintEx( DEBUG_MSG, TEXT("[Job Id: %ld] FaxDevEndJob() NOT CALLED since legacy FSP job is not in progress."), dwJobID); } if (FreeJobEntry(JobEntry, TRUE)) { JobEntry = NULL; } else { DebugPrintEx( DEBUG_ERR, TEXT("Failed to free a job entry (%x)."), JobEntry); ASSERT_FALSE; } // // There could have been a request to change the port status while we were handling this job. // We allow the caller to modify a few of these requests to succeed, like the ring count for instance. // While we still have the job critical section, let's make sure that we commit any requested changes to the // registry. This should be a fairly quick operation. // LeaveCriticalSection( &g_CsJob ); return rVal; } //********************************************************************************* //* Name: ReleaseJob() //* Author: Ronen Barenboim //* Date: April 18, 1999 //********************************************************************************* //* DESCRIPTION: //* Calls the FSP to end the specified job (FaxDevEndJob()). //* Releases the line that was assigned to the job. //* NOTE: The job itself is NOT DELETED and is NOT remvoed from the running //* job list !!! //* //* PARAMETERS: //* [IN/OUT] PJOB_ENTRY JobEntry //* A pointer to the JOB_ENTRY to be ended. //* RETURN VALUE: //* REMARKS: //* If the function is successful then: //* JobEntry->Released = TRUE //* JobEntry->hLine = 0 //* JobEntry->CallHandle = 0 //********************************************************************************* BOOL ReleaseJob( IN PJOB_ENTRY JobEntry ) { BOOL rVal = TRUE; DEBUG_FUNCTION_NAME(TEXT("ReleaseJob")); Assert(JobEntry); Assert(JobEntry->lpJobQueueEntry); if (!FindJobByJob( JobEntry )) { DebugPrintEx( DEBUG_WRN, TEXT("[JobId: %ld] was not found in the running job list."), JobEntry->lpJobQueueEntry->JobId); return TRUE; } EnterCriticalSection( &g_CsJob ); Assert(JobEntry->LineInfo); Assert(JobEntry->LineInfo->Provider); Assert(JobEntry->bFSPJobInProgress); __try { rVal = JobEntry->LineInfo->Provider->FaxDevEndJob( (HANDLE) JobEntry->InstanceData ); if (!rVal) { DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] FaxDevEndJob() failed (ec: 0x%0X)"), JobEntry->lpJobQueueEntry->JobId, GetLastError()); } else { DebugPrintEx( DEBUG_MSG, TEXT("[Job Id: %ld] FaxDevEndJob() succeeded."), JobEntry->lpJobQueueEntry->JobId); JobEntry->bFSPJobInProgress = FALSE; } } __except (HandleFaxExtensionFault(EXCEPTION_SOURCE_FSP, JobEntry->LineInfo->Provider->FriendlyName, GetExceptionCode())) { ASSERT_FALSE; } if (JobEntry->LineInfo->State != FPS_NOT_FAX_CALL) { if( !ReleaseTapiLine( JobEntry->LineInfo, JobEntry->CallHandle )) { DebugPrintEx( DEBUG_ERR, TEXT("ReleaseTapiLine() failed ")); } JobEntry->CallHandle = 0; } else { // // FSP_NOT_FAX_CALL indicates a received call that was handed off to RAS. // In this case we do not want to mark the line as released since it is in // use by RAS. We will use TAPI evens that indicate the line was released to update // the line info. // DebugPrintEx( DEBUG_MSG, TEXT("[JobId: %ld] A call is being handed off to RAS. Line 0x%08X not marked as released."), JobEntry->lpJobQueueEntry->JobId, JobEntry->LineInfo->hLine); } JobEntry->Released = TRUE; // // Cut the link between the line and the job // EnterCriticalSection( &g_CsLine ); JobEntry->LineInfo->JobEntry = NULL; LeaveCriticalSection( &g_CsLine ); LeaveCriticalSection( &g_CsJob ); return rVal; } //********************************************************************************* //* Name: SendDocument() //* Author: Ronen Barenboim //* Date: March 21, 1999 //********************************************************************************* //* DESCRIPTION: //* //* PARAMETERS: //* lpJobEntry //* A pointer to a JOB_ENTRY structure that was created using StartJob(). //* FileName //* The path to the TIFF containing the TIFF to send //* //* RETURN VALUE: //* //********************************************************************************* DWORD SendDocument( PJOB_ENTRY lpJobEntry, LPTSTR FileName ) { PFAX_SEND_ITEM FaxSendItem; DWORD ThreadId; HANDLE hThread; PJOB_QUEUE lpJobQueue; DWORD nRes; DWORD ec = ERROR_SUCCESS; BOOL bUseDeviceTsid; WCHAR wcZero = L'\0'; STRING_PAIR pairs[8]; DEBUG_FUNCTION_NAME(TEXT("SendDocument")); Assert(lpJobEntry); lpJobQueue=lpJobEntry->lpJobQueueEntry; Assert(lpJobQueue && JS_INPROGRESS == lpJobQueue->JobStatus); FaxSendItem = (PFAX_SEND_ITEM) MemAlloc(sizeof(FAX_SEND_ITEM)); if (!FaxSendItem) { ec = ERROR_NOT_ENOUGH_MEMORY; goto Error; } // // Pack all the thread parameters into a FAX_SEND_ITEM structure. // pairs[0].lptstrSrc = lpJobEntry->DialablePhoneNumber; // Use the job entry phone number since it is alrady translated pairs[0].lpptstrDst = &FaxSendItem->PhoneNumber; pairs[1].lptstrSrc = lpJobQueue->RecipientProfile.lptstrName; pairs[1].lpptstrDst = &FaxSendItem->RecipientName; pairs[2].lptstrSrc = lpJobQueue->SenderProfile.lptstrName; pairs[2].lpptstrDst = &FaxSendItem->SenderName; pairs[3].lptstrSrc = lpJobQueue->SenderProfile.lptstrDepartment; pairs[3].lpptstrDst = &FaxSendItem->SenderDept; pairs[4].lptstrSrc = lpJobQueue->SenderProfile.lptstrCompany; pairs[4].lpptstrDst = &FaxSendItem->SenderCompany; pairs[5].lptstrSrc = lpJobQueue->SenderProfile.lptstrBillingCode; pairs[5].lpptstrDst = &FaxSendItem->BillingCode; pairs[6].lptstrSrc = lpJobQueue->JobParamsEx.lptstrDocumentName; pairs[6].lpptstrDst = &FaxSendItem->DocumentName; pairs[7].lptstrSrc = NULL; pairs[7].lpptstrDst = &FaxSendItem->Tsid; FaxSendItem->JobEntry = lpJobEntry; FaxSendItem->FileName = NULL; // Set by FaxSendThread EnterCriticalSection (&g_CsConfig); bUseDeviceTsid = g_fFaxUseDeviceTsid; LeaveCriticalSection (&g_CsConfig); if (!bUseDeviceTsid) { // Check Sender Tsid if ( lpJobQueue->SenderProfile.lptstrTSID && (lpJobQueue->SenderProfile.lptstrTSID[0] != wcZero)) { pairs[7].lptstrSrc = lpJobQueue->SenderProfile.lptstrTSID; } else { // Use Fax number if ( lpJobQueue->SenderProfile.lptstrFaxNumber && (lpJobQueue->SenderProfile.lptstrFaxNumber[0] != wcZero)) { pairs[7].lptstrSrc = lpJobQueue->SenderProfile.lptstrFaxNumber; } } } else { // Use device Tsid pairs[7].lptstrSrc = lpJobEntry->LineInfo->Tsid; } nRes=MultiStringDup(pairs, sizeof(pairs)/sizeof(STRING_PAIR)); if (nRes!=0) { ec=GetLastError(); // MultiStringDup takes care of freeing the memory for the pairs for which the copy succeeded DebugPrintEx( DEBUG_ERR, TEXT("MultiStringDup failed to copy string with index %d. (ec: %ld)"), nRes-1, ec); goto Error; } EnterCriticalSection (&g_CsJob); lpJobEntry->lpwstrJobTsid = StringDup (FaxSendItem->Tsid); LeaveCriticalSection (&g_CsJob); if (NULL != FaxSendItem->Tsid && NULL == lpJobEntry->lpwstrJobTsid) { ec = GetLastError(); DebugPrintEx(DEBUG_ERR,TEXT("StringDup failed (ec: 0x%0X)"),ec); goto Error; } hThread = CreateThreadAndRefCount( NULL, 0, (LPTHREAD_START_ROUTINE) FaxSendThread, (LPVOID) FaxSendItem, 0, &ThreadId ); if (!hThread) { ec=GetLastError(); DebugPrintEx(DEBUG_ERR,TEXT("CreateThreadAndRefCount for FaxSendThread failed (ec: 0x%0X)"),ec); goto Error; } else { DebugPrintEx(DEBUG_MSG,TEXT("FaxSendThread thread created for job id %d (thread id: 0x%0x)"),lpJobQueue->JobId,ThreadId); } CloseHandle( hThread ); Assert (ERROR_SUCCESS == ec); goto Exit; Error: Assert (ERROR_SUCCESS != ec); if ( FaxSendItem ) { MemFree( FaxSendItem->FileName ); MemFree( FaxSendItem->PhoneNumber ); MemFree( FaxSendItem->Tsid ); MemFree( FaxSendItem->RecipientName ); MemFree( FaxSendItem->SenderName ); MemFree( FaxSendItem->SenderDept ); MemFree( FaxSendItem->SenderCompany ); MemFree( FaxSendItem->BillingCode ); MemFree( FaxSendItem->DocumentName ); MemFree( FaxSendItem ); } if (0 == lpJobQueue->dwLastJobExtendedStatus) { // // Job was never really executed - this is a fatal error // lpJobQueue->dwLastJobExtendedStatus = FSPI_ES_FATAL_ERROR; lpJobQueue->ExStatusString[0] = L'\0'; } if (!MarkJobAsExpired(lpJobQueue)) { DEBUG_ERR, TEXT("[JobId: %ld] MarkJobAsExpired failed (ec: %ld)"), lpJobQueue->JobId, GetLastError(); } EndJob(lpJobEntry); lpJobQueue->JobEntry = NULL; Exit: return ec; } DWORD FaxStatusThread( LPVOID UnUsed ) /*++ Routine Description: This fuction runs asychronously as a separate thread to query the status of all outstanding fax jobs. The status is updated in the JOB_ENTRY structure and the print job is updated with a explanitory string. Arguments: UnUsed - UnUsed pointer Return Value: Always zero. --*/ { PJOB_ENTRY JobEntry; PFAX_DEV_STATUS FaxStatus; BOOL Rval; DWORD Bytes; ULONG_PTR CompletionKey; DWORD dwEventId; DEBUG_FUNCTION_NAME(TEXT("FaxStatusThread")); while( TRUE ) { Rval = GetQueuedCompletionStatus( g_StatusCompletionPortHandle, &Bytes, &CompletionKey, (LPOVERLAPPED*) &FaxStatus, INFINITE ); if (!Rval) { DebugPrintEx(DEBUG_ERR,TEXT("GetQueuedCompletionStatus() failed, ec=0x%08x"), GetLastError() ); continue; } if (SERVICE_SHUT_DOWN_KEY == CompletionKey) { // // Service is shutting down // DebugPrintEx( DEBUG_MSG, TEXT("Service is shutting down")); // // Notify all FaxStatusThreads to terminate // if (!PostQueuedCompletionStatus( g_StatusCompletionPortHandle, 0, SERVICE_SHUT_DOWN_KEY, (LPOVERLAPPED) NULL)) { DebugPrintEx( DEBUG_ERR, TEXT("PostQueuedCompletionStatus failed (SERVICE_SHUT_DOWN_KEY - FaxStatusThread). (ec: %ld)"), GetLastError()); } break; } // // (else we're dealing with a status update from an FSP) // BOOL fBadComletionKey = TRUE; PLINE_INFO pLineInfo = (PLINE_INFO)CompletionKey; fBadComletionKey = pLineInfo->Signature != LINE_SIGNATURE; if (fBadComletionKey) { DebugPrintEx(DEBUG_WRN, TEXT("Bad completion key: 0x%08x"), CompletionKey); continue; } BOOL fBadFaxStatus = TRUE; fBadFaxStatus = FaxStatus->SizeOfStruct != sizeof(FAX_DEV_STATUS); if (fBadFaxStatus) { DebugPrintEx(DEBUG_WRN, TEXT("Bad FAX_DEV_STATUS: 0x%08x"), FaxStatus); continue; } EnterCriticalSection( &g_CsJob ); JobEntry = pLineInfo->JobEntry; if (!JobEntry) { // // The FSP reported a status on a LineInfo for which the running // job no longer exists. // // // Free the completion packet memory // DebugPrintEx( DEBUG_WRN, TEXT("Provider [%s] reported a status packet that was processed after the job entry was already released.\n") TEXT("StatusId : 0x%08x\n") TEXT("Line: %s\n") TEXT("Packet address: %p\n") TEXT("Heap: %p"), pLineInfo->Provider->ProviderName, FaxStatus->StatusId, pLineInfo->DeviceName, FaxStatus, pLineInfo->Provider->HeapHandle); if (!HeapFree(pLineInfo->Provider->HeapHandle, 0, FaxStatus )) { DebugPrintEx( DEBUG_ERR, TEXT("Failed to free orphan device status (ec: %ld)"), GetLastError()); // // Nothing else we can do but report it in debug mode // } FaxStatus = NULL; LeaveCriticalSection( &g_CsJob ); continue; } { DWORD dwJobStatus; DWORD dwExtendedStatus; BOOL bPrivateStatusCode; /* ***** NTRAID#EdgeBugs-12680-2001/05/14-t-nicali What if in the meantime another job is executing on the same line. In this case ->JobEntry will point to ANOTHER job !!! The solution should be to provide as a completion key the JobEntry and not the LineInfo !!! ***** */ Assert (JobEntry->lpJobQueueEntry); if (TRUE == JobEntry->fStopUpdateStatus) { DebugPrintEx( DEBUG_WRN, TEXT("JobId: %ld. fStopUpdateStatus was set. Not updating status %ld"), JobEntry->lpJobQueueEntry->JobId, JobEntry->lpJobQueueEntry->JobStatus); if (!HeapFree(pLineInfo->Provider->HeapHandle, 0, FaxStatus )) { DebugPrintEx( DEBUG_ERR, TEXT("Failed to free orphan device status (ec: %ld)"), GetLastError()); // // Nothing else we can do but report it in debug mode // } FaxStatus = NULL; LeaveCriticalSection (&g_CsJob); continue; } // // Do not update final job states // LegacyJobStatusToStatus( FaxStatus->StatusId, &dwJobStatus, &dwExtendedStatus, &bPrivateStatusCode); if (FSPI_JS_ABORTED == dwJobStatus || FSPI_JS_COMPLETED == dwJobStatus || FSPI_JS_FAILED == dwJobStatus || FSPI_JS_FAILED_NO_RETRY == dwJobStatus || FSPI_JS_DELETED == dwJobStatus ) { // // This is a final status update. Final status is updated from FaxSendThread or FaxReceiveThread // DebugPrintEx( DEBUG_WRN, TEXT("JobId: %ld. Final status code. Not updating status %ld"), JobEntry->lpJobQueueEntry->JobId, dwJobStatus); if (!HeapFree(pLineInfo->Provider->HeapHandle, 0, FaxStatus )) { DebugPrintEx( DEBUG_ERR, TEXT("Failed to free orphan device status (ec: %ld)"), GetLastError()); // // Nothing else we can do but report it in debug mode // } FaxStatus = NULL; LeaveCriticalSection (&g_CsJob); continue; } // // Go ahead with updating the status // FreeFSPIJobStatus(&JobEntry->FSPIJobStatus, FALSE); memset(&JobEntry->FSPIJobStatus, 0, sizeof(FSPI_JOB_STATUS)); JobEntry->FSPIJobStatus.dwSizeOfStruct = sizeof(FSPI_JOB_STATUS); // // This is done for backward compatability with W2K Fax API. // GetJobData() and FAX_GetDeviceStatus() will use this value to return // the job status for legacy jobs. // JobEntry->LineInfo->State = FaxStatus->StatusId; LegacyJobStatusToStatus( FaxStatus->StatusId, &JobEntry->FSPIJobStatus.dwJobStatus, &JobEntry->FSPIJobStatus.dwExtendedStatus, &bPrivateStatusCode); if (bPrivateStatusCode) { JobEntry->FSPIJobStatus.fAvailableStatusInfo |= FSPI_JOB_STATUS_INFO_FSP_PRIVATE_STATUS_CODE; } JobEntry->FSPIJobStatus.dwExtendedStatusStringId = FaxStatus->StringId; JobEntry->FSPIJobStatus.dwPageCount = FaxStatus->PageCount; JobEntry->FSPIJobStatus.fAvailableStatusInfo |= FSPI_JOB_STATUS_INFO_PAGECOUNT; if (FaxStatus->CSI) { JobEntry->FSPIJobStatus.lpwstrRemoteStationId = StringDup( FaxStatus->CSI ); if (!JobEntry->FSPIJobStatus.lpwstrRemoteStationId ) { DebugPrintEx( DEBUG_ERR, TEXT("StringDup( FaxStatus->CSI ) failed (ec: %ld)"), GetLastError()); } } if (FaxStatus->CallerId) { JobEntry->FSPIJobStatus.lpwstrCallerId = StringDup( FaxStatus->CallerId ); if (!JobEntry->FSPIJobStatus.lpwstrCallerId ) { DebugPrintEx( DEBUG_ERR, TEXT("StringDup( FaxStatus.CallerId ) failed (ec: %ld)"), GetLastError()); } } if (FaxStatus->RoutingInfo) { JobEntry->FSPIJobStatus.lpwstrRoutingInfo = StringDup( FaxStatus->RoutingInfo ); if (!JobEntry->FSPIJobStatus.lpwstrRoutingInfo ) { DebugPrintEx( DEBUG_ERR, TEXT("StringDup( FaxStatus.RoutingInfo ) failed (ec: %ld)"), GetLastError()); } } // // Get extended status string // JobEntry->ExStatusString[0] = L'\0'; if (JobEntry->FSPIJobStatus.dwExtendedStatusStringId != 0) { DWORD dwSize; HINSTANCE hLoadInstance; Assert (JobEntry->FSPIJobStatus.dwExtendedStatus != 0); if ( !_tcsicmp(JobEntry->LineInfo->Provider->szGUID,REGVAL_T30_PROVIDER_GUID_STRING) ) { // special case where the FSP is our FSP (fxst30.dll). hLoadInstance = g_hResource; } else { hLoadInstance = JobEntry->LineInfo->Provider->hModule; } dwSize = LoadString (hLoadInstance, JobEntry->FSPIJobStatus.dwExtendedStatusStringId, JobEntry->ExStatusString, ARR_SIZE(JobEntry->ExStatusString)); if (dwSize == 0) { DebugPrintEx( DEBUG_ERR, TEXT("Failed to load extended status string (ec: %ld) stringid : %ld, Provider: %s"), GetLastError(), JobEntry->FSPIJobStatus.dwExtendedStatusStringId, JobEntry->LineInfo->Provider->ImageName); JobEntry->FSPIJobStatus.fAvailableStatusInfo &= ~FSPI_JOB_STATUS_INFO_FSP_PRIVATE_STATUS_CODE; JobEntry->FSPIJobStatus.dwExtendedStatusStringId = 0; JobEntry->FSPIJobStatus.dwExtendedStatus = 0; } } dwEventId = MapFSPIJobStatusToEventId(&JobEntry->FSPIJobStatus); // // Note: W2K Fax did issue notifications with EventId == 0 whenever an // FSP reported proprietry status code. To keep backward compatability // we keep up this behaviour although it might be regarded as a bug // if ( !CreateFaxEvent( JobEntry->LineInfo->PermanentLineID, dwEventId, JobEntry->lpJobQueueEntry->JobId ) ) { DebugPrintEx( DEBUG_ERR, TEXT("CreateFaxEvent failed. (ec: %ld)"), GetLastError()); } EnterCriticalSection (&g_CsQueue); DWORD dwRes = CreateQueueEvent ( FAX_JOB_EVENT_TYPE_STATUS, JobEntry->lpJobQueueEntry ); if (ERROR_SUCCESS != dwRes) { DebugPrintEx( DEBUG_ERR, TEXT("CreateQueueEvent(FAX_JOB_EVENT_TYPE_STATUS) failed for job id %ld (ec: %lc)"), JobEntry->lpJobQueueEntry->UniqueId, dwRes); } LeaveCriticalSection (&g_CsQueue); HeapFree( JobEntry->LineInfo->Provider->HeapHandle, 0, FaxStatus ); } LeaveCriticalSection( &g_CsJob ); } if (!DecreaseServiceThreadsCount()) { DebugPrintEx( DEBUG_ERR, TEXT("DecreaseServiceThreadsCount() failed (ec: %ld)"), GetLastError()); } return 0; } BOOL InitializeJobManager( PREG_FAX_SERVICE FaxReg ) /*++ Routine Description: This fuction initializes the thread pool and FAX service queues. Arguments: ThreadHint - Number of threads to create in the initial pool. Return Value: Thread return value. --*/ { BOOL bRet; DEBUG_FUNCTION_NAME(TEXT("InitializeJobManager")); g_StatusCompletionPortHandle = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, MAX_STATUS_THREADS ); if (!g_StatusCompletionPortHandle) { DWORD ec = GetLastError(); FaxLog( FAXLOG_CATEGORY_INIT, FAXLOG_LEVEL_MIN, 1, MSG_SERVICE_INIT_FAILED_SYS_RESOURCE, DWORD2DECIMAL(ec) ); DebugPrintEx(DEBUG_ERR,TEXT("Failed to create StatusCompletionPort (ec: %ld)"), GetLastError() ); goto Error; } bRet = TRUE; goto Exit; Error: bRet = FALSE; Exit: return bRet; } VOID SetGlobalsFromRegistry( PREG_FAX_SERVICE FaxReg ) { Assert(FaxReg); DEBUG_FUNCTION_NAME(TEXT("SetGlobalsFromRegistry")); EnterCriticalSection (&g_CsConfig); g_dwFaxSendRetries = FaxReg->Retries; g_dwFaxSendRetryDelay = (INT) FaxReg->RetryDelay; g_dwFaxDirtyDays = FaxReg->DirtyDays; g_dwNextJobId = FaxReg->NextJobNumber; g_dwQueueState = FaxReg->dwQueueState; g_fFaxUseDeviceTsid = FaxReg->UseDeviceTsid; g_fFaxUseBranding = FaxReg->Branding; g_fServerCp = FaxReg->ServerCp; g_StartCheapTime = FaxReg->StartCheapTime; g_StopCheapTime = FaxReg->StopCheapTime; LeaveCriticalSection (&g_CsConfig); return; } BOOL FillMsTagInfo( LPTSTR FaxFileName, const JOB_QUEUE * lpcJobQueue ) /*++ Routine Description: Add Ms Tiff Tags to a sent fax. Wraps TiffAddMsTags... Arguments: FaxFileName - Name of the file to archive SendTime - time the fax was sent FaxStatus - job status FaxSend - FAX_SEND structure for sent fax, includes CSID. Return Value: TRUE - The tags were added. FALSE - The tags were not added. --*/ { BOOL success = FALSE; MS_TAG_INFO MsTagInfo = {0}; WCHAR wcZero = L'\0'; PJOB_ENTRY lpJobEntry; LPCFSPI_JOB_STATUS lpcFSPIJobStatus; DEBUG_FUNCTION_NAME(TEXT("FillMsTagInfo")); Assert (lpcJobQueue); Assert (lpcJobQueue->lpParentJob); lpJobEntry = lpcJobQueue->JobEntry; Assert(lpJobEntry); lpcFSPIJobStatus = &lpJobEntry->FSPIJobStatus; if (lpcJobQueue->RecipientProfile.lptstrName && (lpcJobQueue->RecipientProfile.lptstrName[0] != wcZero) ) { MsTagInfo.RecipName = lpcJobQueue->RecipientProfile.lptstrName; } if (lpcJobQueue->RecipientProfile.lptstrFaxNumber && (lpcJobQueue->RecipientProfile.lptstrFaxNumber[0] != wcZero) ) { MsTagInfo.RecipNumber = lpcJobQueue->RecipientProfile.lptstrFaxNumber; } if (lpcJobQueue->SenderProfile.lptstrName && (lpcJobQueue->SenderProfile.lptstrName[0] != wcZero) ) { MsTagInfo.SenderName = lpcJobQueue->SenderProfile.lptstrName; } if (lpcFSPIJobStatus->lpwstrRoutingInfo && (lpcFSPIJobStatus->lpwstrRoutingInfo[0] != wcZero) ) { MsTagInfo.Routing = lpcFSPIJobStatus->lpwstrRoutingInfo; } if (lpcFSPIJobStatus->lpwstrRemoteStationId && (lpcFSPIJobStatus->lpwstrRemoteStationId[0] != wcZero) ) { MsTagInfo.Csid = lpcFSPIJobStatus->lpwstrRemoteStationId; } if (lpJobEntry->lpwstrJobTsid && (lpJobEntry->lpwstrJobTsid[0] != wcZero) ) { MsTagInfo.Tsid = lpJobEntry->lpwstrJobTsid; } if (!GetRealFaxTimeAsFileTime (lpJobEntry, FAX_TIME_TYPE_START, (FILETIME*)&MsTagInfo.StartTime)) { MsTagInfo.StartTime = 0; DebugPrintEx(DEBUG_ERR,TEXT("GetRealFaxTimeAsFileTime (Start time) Failed (ec: %ld)"), GetLastError() ); } if (!GetRealFaxTimeAsFileTime (lpJobEntry, FAX_TIME_TYPE_END, (FILETIME*)&MsTagInfo.EndTime)) { MsTagInfo.EndTime = 0; DebugPrintEx(DEBUG_ERR,TEXT("GetRealFaxTimeAsFileTime (Eend time) Failed (ec: %ld)"), GetLastError() ); } MsTagInfo.SubmissionTime = lpcJobQueue->lpParentJob->SubmissionTime; MsTagInfo.OriginalScheduledTime = lpcJobQueue->lpParentJob->OriginalScheduleTime; MsTagInfo.Type = JT_SEND; if (lpJobEntry->LineInfo->DeviceName && (lpJobEntry->LineInfo->DeviceName[0] != wcZero) ) { MsTagInfo.Port = lpJobEntry->LineInfo->DeviceName; } MsTagInfo.Pages = lpcJobQueue->PageCount; MsTagInfo.Retries = lpcJobQueue->SendRetries; if (lpcJobQueue->RecipientProfile.lptstrCompany && (lpcJobQueue->RecipientProfile.lptstrCompany[0] != wcZero) ) { MsTagInfo.RecipCompany = lpcJobQueue->RecipientProfile.lptstrCompany; } if (lpcJobQueue->RecipientProfile.lptstrStreetAddress && (lpcJobQueue->RecipientProfile.lptstrStreetAddress[0] != wcZero) ) { MsTagInfo.RecipStreet = lpcJobQueue->RecipientProfile.lptstrStreetAddress; } if (lpcJobQueue->RecipientProfile.lptstrCity && (lpcJobQueue->RecipientProfile.lptstrCity[0] != wcZero) ) { MsTagInfo.RecipCity = lpcJobQueue->RecipientProfile.lptstrCity; } if (lpcJobQueue->RecipientProfile.lptstrState && (lpcJobQueue->RecipientProfile.lptstrState[0] != wcZero) ) { MsTagInfo.RecipState = lpcJobQueue->RecipientProfile.lptstrState; } if (lpcJobQueue->RecipientProfile.lptstrZip && (lpcJobQueue->RecipientProfile.lptstrZip[0] != wcZero) ) { MsTagInfo.RecipZip = lpcJobQueue->RecipientProfile.lptstrZip; } if (lpcJobQueue->RecipientProfile.lptstrCountry && (lpcJobQueue->RecipientProfile.lptstrCountry[0] != wcZero) ) { MsTagInfo.RecipCountry = lpcJobQueue->RecipientProfile.lptstrCountry; } if (lpcJobQueue->RecipientProfile.lptstrTitle && (lpcJobQueue->RecipientProfile.lptstrTitle[0] != wcZero) ) { MsTagInfo.RecipTitle = lpcJobQueue->RecipientProfile.lptstrTitle; } if (lpcJobQueue->RecipientProfile.lptstrDepartment && (lpcJobQueue->RecipientProfile.lptstrDepartment[0] != wcZero) ) { MsTagInfo.RecipDepartment = lpcJobQueue->RecipientProfile.lptstrDepartment; } if (lpcJobQueue->RecipientProfile.lptstrOfficeLocation && (lpcJobQueue->RecipientProfile.lptstrOfficeLocation[0] != wcZero) ) { MsTagInfo.RecipOfficeLocation = lpcJobQueue->RecipientProfile.lptstrOfficeLocation; } if (lpcJobQueue->RecipientProfile.lptstrHomePhone && (lpcJobQueue->RecipientProfile.lptstrHomePhone[0] != wcZero) ) { MsTagInfo.RecipHomePhone = lpcJobQueue->RecipientProfile.lptstrHomePhone; } if (lpcJobQueue->RecipientProfile.lptstrOfficePhone && (lpcJobQueue->RecipientProfile.lptstrOfficePhone[0] != wcZero) ) { MsTagInfo.RecipOfficePhone = lpcJobQueue->RecipientProfile.lptstrOfficePhone; } if (lpcJobQueue->RecipientProfile.lptstrEmail && (lpcJobQueue->RecipientProfile.lptstrEmail[0] != wcZero) ) { MsTagInfo.RecipEMail = lpcJobQueue->RecipientProfile.lptstrEmail; } if (lpcJobQueue->SenderProfile.lptstrFaxNumber && (lpcJobQueue->SenderProfile.lptstrFaxNumber[0] != wcZero) ) { MsTagInfo.SenderNumber = lpcJobQueue->SenderProfile.lptstrFaxNumber; } if (lpcJobQueue->SenderProfile.lptstrCompany && (lpcJobQueue->SenderProfile.lptstrCompany[0] != wcZero) ) { MsTagInfo.SenderCompany = lpcJobQueue->SenderProfile.lptstrCompany; } if (lpcJobQueue->SenderProfile.lptstrStreetAddress && (lpcJobQueue->SenderProfile.lptstrStreetAddress[0] != wcZero) ) { MsTagInfo.SenderStreet = lpcJobQueue->SenderProfile.lptstrStreetAddress; } if (lpcJobQueue->SenderProfile.lptstrCity && (lpcJobQueue->SenderProfile.lptstrCity[0] != wcZero) ) { MsTagInfo.SenderCity = lpcJobQueue->SenderProfile.lptstrCity; } if (lpcJobQueue->SenderProfile.lptstrState && (lpcJobQueue->SenderProfile.lptstrState[0] != wcZero) ) { MsTagInfo.SenderState = lpcJobQueue->SenderProfile.lptstrState; } if (lpcJobQueue->SenderProfile.lptstrZip && (lpcJobQueue->SenderProfile.lptstrZip[0] != wcZero) ) { MsTagInfo.SenderZip = lpcJobQueue->SenderProfile.lptstrZip; } if (lpcJobQueue->SenderProfile.lptstrCountry && (lpcJobQueue->SenderProfile.lptstrCountry[0] != wcZero) ) { MsTagInfo.SenderCountry = lpcJobQueue->SenderProfile.lptstrCountry; } if (lpcJobQueue->SenderProfile.lptstrTitle && (lpcJobQueue->SenderProfile.lptstrTitle[0] != wcZero) ) { MsTagInfo.SenderTitle = lpcJobQueue->SenderProfile.lptstrTitle; } if (lpcJobQueue->SenderProfile.lptstrDepartment && (lpcJobQueue->SenderProfile.lptstrDepartment[0] != wcZero) ) { MsTagInfo.SenderDepartment = lpcJobQueue->SenderProfile.lptstrDepartment; } if (lpcJobQueue->SenderProfile.lptstrOfficeLocation && (lpcJobQueue->SenderProfile.lptstrOfficeLocation[0] != wcZero) ) { MsTagInfo.SenderOfficeLocation = lpcJobQueue->SenderProfile.lptstrOfficeLocation; } if (lpcJobQueue->SenderProfile.lptstrHomePhone && (lpcJobQueue->SenderProfile.lptstrHomePhone[0] != wcZero) ) { MsTagInfo.SenderHomePhone = lpcJobQueue->SenderProfile.lptstrHomePhone; } if (lpcJobQueue->SenderProfile.lptstrOfficePhone && (lpcJobQueue->SenderProfile.lptstrOfficePhone[0] != wcZero) ) { MsTagInfo.SenderOfficePhone = lpcJobQueue->SenderProfile.lptstrOfficePhone; } if (lpcJobQueue->SenderProfile.lptstrEmail && (lpcJobQueue->SenderProfile.lptstrEmail[0] != wcZero) ) { MsTagInfo.SenderEMail = lpcJobQueue->SenderProfile.lptstrEmail; } if (lpcJobQueue->SenderProfile.lptstrBillingCode && (lpcJobQueue->SenderProfile.lptstrBillingCode[0] != wcZero) ) { MsTagInfo.SenderBilling = lpcJobQueue->SenderProfile.lptstrBillingCode; } if (lpcJobQueue->JobParamsEx.lptstrDocumentName && (lpcJobQueue->JobParamsEx.lptstrDocumentName[0] != wcZero) ) { MsTagInfo.Document = lpcJobQueue->JobParamsEx.lptstrDocumentName; } if (lpcJobQueue->lpParentJob->CoverPageEx.lptstrSubject && (lpcJobQueue->lpParentJob->CoverPageEx.lptstrSubject[0] != wcZero) ) { MsTagInfo.Subject = lpcJobQueue->lpParentJob->CoverPageEx.lptstrSubject; } if (lpcJobQueue->lpParentJob->UserName && (lpcJobQueue->lpParentJob->UserName[0] != wcZero) ) { MsTagInfo.SenderUserName = lpcJobQueue->lpParentJob->UserName; } if (lpcJobQueue->SenderProfile.lptstrTSID && (lpcJobQueue->SenderProfile.lptstrTSID[0] != wcZero) ) { MsTagInfo.SenderTsid = lpcJobQueue->SenderProfile.lptstrTSID; } MsTagInfo.dwStatus = JS_COMPLETED; // We archive only succesfully sent faxes MsTagInfo.dwExtendedStatus = lpcFSPIJobStatus->dwExtendedStatus; if (lpJobEntry->ExStatusString[0] != wcZero) { MsTagInfo.lptstrExtendedStatus = lpJobEntry->ExStatusString; } MsTagInfo.dwlBroadcastId = lpcJobQueue->lpParentJob->UniqueId; MsTagInfo.Priority = lpcJobQueue->lpParentJob->JobParamsEx.Priority; success = TiffAddMsTags( FaxFileName, &MsTagInfo, TRUE ); if (!success) { DebugPrintEx( DEBUG_ERR, TEXT("TiffAddMsTags failed, ec = %ld"), GetLastError ()); } if(!AddNTFSStorageProperties( FaxFileName, &MsTagInfo , TRUE )) { if (ERROR_OPEN_FAILED != GetLastError ()) { // // If AddNTFSStorageProperties fails with ERROR_OPEN_FAIL then the archive // folder is not on an NTFS 5 partition. // This is ok - NTFS properties are a backup mechanism but not a must // DebugPrintEx( DEBUG_ERR, TEXT("AddNTFSStorageProperties failed, ec = %ld"), GetLastError ()); success = FALSE; } else { DebugPrintEx( DEBUG_WRN, TEXT("AddNTFSStorageProperties failed with ERROR_OPEN_FAIL. Probably not an NTFS 5 partition")); } } return success; } // FillMsTagInfo //********************************************************************************* //* Name: ArchiveOutboundJob() //* Author: Ronen Barenboim //* Date: June 03, 1999 //********************************************************************************* //* DESCRIPTION: //* Archive a tiff file that has been sent by copying the file to an archive //* directory. Also adds the MSTags to the new file generated at the //* archive (not to the source file). //* //* PARAMETERS: //* [IN ] const JOB_QUEUE * lpcJobQueue //* Pointer to the recipient job which is to be archived. //* //* RETURN VALUE: //* TRUE if the opeation succeeded. //* FALSE if the operation failed. //********************************************************************************* BOOL ArchiveOutboundJob( const JOB_QUEUE * lpcJobQueue ) { BOOL rVal = FALSE; WCHAR ArchiveFileName[MAX_PATH] = {0}; LPWSTR lpwszUserSid = NULL; DWORD ec = ERROR_SUCCESS; WCHAR wszArchiveFolder[MAX_PATH]; DEBUG_FUNCTION_NAME(TEXT("ArchiveOutboundJob")); Assert(lpcJobQueue); // // be sure that the dir exists // EnterCriticalSection (&g_CsConfig); lstrcpyn ( wszArchiveFolder, g_ArchivesConfig[FAX_MESSAGE_FOLDER_SENTITEMS].lpcstrFolder, MAX_PATH); LeaveCriticalSection (&g_CsConfig); ec=IsValidFaxFolder(wszArchiveFolder); if (ERROR_SUCCESS != ec) { DebugPrintEx(DEBUG_ERR, TEXT("IsValidFaxFolder failed for folder : %s (ec=%lu)."), wszArchiveFolder, ec); FaxLog( FAXLOG_CATEGORY_OUTBOUND, FAXLOG_LEVEL_MIN, 2, MSG_FAX_ARCHIVE_OUTBOX_FOLDER_ERR, wszArchiveFolder, DWORD2DECIMAL(ec) ); goto Error; } // // get the user sid string // if (!ConvertSidToStringSid(lpcJobQueue->lpParentJob->UserSid, &lpwszUserSid)) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("ConvertSidToStringSid() failed (ec: %ld)"), ec); goto Error; } // // get the file name // if (GenerateUniqueArchiveFileName( wszArchiveFolder, ArchiveFileName, ARR_SIZE(ArchiveFileName), lpcJobQueue->UniqueId, lpwszUserSid)) { rVal = TRUE; } else { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("Failed to generate unique name for archive file at dir [%s] (ec: %ld)"), wszArchiveFolder, ec); FaxLog( FAXLOG_CATEGORY_OUTBOUND, FAXLOG_LEVEL_MIN, 1, MSG_FAX_ARCHIVE_CREATE_FILE_FAILED, DWORD2DECIMAL(ec) ); goto Error; } if (rVal) { Assert(lpcJobQueue->FileName); rVal = CopyFile( lpcJobQueue->FileName, ArchiveFileName, FALSE ); if (!rVal) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("CopyFile [%s] to [%s] failed. (ec: %ld)"), lpcJobQueue->FileName, ArchiveFileName, ec); FaxLog( FAXLOG_CATEGORY_OUTBOUND, FAXLOG_LEVEL_MIN, 1, MSG_FAX_ARCHIVE_CREATE_FILE_FAILED, DWORD2DECIMAL(ec) ); if (!DeleteFile(ArchiveFileName)) { DebugPrintEx( DEBUG_ERR, TEXT("DeleteFile [%s] failed. (ec: %ld)"), ArchiveFileName, GetLastError()); } goto Error; } } if (rVal) { DWORD dwRes; HANDLE hFind; WIN32_FIND_DATA FindFileData; if (!FillMsTagInfo( ArchiveFileName, lpcJobQueue )) { dwRes = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("Failed to add MS TIFF tags to archived file %s. (ec: %ld)"), ArchiveFileName, dwRes); FaxLog( FAXLOG_CATEGORY_OUTBOUND, FAXLOG_LEVEL_MIN, 2, MSG_FAX_ARCHIVE_NO_TAGS, ArchiveFileName, DWORD2HEX(dwRes) ); } dwRes = CreateArchiveEvent (lpcJobQueue->UniqueId, FAX_EVENT_TYPE_OUT_ARCHIVE, FAX_JOB_EVENT_TYPE_ADDED, lpcJobQueue->lpParentJob->UserSid); if (ERROR_SUCCESS != dwRes) { DebugPrintEx( DEBUG_ERR, TEXT("CreateConfigEvent(FAX_CONFIG_TYPE_*_ARCHIVE) failed (ec: %lc)"), dwRes); } hFind = FindFirstFile( ArchiveFileName, &FindFileData); if (INVALID_HANDLE_VALUE == hFind) { DebugPrintEx( DEBUG_ERR, TEXT("FindFirstFile failed (ec: %lc), File %s"), GetLastError(), ArchiveFileName); } else { // Update the archive size - for quota management EnterCriticalSection (&g_CsConfig); if (FAX_ARCHIVE_FOLDER_INVALID_SIZE != g_ArchivesConfig[FAX_MESSAGE_FOLDER_SENTITEMS].dwlArchiveSize) { g_ArchivesConfig[FAX_MESSAGE_FOLDER_SENTITEMS].dwlArchiveSize += (MAKELONGLONG(FindFileData.nFileSizeLow ,FindFileData.nFileSizeHigh)); } LeaveCriticalSection (&g_CsConfig); Assert (FindFileData.nFileSizeLow); if (!FindClose(hFind)) { DebugPrintEx( DEBUG_ERR, TEXT("FindClose failed (ec: %lc)"), GetLastError()); } } FaxLog( FAXLOG_CATEGORY_OUTBOUND, FAXLOG_LEVEL_MAX, 2, MSG_FAX_SENT_ARCHIVE_SUCCESS, lpcJobQueue->FileName, ArchiveFileName ); } Assert( ERROR_SUCCESS == ec); goto Exit; Error: Assert( ERROR_SUCCESS != ec); FaxLog( FAXLOG_CATEGORY_OUTBOUND, FAXLOG_LEVEL_MIN, 3, MSG_FAX_ARCHIVE_FAILED, lpcJobQueue->FileName, wszArchiveFolder, DWORD2HEX(GetLastError()) ); Exit: if (ERROR_SUCCESS != ec) { SetLastError(ec); } if (lpwszUserSid != NULL) { LocalFree (lpwszUserSid); } return (ERROR_SUCCESS == ec); } BOOL UpdatePerfCounters(const JOB_QUEUE * lpcJobQueue) { SYSTEMTIME SystemTime ; DWORD Seconds ; HANDLE FileHandle ; DWORD Bytes = 0 ; /// Compute #bytes in the file FaxSend.FileName and stick it here! const JOB_ENTRY * lpcJobEntry; DEBUG_FUNCTION_NAME(TEXT("UpdatePerfCounters")); Assert(lpcJobQueue); lpcJobEntry = lpcJobQueue->JobEntry; Assert(lpcJobEntry); FileHandle = SafeCreateFile( lpcJobEntry->lpJobQueueEntry->FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(FileHandle != INVALID_HANDLE_VALUE) { Bytes = GetFileSize( FileHandle, NULL ); CloseHandle( FileHandle ); } if (!FileTimeToSystemTime( (FILETIME*)&lpcJobEntry->ElapsedTime, &SystemTime )) { DebugPrintEx( DEBUG_ERR, TEXT("FileTimeToSystemTime failed (ec: %ld)"), GetLastError()); Assert(FALSE); memset(&SystemTime,0,sizeof(SYSTEMTIME)); } Seconds = (DWORD)( SystemTime.wSecond + 60 * ( SystemTime.wMinute + 60 * SystemTime.wHour )); InterlockedIncrement( (PLONG)&g_pFaxPerfCounters->OutboundFaxes ); InterlockedIncrement( (PLONG)&g_pFaxPerfCounters->TotalFaxes ); InterlockedExchangeAdd( (PLONG)&g_pFaxPerfCounters->OutboundPages, (LONG)lpcJobEntry->FSPIJobStatus.dwPageCount ); InterlockedExchangeAdd( (PLONG)&g_pFaxPerfCounters->TotalPages, (LONG)lpcJobEntry->FSPIJobStatus.dwPageCount ); EnterCriticalSection( &g_CsPerfCounters ); g_dwOutboundSeconds += Seconds; g_dwTotalSeconds += Seconds; g_pFaxPerfCounters->OutboundMinutes = g_dwOutboundSeconds / 60 ; g_pFaxPerfCounters->TotalMinutes = g_dwTotalSeconds / 60 ; g_pFaxPerfCounters->OutboundBytes += Bytes; g_pFaxPerfCounters->TotalBytes += Bytes; LeaveCriticalSection( &g_CsPerfCounters ); return TRUE; } BOOL MarkJobAsExpired(PJOB_QUEUE lpJobQueue) { FILETIME CurrentFileTime; LARGE_INTEGER NewTime; DWORD dwMaxRetries; BOOL rVal = TRUE; DEBUG_FUNCTION_NAME(TEXT("MarkJobAsExpired")); Assert(lpJobQueue); Assert( JT_SEND == lpJobQueue->JobType || JT_ROUTING == lpJobQueue->JobType ); EnterCriticalSection(&g_CsQueue); lpJobQueue->JobStatus = JS_RETRIES_EXCEEDED; EnterCriticalSection (&g_CsConfig); dwMaxRetries = g_dwFaxSendRetries; LeaveCriticalSection (&g_CsConfig); lpJobQueue->SendRetries = dwMaxRetries + 1; // // Set the job's ScheduleTime field to the time it totaly failed. // (current time). // GetSystemTimeAsFileTime( &CurrentFileTime ); //Can not fail (Win32 SDK) NewTime.LowPart = CurrentFileTime.dwLowDateTime; NewTime.HighPart = CurrentFileTime.dwHighDateTime; lpJobQueue->ScheduleTime = NewTime.QuadPart; if (!CommitQueueEntry(lpJobQueue)) { DebugPrintEx( DEBUG_ERR, TEXT("CommitQueueEntry() for recipien job %s has failed. (ec: %ld)"), lpJobQueue->FileName, GetLastError()); rVal = FALSE; } if (JT_SEND == lpJobQueue->JobType) { Assert (lpJobQueue->lpParentJob); lpJobQueue->lpParentJob->dwFailedRecipientJobsCount+=1; // // The parent job keeps the schedule of the last recipient job that failed. // The job retention policy for the parent will be based on that // schedule. lpJobQueue->lpParentJob->ScheduleTime = lpJobQueue->ScheduleTime; if (!CommitQueueEntry(lpJobQueue->lpParentJob)) { DebugPrintEx( DEBUG_ERR, TEXT("CommitQueueEntry() for parent job %s has failed. (ec: %ld)"), lpJobQueue->lpParentJob->FileName, GetLastError()); rVal = FALSE; } } LeaveCriticalSection(&g_CsQueue); return rVal; } //********************************************************************************* //* Name: CreateJobEntry() //* Author: Ronen Barenboim //* Date: May 31, 1999 //********************************************************************************* //* DESCRIPTION: //* Creates and initializes a new JOB_ENTRY. //* Opens the line the job is to be executed on (if it is a TAPI line) //* and creates the attachement between the line and the job. //* PARAMETERS: //* [IN/OUT] PJOB_QUEUE lpJobQueue //* For outgoing jobs this points to the JOB_QUEUE of the outgoing job. //* for receive job this should be set to NULL. //* [IN/OUT] LINE_INFO * lpLineInfo //* A pointer to the LINE_INFO information of the line on which the job //* is to be executed. //* [IN ] BOOL bTranslateNumber //* TRUE if the recipient number needs to be translated into dilable //* string (needed for legacy FaxDevSend() where the number must be //* dilable and not canonical). //* //* RETURN VALUE: //* //********************************************************************************* PJOB_ENTRY CreateJobEntry( PJOB_QUEUE lpJobQueue, LINE_INFO * lpLineInfo, BOOL bTranslateNumber ) { BOOL Failure = TRUE; PJOB_ENTRY JobEntry = NULL; DWORD rc = ERROR_SUCCESS;; DEBUG_FUNCTION_NAME(TEXT("CreateJobEntry")); Assert(!(lpJobQueue && lpJobQueue->JobType != JT_SEND)); Assert(!(bTranslateNumber && !lpJobQueue)); Assert (lpLineInfo); JobEntry = (PJOB_ENTRY) MemAlloc( sizeof(JOB_ENTRY) ); if (!JobEntry) { rc=GetLastError(); DebugPrintEx(DEBUG_ERR,_T("Failed to allocated memory for JOB_ENTRY.")); goto exit; } memset(JobEntry, 0, sizeof(JOB_ENTRY)); if (lpJobQueue) { if (! _tcslen(lpJobQueue->tczDialableRecipientFaxNumber)) { // // The Fax Number was not compound, make translation as before // if (bTranslateNumber) { rc = TranslateCanonicalNumber(lpJobQueue->RecipientProfile.lptstrFaxNumber, lpLineInfo->DeviceId, JobEntry->DialablePhoneNumber, ARR_SIZE(JobEntry->DialablePhoneNumber), JobEntry->DisplayablePhoneNumber, ARR_SIZE(JobEntry->DisplayablePhoneNumber)); if (ERROR_SUCCESS != rc) { DebugPrintEx( DEBUG_ERR, TEXT("TranslateCanonicalNumber() faield for number: %s (ec: %ld)"), lpJobQueue->RecipientProfile.lptstrFaxNumber, rc); goto exit; } } else { _tcsncpy(JobEntry->DialablePhoneNumber, lpJobQueue->RecipientProfile.lptstrFaxNumber, SIZEOF_PHONENO ); JobEntry->DialablePhoneNumber[SIZEOF_PHONENO - 1] = '\0'; _tcsncpy(JobEntry->DisplayablePhoneNumber, lpJobQueue->RecipientProfile.lptstrFaxNumber, SIZEOF_PHONENO ); JobEntry->DisplayablePhoneNumber[SIZEOF_PHONENO - 1] = '\0'; } } else { // // The Fax Number was compound, no translation needed // Take Dialable from JobQueue and Displayable from Recipient's PersonalProfile's FaxNumber // _tcsncpy(JobEntry->DialablePhoneNumber, lpJobQueue->tczDialableRecipientFaxNumber, SIZEOF_PHONENO ); _tcsncpy(JobEntry->DisplayablePhoneNumber, lpJobQueue->RecipientProfile.lptstrFaxNumber, (SIZEOF_PHONENO - 1)); JobEntry->DisplayablePhoneNumber[SIZEOF_PHONENO - 1] = '\0'; } } else { // // lpJobQueue is NULL // JobEntry->DialablePhoneNumber[0] = L'\0'; JobEntry->DisplayablePhoneNumber[0] = L'\0'; } JobEntry->CallHandle = 0; JobEntry->InstanceData = 0; JobEntry->LineInfo = lpLineInfo; JobEntry->SendIdx = -1; JobEntry->Released = FALSE; JobEntry->lpJobQueueEntry = lpJobQueue; JobEntry->bFSPJobInProgress = FALSE; memset(&JobEntry->FSPIJobStatus,0,sizeof(FSPI_JOB_STATUS)); JobEntry->FSPIJobStatus.dwSizeOfStruct = sizeof(FSPI_JOB_STATUS); JobEntry->FSPIJobStatus.dwJobStatus = FSPI_JS_UNKNOWN; GetSystemTimeAsFileTime( (FILETIME*) &JobEntry->StartTime ); EnterCriticalSection (&g_CsLine); if (!(lpLineInfo->Flags & FPF_VIRTUAL) && (!lpLineInfo->hLine)) { if (!OpenTapiLine( lpLineInfo )) { rc = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("OpenTapiLine failed. (ec: %ld)"), rc); LeaveCriticalSection (&g_CsLine); goto exit; } } // // Attach the job to the line selected to service it. // lpLineInfo->JobEntry = JobEntry; LeaveCriticalSection (&g_CsLine); Failure = FALSE; exit: if (Failure) { // Failure is initialized to TRUE if (JobEntry) { MemFree( JobEntry ); } JobEntry = NULL; } if (ERROR_SUCCESS != rc) { SetLastError(rc); } return JobEntry; } // CreateJobEntry //********************************************************************************* //* Name: TranslateCanonicalNumber() //* Author: Ronen Barenboim //* Date: May 31, 1999 //********************************************************************************* //* DESCRIPTION: //* Translates a canonical number to a dilable + displayable number. //* //* PARAMETERS: //* [IN ] LPTSTR lptstrFaxNumber //* The canonical number to translate. //* //* [IN ] DWORD dwDeviceID //* The device ID. //* //* [OUT] LPTSTR lptstrDialableAddress //* Buffer to receive the dialable translated address. //* //* [IN] DWORD dwDialableAddressCount //* size in TCHARs of the buffer pointed by lptstrDialableAddress //* //* [OUT] LPTSTR lptstrDisplayableAddress //* Buffer to receive the displayable translated address. //* //* [IN] DWORD dwDisplayableAddressCount //* size in TCHARs of the buffer pointed by lptstrDialableAddress //* //* RETURN VALUE: //* Win32 / HRESULT error code //********************************************************************************* static DWORD TranslateCanonicalNumber( LPTSTR lptstrCanonicalFaxNumber, DWORD dwDeviceID, LPTSTR lptstrDialableAddress, DWORD dwDialableAddressCount, LPTSTR lptstrDisplayableAddress, DWORD dwDisplayableAddressCount ) { DWORD ec = ERROR_SUCCESS; LPLINETRANSLATEOUTPUT LineTranslateOutput = NULL; DEBUG_FUNCTION_NAME(TEXT("TranslateCanonicalNumber")); Assert(lptstrCanonicalFaxNumber && lptstrDialableAddress && lptstrDisplayableAddress); ec = MyLineTranslateAddress( lptstrCanonicalFaxNumber, dwDeviceID, &LineTranslateOutput ); if (ERROR_SUCCESS == ec) { LPTSTR lptstrTranslateBuffer; HRESULT hr; // // Copy displayable string // TAPI returns credit card numbers in the displayable string. // return the input canonical number as the displayable string. // hr = StringCchCopy( lptstrDisplayableAddress, dwDisplayableAddressCount, lptstrCanonicalFaxNumber); if (FAILED(hr)) { DebugPrintEx( DEBUG_ERR, TEXT("StringCchCopy() failed (ec: %ld)"), hr); ec = HRESULT_CODE(hr); goto Exit; } // // Copy dialable string // Assert (LineTranslateOutput->dwDialableStringSize > 0); lptstrTranslateBuffer=(LPTSTR)((LPBYTE)LineTranslateOutput + LineTranslateOutput->dwDialableStringOffset); hr = StringCchCopy( lptstrDialableAddress, dwDialableAddressCount, lptstrTranslateBuffer); if (FAILED(hr)) { DebugPrintEx( DEBUG_ERR, TEXT("StringCchCopy() failed (ec: %ld)"), hr); ec = HRESULT_CODE(hr); goto Exit; } } else { // ec is a Tapi ERROR DebugPrintEx( DEBUG_ERR, TEXT("MyLineTranslateAddress() failed for fax number: [%s] (ec: %ld)"), lptstrCanonicalFaxNumber, ec); goto Exit; } Assert (ERROR_SUCCESS == ec); Exit: MemFree( LineTranslateOutput ); if (ERROR_SUCCESS != ec) { SetLastError(ec); } return ec; } // TranslateCanonicalNumber //********************************************************************************* //* Name: HandleCompletedSendJob() //* Author: Ronen Barenboim //* Date: June 01, 1999 //********************************************************************************* //* DESCRIPTION: //* Handles the completion of a recipient job. Called when a recipient job //* has reaced a JS_COMPLETED state. //* //* IMPORTANT- This call can be blocking. Calling thread MUST NOT hold any critical section //* //* - Marks the job as completed (JS_COMPLETED). //* - Archives the sent file if required. //* - Sends a positive receipt //* - Removes the parent job if required. //* //* PARAMETERS: //* [IN ] PJOB_ENTRY lpJobEntry //* //* RETURN VALUE: //* TRUE //* If the operation completed successfully. //* FALSE //* If the operation failed. Call GetLastError() for extended errror //* information. //********************************************************************************* BOOL HandleCompletedSendJob(PJOB_ENTRY lpJobEntry) { PJOB_QUEUE lpJobQueue = NULL; DWORD ec = 0; BOOL fCOMInitiliazed = FALSE; HRESULT hr; BOOL bArchiveSentItems; DWORD dwRes; DEBUG_FUNCTION_NAME(TEXT("HandleCompletedSendJob)")); EnterCriticalSection ( &g_CsJob ); EnterCriticalSection (&g_CsConfig); bArchiveSentItems = g_ArchivesConfig[FAX_MESSAGE_FOLDER_SENTITEMS].bUseArchive; LeaveCriticalSection (&g_CsConfig); Assert(lpJobEntry); lpJobQueue = lpJobEntry->lpJobQueueEntry; Assert(lpJobQueue); Assert(JT_SEND == lpJobQueue->JobType); Assert(FSPI_JS_COMPLETED == lpJobEntry->FSPIJobStatus.dwJobStatus); // // Update end time in JOB_ENTRY // GetSystemTimeAsFileTime( (FILETIME*) &lpJobEntry->EndTime ); // // Update elapsed time in JOB_ENTRY // Assert (lpJobEntry->EndTime >= lpJobEntry->StartTime); lpJobEntry->ElapsedTime = lpJobEntry->EndTime - lpJobEntry->StartTime; // // We generate a full tiff for each recipient // so we will have something to put in the send archive. // if (!lpJobQueue->FileName) { DebugPrintEx( DEBUG_MSG, TEXT("[JobId: %ld] Generating body for recipient job."), lpJobQueue->JobId ); if (!CreateTiffFileForJob(lpJobQueue)) { dwRes = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] CreateTiffFileForJob failed. (ec: %ld)"), lpJobQueue->JobId, dwRes); FaxLog( FAXLOG_CATEGORY_OUTBOUND, FAXLOG_LEVEL_MIN, 1, MSG_FAX_TIFF_CREATE_FAILED_NO_ARCHIVE, g_wszFaxQueueDir, DWORD2DECIMAL(dwRes) ); } } // Needed for Archiving hr = CoInitialize (NULL); if (FAILED (hr)) { WCHAR wszArchiveFolder[MAX_PATH]; EnterCriticalSection (&g_CsConfig); lstrcpyn ( wszArchiveFolder, g_ArchivesConfig[FAX_MESSAGE_FOLDER_SENTITEMS].lpcstrFolder, MAX_PATH); LeaveCriticalSection (&g_CsConfig); DebugPrintEx( DEBUG_ERR, TEXT("CoInitilaize failed, err %ld"), hr); FaxLog( FAXLOG_CATEGORY_OUTBOUND, FAXLOG_LEVEL_MIN, 3, MSG_FAX_ARCHIVE_FAILED, lpJobQueue->FileName, wszArchiveFolder, DWORD2DECIMAL(hr) ); } else { fCOMInitiliazed = TRUE; } if (lpJobQueue->FileName) //might be null if we failed to generate a TIFF { // // Archive the file (also adds MS Tags to the tiff at the archive directory) // if (bArchiveSentItems && fCOMInitiliazed) { if (!ArchiveOutboundJob(lpJobQueue)) { DebugPrintEx( DEBUG_ERR, TEXT("JobId: %ld] ArchiveOutboundJob() failed (ec: %ld)"), lpJobQueue->JobId, GetLastError()); // // The event log entry is generated by the function itself // } } } // // Log the succesful send to the event log // EnterCriticalSection (&g_CsOutboundActivityLogging); if (INVALID_HANDLE_VALUE == g_hOutboxActivityLogFile) { DebugPrintEx(DEBUG_ERR, TEXT("Logging not initialized")); } else { if (!LogOutboundActivity(lpJobQueue)) { DebugPrintEx(DEBUG_ERR, TEXT("Logging outbound activity failed")); } } LeaveCriticalSection (&g_CsOutboundActivityLogging); if (fCOMInitiliazed == TRUE) { CoUninitialize (); } FaxLogSend(lpJobQueue, FALSE); // // Increment counters for Performance Monitor // if (g_pFaxPerfCounters) { if (!UpdatePerfCounters(lpJobQueue)) { DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] UpdatePerfCounters() failed. (ec: %ld)"), lpJobQueue->JobId, GetLastError()); Assert(FALSE); } } EnterCriticalSection ( &g_CsQueue ); // // Mark the job as completed (new client API) // lpJobQueue->JobStatus = JS_COMPLETED; // // Save the last extended status before ending this job // lpJobQueue->dwLastJobExtendedStatus = lpJobQueue->JobEntry->FSPIJobStatus.dwExtendedStatus; hr = StringCchCopy( lpJobQueue->ExStatusString, ARR_SIZE(lpJobQueue->ExStatusString), lpJobQueue->JobEntry->ExStatusString); if (FAILED(hr)) { // // Can never happen, we use large enough buffer. // ASSERT_FALSE; } if (!UpdatePersistentJobStatus(lpJobQueue)) { DebugPrintEx( DEBUG_ERR, TEXT("Failed to update persistent job status to 0x%08x"), lpJobQueue->JobStatus); Assert(FALSE); } lpJobQueue->lpParentJob->dwCompletedRecipientJobsCount+=1; // // Create Fax EventEx // dwRes = CreateQueueEvent ( FAX_JOB_EVENT_TYPE_STATUS, lpJobQueue ); if (ERROR_SUCCESS != dwRes) { DebugPrintEx( DEBUG_ERR, TEXT("CreateQueueEvent(FAX_JOB_EVENT_TYPE_STATUS) failed for job id %ld (ec: %lc)"), lpJobQueue->UniqueId, dwRes); } // // We will send the receipt once we are out of all critical sections because this call can be blocking. // just increase the preview refernce count so the job will not be deleted. // IncreaseJobRefCount (lpJobQueue, TRUE); // TRUE - preview // // Copy receipt information from JobEntry. // lpJobQueue->StartTime = lpJobQueue->JobEntry->StartTime; lpJobQueue->EndTime = lpJobQueue->JobEntry->EndTime; // // EndJob() must be called BEFORE we remove the parent job (and recipients) // lpJobQueue->JobEntry->LineInfo->State = FPS_AVAILABLE; // // We just completed a send job on the device - update counter. // (VOID) UpdateDeviceJobsCounter (lpJobQueue->JobEntry->LineInfo, // Device to update TRUE, // Sending -1, // Number of new jobs (-1 = decrease by one) TRUE); // Enable events if (!EndJob( lpJobQueue->JobEntry )) { DebugPrintEx( DEBUG_ERR, TEXT("EndJob Failed. (ec: %ld)"), GetLastError()); } lpJobQueue->JobEntry = NULL; DecreaseJobRefCount (lpJobQueue, TRUE); // This will mark it as JS_DELETING if needed // // Notify the queue that a device is now available. // if (!SetEvent( g_hJobQueueEvent )) { DebugPrintEx( DEBUG_ERR, TEXT("Failed to set g_hJobQueueEvent. (ec: %ld)"), GetLastError()); g_ScanQueueAfterTimeout = TRUE; } LeaveCriticalSection ( &g_CsQueue ); LeaveCriticalSection ( &g_CsJob ); // // Now send the receipt // if (!SendJobReceipt (TRUE, lpJobQueue, lpJobQueue->FileName)) { ec = GetLastError (); DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] SendJobReceipt failed. (ec: %ld)"), lpJobQueue->JobId, ec ); } EnterCriticalSection (&g_CsQueue); DecreaseJobRefCount (lpJobQueue, TRUE, TRUE, TRUE); // last TRUE for Preview ref count. LeaveCriticalSection (&g_CsQueue); return TRUE; } // HandleCompletedSendJob //********************************************************************************* //* Name: HandleFailedSendJob() //* Author: Ronen Barenboim //* Date: June 01, 1999 //********************************************************************************* //* DESCRIPTION: //* Handles the post failure operations of a send job. //* //* IMPORTANT- This call can be blocking. Calling thread MUST NOT hold any critical section //* //* PARAMETERS: //* [IN ] PJOB_ENTRY lpJobEntry //* The job that failed. It must be in FSPI_JS_ABORTED or FSPI_JS_FAILED //* state. //* RETURN VALUE: //* TRUE //* If the operation completed successfully. //* FALSE //* If the operation failed. Call GetLastError() for extended errror //* information. //********************************************************************************* BOOL HandleFailedSendJob(PJOB_ENTRY lpJobEntry) { PJOB_QUEUE lpJobQueue; BOOL bRetrying = FALSE; DEBUG_FUNCTION_NAME(TEXT("HandleFailedSendJob")); DWORD dwRes; TCHAR tszJobTiffFile[MAX_PATH] = {0}; // Deleted after receipt is sent BOOL fAddRetryDelay = TRUE; EnterCriticalSection ( &g_CsJob ); EnterCriticalSection ( &g_CsQueue ); Assert(lpJobEntry); lpJobQueue = lpJobEntry->lpJobQueueEntry; Assert(lpJobQueue); DebugPrintEx( DEBUG_MSG, TEXT("Failed Job: %ld"), lpJobQueue->JobId); Assert( FSPI_JS_ABORTED == lpJobEntry->FSPIJobStatus.dwJobStatus || FSPI_JS_FAILED == lpJobEntry->FSPIJobStatus.dwJobStatus || FSPI_JS_FAILED_NO_RETRY == lpJobEntry->FSPIJobStatus.dwJobStatus || FSPI_JS_DELETED == lpJobEntry->FSPIJobStatus.dwJobStatus); // // Do not cache rendered tiff files // if (lpJobQueue->FileName) { // // We simply store the file name to delete and delete it later // since we might need it for receipt attachment. // _tcsncpy (tszJobTiffFile, lpJobQueue->FileName, (sizeof (tszJobTiffFile) / sizeof (tszJobTiffFile[0]))-1); MemFree (lpJobQueue->FileName); lpJobQueue->FileName = NULL; } // // Update end time in JOB_ENTRY // GetSystemTimeAsFileTime( (FILETIME*) &lpJobEntry->EndTime ); // // Update elapsed time in JOB_ENTRY // Assert (lpJobEntry->EndTime >= lpJobEntry->StartTime); lpJobEntry->ElapsedTime = lpJobEntry->EndTime - lpJobEntry->StartTime; if ( FSPI_JS_ABORTED == lpJobEntry->FSPIJobStatus.dwJobStatus) { // // The FSP reported the job was aborted. // DebugPrintEx( DEBUG_MSG, TEXT("[Job Id: %ld] EFSP reported that job was aborted."), lpJobQueue->JobId); // // Check if the job was aborted by the service (shutting down) or by the user // if (FALSE == lpJobEntry->fSystemAbort) { // // The event log about a canceled job will be reported at the end of this if..else block. // lpJobEntry->Aborting = 1; bRetrying = FALSE; // Do not retry on cancel } else { // // SystemAbort // Don't increase the retry count since this is not really a failure. // bRetrying = TRUE; fAddRetryDelay = FALSE; } } else if ( FSPI_JS_FAILED == lpJobEntry->FSPIJobStatus.dwJobStatus) { switch (lpJobEntry->FSPIJobStatus.dwExtendedStatus) { case FSPI_ES_LINE_UNAVAILABLE: // // this is the glare condition. Someone snatched the line before the FSP // had a chance to grab it. // We will try again but will not increase the retry count. // EnterCriticalSection (&g_CsLine); // // Check if the line was busy or closed // if (!(lpJobEntry->LineInfo->Flags & FPF_VIRTUAL)) { // // Tapi line // if (NULL == lpJobEntry->LineInfo->hLine) { // // Tapi worker thread got LINE_CLOSE // fAddRetryDelay = FALSE; } } LeaveCriticalSection (&g_CsLine); bRetrying = TRUE; if (g_pFaxPerfCounters) { // // Increase the 'Outbound failed connections' counter. // InterlockedIncrement( (PLONG)&g_pFaxPerfCounters->OutboundFailedConnections ); } // // Don't increase the retry count since this is not really a failure. // break; case FSPI_ES_NO_ANSWER: case FSPI_ES_NO_DIAL_TONE: case FSPI_ES_DISCONNECTED: case FSPI_ES_BUSY: case FSPI_ES_NOT_FAX_CALL: case FSPI_ES_CALL_DELAYED: // // For these error codes we need to retry // bRetrying = CheckForJobRetry(lpJobQueue); if (g_pFaxPerfCounters) { // // Increase the 'Outbound failed connections' counter. // InterlockedIncrement( (PLONG)&g_pFaxPerfCounters->OutboundFailedConnections ); } break; case FSPI_ES_FATAL_ERROR: // // For these error codes we need to retry // bRetrying = CheckForJobRetry(lpJobQueue); if (g_pFaxPerfCounters) { // // Increase the 'Outbound failed transmissions' counter. // InterlockedIncrement( (PLONG)&g_pFaxPerfCounters->OutboundFailedXmit ); } break; case FSPI_ES_BAD_ADDRESS: case FSPI_ES_CALL_BLACKLISTED: // // No retry for these error codes // bRetrying = FALSE; if (g_pFaxPerfCounters) { // // Increase the 'Outbound failed connections' counter. // InterlockedIncrement( (PLONG)&g_pFaxPerfCounters->OutboundFailedConnections ); } break; default: // // Our default for extension codes // is to retry. // bRetrying = CheckForJobRetry(lpJobQueue); if (g_pFaxPerfCounters) { // // Increase the 'Outbound failed transmissions' counter. // InterlockedIncrement( (PLONG)&g_pFaxPerfCounters->OutboundFailedXmit ); } break; } } else if ( FSPI_JS_FAILED_NO_RETRY == lpJobEntry->FSPIJobStatus.dwJobStatus ) { // // The FSP indicated that there is no point in retrying this job. // bRetrying = FALSE; } else if ( FSPI_JS_DELETED == lpJobEntry->FSPIJobStatus.dwJobStatus ) { // // This is the case where the job can not be reestablished // we treat it as a failure with no retry. bRetrying = FALSE; } if (lpJobEntry->Aborting ) { // // An abort operation is in progress for this job. // No point in retrying. // Just mark the job as canceled and see if we can remove the parent job yet. // DebugPrintEx( DEBUG_MSG, TEXT("[JobId: %ld] lpJobEntry->Aborting is ON.")); lpJobQueue->JobStatus = JS_CANCELED; if (!UpdatePersistentJobStatus(lpJobQueue)) { DebugPrintEx( DEBUG_ERR, TEXT("Failed to update persistent job status to 0x%08x"), lpJobQueue->JobStatus); Assert(FALSE); } lpJobQueue->lpParentJob->dwCanceledRecipientJobsCount+=1; bRetrying = FALSE; } // // Save the last extended status before ending this job // lpJobQueue->dwLastJobExtendedStatus = lpJobEntry->FSPIJobStatus.dwExtendedStatus; HRESULT hr = StringCchCopy( lpJobQueue->ExStatusString, ARR_SIZE(lpJobQueue->ExStatusString), lpJobQueue->JobEntry->ExStatusString); if (FAILED(hr)) { // // Can never happen, we use large enough buffer. // ASSERT_FALSE; } if (!bRetrying && !lpJobEntry->Aborting) { // // If we do not handle an abort request (in this case we do not want // to count it as a failure since it will be counted as Canceled) and we decided // not to retry then we need to mark the job as expired. // if (0 == lpJobQueue->dwLastJobExtendedStatus) { // // Job was never really executed - this is a fatal error // lpJobQueue->dwLastJobExtendedStatus = FSPI_ES_FATAL_ERROR; lpJobQueue->ExStatusString[0] = L'\0'; } if (!MarkJobAsExpired(lpJobQueue)) { DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] MarkJobAsExpired failed (ec: %ld)"), lpJobQueue->JobId, GetLastError()); } } if (!bRetrying) { // // Job reached final failure state - send negative receipt // We will send the receipt once we are out of all critical sections because this call can be blocking. // just increase the preview refernce count so the job will not be deleted. // IncreaseJobRefCount (lpJobQueue, TRUE); // TRUE - preview // // Copy receipt information from JobEntry. // lpJobQueue->StartTime = lpJobQueue->JobEntry->StartTime; lpJobQueue->EndTime = lpJobQueue->JobEntry->EndTime; } else { // // Job marked for retry. Do not delete it. Reschedule it. // DebugPrintEx( DEBUG_MSG, TEXT("[JobId: %ld] Set for retry (JS_RETRYING). Retry Count = %ld)"), lpJobQueue->JobId, lpJobQueue->SendRetries); lpJobQueue->JobStatus = JS_RETRYING; // // Job entry must be NULLified before leaving the CS. // This is done below because we still need the Job entry for logging // if (TRUE == fAddRetryDelay) { // // Send failure - Reschedule // RescheduleJobQueueEntry( lpJobQueue ); } else { // // FaxDevShutDown() was called, or We lost the line, Do not add retry delay // if (!CommitQueueEntry(lpJobQueue)) { DebugPrintEx( DEBUG_ERR, TEXT("CommitQueueEntry() for recipien job %s has failed. (ec: %ld)"), lpJobQueue->FileName, GetLastError()); } } } FaxLogSend( lpJobQueue, bRetrying); if (!bRetrying) { EnterCriticalSection (&g_CsOutboundActivityLogging); if (INVALID_HANDLE_VALUE == g_hOutboxActivityLogFile) { DebugPrintEx(DEBUG_ERR, TEXT("Logging not initialized")); } else { if (!LogOutboundActivity(lpJobQueue)) { DebugPrintEx(DEBUG_ERR, TEXT("Logging outbound activity failed")); } } LeaveCriticalSection (&g_CsOutboundActivityLogging); } // // Notify clients on status change // dwRes = CreateQueueEvent ( FAX_JOB_EVENT_TYPE_STATUS, lpJobQueue); if (ERROR_SUCCESS != dwRes) { DebugPrintEx( DEBUG_ERR, TEXT("CreateQueueEvent(FAX_JOB_EVENT_TYPE_STATUS) failed for job id %ld (ec: %lc)"), lpJobQueue->UniqueId, dwRes); } // // EndJob() must be called BEFORE we remove the parent job (and recipients) // lpJobEntry->LineInfo->State = FPS_AVAILABLE; // // We just completed a send job on the device - update counter. // (VOID) UpdateDeviceJobsCounter ( lpJobEntry->LineInfo, // Device to update TRUE, // Sending -1, // Number of new jobs (-1 = decrease by one) TRUE); // Enable events if (!EndJob( lpJobEntry )) { DebugPrintEx( DEBUG_ERR, TEXT("EndJob Failed. (ec: %ld)"), GetLastError()); } lpJobQueue->JobEntry = NULL; if (JS_CANCELED == lpJobQueue->JobStatus) { DWORD dwJobId; dwJobId = lpJobQueue->JobId; // Job was canceled - decrease reference count DecreaseJobRefCount (lpJobQueue, TRUE); // This will mark it as JS_DELETING if needed // // We need to send the legacy W2K FEI_DELETING notification. // if (!CreateFaxEvent(0, FEI_DELETED, dwJobId)) { DebugPrintEx( DEBUG_ERR, TEXT("CreateFaxEvent() failed. Event: 0x%08X JobId: %ld DeviceId: (ec: %ld)"), FEI_DELETED, lpJobQueue->JobId, 0, GetLastError()); } } // // Notify the queue that a device is now available. // if (!SetEvent( g_hJobQueueEvent )) { DebugPrintEx( DEBUG_ERR, TEXT("Failed to set g_hJobQueueEvent. (ec: %ld)"), GetLastError()); g_ScanQueueAfterTimeout = TRUE; } LeaveCriticalSection ( &g_CsQueue ); LeaveCriticalSection ( &g_CsJob ); // // Now, send the receipt // if (!bRetrying) { // // Job reached final failure state - send negative receipt // if (!SendJobReceipt (FALSE, lpJobQueue, tszJobTiffFile)) { DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] SendJobReceipt failed. (ec: %ld)"), lpJobQueue->JobId, GetLastError ()); } EnterCriticalSection (&g_CsQueue); DecreaseJobRefCount (lpJobQueue, TRUE, TRUE, TRUE); // last TRUE for Preview ref count. LeaveCriticalSection (&g_CsQueue); } if (lstrlen (tszJobTiffFile)) { // // Now we can safely delete the job's TIFF file // DebugPrintEx(DEBUG_MSG, TEXT("Deleting per recipient body file %s"), tszJobTiffFile); if (!DeleteFile( tszJobTiffFile )) { DebugPrintEx(DEBUG_MSG, TEXT("Failed to delete per recipient body file %s (ec: %ld)"), tszJobTiffFile, GetLastError()); } } return TRUE; } // HandleFailedSendJob //********************************************************************************* //* Name: StartReceiveJob() //* Author: Ronen Barenboim //* Date: June 02, 1999 //********************************************************************************* //* DESCRIPTION: //* Starts a receive job on the specified device. //* PARAMETERS: //* [IN ] DWORD DeviceId //* The permanent line id (not TAPI) of the device on which the fax is //* to be received. //* //* RETURN VALUE: //* //********************************************************************************* PJOB_ENTRY StartReceiveJob( DWORD DeviceId ) { BOOL Failure = TRUE; PJOB_ENTRY JobEntry = NULL; PLINE_INFO LineInfo; BOOL bRes = FALSE; DWORD rc = ERROR_SUCCESS; DEBUG_FUNCTION_NAME(TEXT("StartRecieveJob")); LineInfo = GetTapiLineForFaxOperation( DeviceId, JT_RECEIVE, NULL ); if (!LineInfo) { // // Could not find a line to send the fax on. // rc = GetLastError(); DebugPrintEx( DEBUG_WRN, TEXT("Failed to find a line to send the fax on. (ec: %ld)"), rc); goto exit; } JobEntry = CreateJobEntry(NULL, LineInfo, FALSE); if (!JobEntry) { rc = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("Failed to create JobEntry. (ec: %ld)"), rc); goto exit; } __try { // // Call the FSP associated with the line to start a fax job. Note that at this // point it is not known if the job is send or receive. // bRes = LineInfo->Provider->FaxDevStartJob( LineInfo->hLine, LineInfo->DeviceId, (PHANDLE) &JobEntry->InstanceData, // JOB_ENTRY.InstanceData is where the FSP will place its // job handle (fax handle). g_StatusCompletionPortHandle, (ULONG_PTR) LineInfo ); // Note that the completion key provided to the FSP is the LineInfo // pointer. When the FSP reports status it uses this key thus allowing // us to know to which line the status belongs. } __except (HandleFaxExtensionFault(EXCEPTION_SOURCE_FSP, LineInfo->Provider->FriendlyName, GetExceptionCode())) { ASSERT_FALSE; } if (!bRes) { rc = GetLastError(); DebugPrintEx(DEBUG_ERR,TEXT("FaxDevStartJob failed (ec: %ld)"),GetLastError()); goto exit; } // // Add the new JOB_ENTRY to the job list. // EnterCriticalSection( &g_CsJob ); JobEntry->bFSPJobInProgress = TRUE; InsertTailList( &g_JobListHead, &JobEntry->ListEntry ); LeaveCriticalSection( &g_CsJob ); Failure = FALSE; // // Attach the job to the line selected to service it. // LineInfo->JobEntry = JobEntry; exit: if (Failure) { // Failure is initialized to TRUE if (LineInfo) { ReleaseTapiLine( LineInfo, 0 ); } if (JobEntry) { EndJob(JobEntry); } JobEntry = NULL; } if (ERROR_SUCCESS != rc) { SetLastError(rc); FaxLog(FAXLOG_CATEGORY_INBOUND, FAXLOG_LEVEL_MIN, 0, MSG_FAX_RECEIVE_FAILED); } return JobEntry; } //********************************************************************************* //* Name: StartRoutingJob() //* Author: Mooly Beery (MoolyB) //* Date: July 20, 2000 //********************************************************************************* //* DESCRIPTION: //* Starts a routing operation. Must lock g_CsJob and g_CsQueue. //* PARAMETERS: //* [IN/OUT ] PJOB_QUEUE lpJobQueueEntry //* A pointer to the job for which the routing operation is to be //* performed. //* //* RETURN VALUE: //* TRUE //* If the operation succeeded. //* FALSE //* If the operation failed. Call GetLastError() to get extended error //* information. //* //********************************************************************************* BOOL StartRoutingJob( PJOB_QUEUE lpJobQueueEntry ) { DWORD ec = ERROR_SUCCESS; HANDLE hThread = NULL; DWORD ThreadId; DEBUG_FUNCTION_NAME(TEXT("StartRoutingJob")); // // We mark the job as IN_PROGRESS so it can not be deleted or routed simultaneously // lpJobQueueEntry->JobStatus = JS_INPROGRESS; hThread = CreateThreadAndRefCount( NULL, 0, (LPTHREAD_START_ROUTINE) FaxRouteThread, (LPVOID) lpJobQueueEntry, 0, &ThreadId ); if (hThread == NULL) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, _T("CreateThreadAndRefCount for FaxRouteThread failed (ec: 0x%0X)"), ec); if (!MarkJobAsExpired(lpJobQueueEntry)) { DEBUG_ERR, TEXT("[JobId: %ld] MarkJobAsExpired failed (ec: %ld)"), lpJobQueueEntry->JobId, GetLastError(); } SetLastError(ec); return FALSE; } DebugPrintEx( DEBUG_MSG, _T("FaxRouteThread thread created for job id %d ") _T("(thread id: 0x%0x)"), lpJobQueueEntry->JobId, ThreadId); CloseHandle( hThread ); // // Create Fax EventEx // DWORD dwRes = CreateQueueEvent ( FAX_JOB_EVENT_TYPE_STATUS, lpJobQueueEntry); if (ERROR_SUCCESS != dwRes) { DebugPrintEx( DEBUG_ERR, _T("CreateQueueEvent(FAX_JOB_EVENT_TYPE_STATUS) ") _T("failed for job id %ld (ec: %ld)"), lpJobQueueEntry->JobId, dwRes); } return TRUE; } //********************************************************************************* //* Name: StartSendJob() //* Author: Ronen Barenboim //* Date: June 02, 1999 //********************************************************************************* //* DESCRIPTION: //* Starts a send operation on a legacy of Extened FSP device. //* PARAMETERS: //* [IN/OUT ] PJOB_QUEUE lpJobQueueEntry //* A pointer to the recipient job for which the send operation is to be //* performed. For extended sends this is the Anchor recipient. //* //* [IN/OUT] PLINE_INFO lpLineInfo //* A pointer to the line on which the send operatin is to be performed. //* //* RETURN VALUE: //* TRUE //* If the operation succeeded. //* FALSE //* If the operation failed. Call GetLastError() to get extended error //* information. //* //********************************************************************************* BOOL StartSendJob( PJOB_QUEUE lpJobQueueEntry, PLINE_INFO lpLineInfo ) { DWORD rc = ERROR_SUCCESS; DEBUG_FUNCTION_NAME(TEXT("StartSendJob")); Assert(lpJobQueueEntry); Assert(JT_SEND == lpJobQueueEntry->JobType); Assert(lpLineInfo); if (FSPI_API_VERSION_1 == lpLineInfo->Provider->dwAPIVersion) { if (!StartLegacySendJob(lpJobQueueEntry,lpLineInfo)) { rc = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("StartLegacySendJob() failed for JobId: %ld (ec: %ld)"), lpJobQueueEntry->JobId, GetLastError()); goto exit; } } else { DebugPrintEx( DEBUG_ERR, TEXT("Unsupported FSPI version (0x%08X) for line : %s "), lpLineInfo->Provider->dwAPIVersion, lpLineInfo->DeviceName); Assert(FALSE); goto exit; } exit: if (ERROR_SUCCESS != rc) { SetLastError(rc); TCHAR strJobID[20]={0}; // // Convert Job ID into a string. (the string is 18 TCHARs long !!!) // HRESULT hr = StringCchPrintf( strJobID, ARR_SIZE(strJobID), TEXT("0x%016I64x"), lpJobQueueEntry->UniqueId); if (FAILED(hr)) { // // Should never happen, we use large enough buffer. // ASSERT_FALSE; } FaxLog( FAXLOG_CATEGORY_OUTBOUND, FAXLOG_LEVEL_MIN, 7, MSG_FAX_SEND_FAILED, lpJobQueueEntry->SenderProfile.lptstrName, lpJobQueueEntry->SenderProfile.lptstrBillingCode, lpJobQueueEntry->SenderProfile.lptstrCompany, lpJobQueueEntry->SenderProfile.lptstrDepartment, lpLineInfo->DeviceName, strJobID, lpJobQueueEntry->lpParentJob->UserName ); } return (0 == rc); } //********************************************************************************* //* Name: StartLegacySendJob() //* Author: Ronen Barenboim //* Date: June 02, 1999 //********************************************************************************* //* DESCRIPTION: //* Starts the operation of sending a fax on a legacy FSP device. //* - creates the JOB_ENTRY //* - calls FaxDevStartJob() //* - calls SendDocument() to actually send the document //* - calls EndJob() if anything goes wrong. //* //* PARAMETERS: //* [XXX] PJOB_QUEUE lpJobQueue //* A pointer to the recipient job for the send operation is to be started. //* [XXX] PLINE_INFO lpLineInfo //* A pointer to the LINE_INFO of the line on which the fax is to be sent. //* //* //* RETURN VALUE: //* TRUE if the operation succeeded. //* FALSE if it failed. Call GetLastError() to get extended error information. //* //********************************************************************************* PJOB_ENTRY StartLegacySendJob( PJOB_QUEUE lpJobQueue, PLINE_INFO lpLineInfo ) { PJOB_ENTRY lpJobEntry = NULL; DWORD rc = 0; DWORD dwRes; DEBUG_FUNCTION_NAME(TEXT("StartLegacySendJob")); Assert(JT_SEND == lpJobQueue->JobType); Assert(FSPI_API_VERSION_1 == lpLineInfo->Provider->dwAPIVersion); lpJobEntry = CreateJobEntry(lpJobQueue, lpLineInfo, TRUE); if (!lpJobEntry) { rc = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("Failed to create JobEntry for JobId: %ld. (ec: %ld)"), lpJobQueue->JobId, rc); goto Error; } lpJobQueue->JobStatus = JS_INPROGRESS; // // Add the new JOB_ENTRY to the job list. // EnterCriticalSection( &g_CsJob ); InsertTailList( &g_JobListHead, &lpJobEntry->ListEntry ); LeaveCriticalSection( &g_CsJob ); // // Attach the job to the line selected to service it. // lpLineInfo->JobEntry = lpJobEntry; lpJobQueue->JobEntry = lpJobEntry; __try { // // Call the FSP associated with the line to start a fax job. Note that at this // point it is not known if the job is send or receive. // if (lpLineInfo->Provider->FaxDevStartJob( lpLineInfo->hLine, lpLineInfo->DeviceId, (PHANDLE) &lpJobEntry->InstanceData, // JOB_ENTRY.InstanceData is where the FSP will place its // job handle (fax handle). g_StatusCompletionPortHandle, (ULONG_PTR) lpLineInfo )) // Note that the completion key provided to the FSP is the LineInfo // pointer. When the FSP reports status it uses this key thus allowing // us to know to which line the status belongs. { DebugPrintEx( DEBUG_MSG, TEXT("FaxDevStartJob() Successfuly called for JobId: %ld)"), lpJobQueue->JobId); lpJobEntry->bFSPJobInProgress = TRUE; } else { rc = GetLastError(); DebugPrintEx(DEBUG_ERR,TEXT("FaxDevStartJob() failed (ec: %ld)"),rc); if (0 == rc) { // // FSP failed to report last error so we set our own. // DebugPrintEx(DEBUG_ERR,TEXT("FaxDevStartJob() failed but reported 0 for last error")); rc = ERROR_GEN_FAILURE; } goto Error; } } __except (HandleFaxExtensionFault(EXCEPTION_SOURCE_FSP, lpLineInfo->Provider->FriendlyName, GetExceptionCode())) { ASSERT_FALSE; } // // start the send job // rc = SendDocument( lpJobEntry, lpJobQueue->FileName ); if (rc) { DebugPrintEx( DEBUG_ERR, TEXT("SendDocument failed for JobId: %ld (ec: %ld)"), lpJobQueue->JobId, rc); goto Error; } Assert (0 == rc); goto Exit; Error: Assert( 0 != rc); if (lpJobEntry) { if (!EndJob(lpJobEntry)) { DebugPrintEx( DEBUG_ERR, TEXT("EndJob() failed for JobId: %ld (ec: %ld)"), lpJobQueue->JobId, GetLastError()); } lpJobEntry = NULL; lpJobQueue->JobEntry = NULL; } else { // // Release the line // if (!ReleaseTapiLine(lpLineInfo, NULL)) { DebugPrintEx( DEBUG_ERR, TEXT("ReleaseTapiLine() failed (ec: %ld)"), GetLastError()); } } // // set the job into the retries exceeded state // if (0 == lpJobQueue->dwLastJobExtendedStatus) { // // Job was never really executed - this is a fatal error // lpJobQueue->dwLastJobExtendedStatus = FSPI_ES_FATAL_ERROR; lpJobQueue->ExStatusString[0] = L'\0'; } if (!MarkJobAsExpired(lpJobQueue)) { DebugPrintEx( DEBUG_ERR, TEXT("[JobId: %ld] MarkJobAsExpired failed (ec: %ld)"), lpJobQueue->JobId, GetLastError()); } // // Notify clients on status change // dwRes = CreateQueueEvent ( FAX_JOB_EVENT_TYPE_STATUS, lpJobQueue); if (ERROR_SUCCESS != dwRes) { DebugPrintEx( DEBUG_ERR, _T("CreateQueueEvent(FAX_JOB_EVENT_TYPE_STATUS) failed for job id %ld (ec: %lc)"), lpJobQueue->UniqueId, dwRes); } Exit: if (rc) { SetLastError(rc); } return lpJobEntry; } //********************************************************************************* //* Name: UpdateJobStatus() //* Author: Ronen Barenboim //* Date: June 01, 1999 //********************************************************************************* //* DESCRIPTION: //* Updated the FSPI job status kept in the job entry. //* Generates legacy API event and new events as required. //* PARAMETERS: //* [OUT] PJOB_ENTRY lpJobEntry //* The job entry whose FSPI status is to be udpated. //* //* [IN] LPCFSPI_JOB_STATUS lpcFSPJobStatus //* The new FSPI job status. //* //* RETURN VALUE: //* TRUE if the operation succeeded. //* FALSE if the operation failed. Call GetLastError() to get extended error //* information. //* Remarks: //* The function fress the last FSPI job status held in the job entry //* (if any). //********************************************************************************* BOOL UpdateJobStatus( PJOB_ENTRY lpJobEntry, LPCFSPI_JOB_STATUS lpcFSPJobStatus ) { DWORD ec = 0; DWORD dwEventId; DWORD Size = 0; HINSTANCE hLoadInstance = NULL; DEBUG_FUNCTION_NAME(TEXT("UpdateJobStatus")); Assert(lpJobEntry); Assert(lpcFSPJobStatus); Assert (lpJobEntry->lpJobQueueEntry); EnterCriticalSection( &g_CsJob ); DebugPrintEx( DEBUG_MSG, TEXT("dwJobStatus: 0x%08X dwExtendedStatus: 0x%08X"), lpcFSPJobStatus->dwJobStatus, lpcFSPJobStatus->dwExtendedStatus ); if (TRUE == lpJobEntry->fStopUpdateStatus) { DebugPrintEx( DEBUG_WRN, TEXT("JobId: %ld. fStopUpdateStatus was set. Not updating status"), lpJobEntry->lpJobQueueEntry->JobId, lpJobEntry->lpJobQueueEntry->JobStatus); LeaveCriticalSection (&g_CsJob); return TRUE; } // // Map the FSPI job status to an FEI_* event (0 if not event matches the status) // dwEventId = MapFSPIJobStatusToEventId(lpcFSPJobStatus); // // Note: W2K Fax did issue notifications with EventId == 0 whenever an // FSP reported proprietry status code. To keep backward compatability // we keep up this behaviour although it might be regarded as a bug // if (!CreateFaxEvent( lpJobEntry->LineInfo->PermanentLineID, dwEventId, lpJobEntry->lpJobQueueEntry->JobId )) { if ( TRUE == g_bServiceIsDown) { DebugPrintEx( DEBUG_WRN, TEXT("CreateFaxEvent() failed. Event: 0x%08X JobId: %ld DeviceId: (Service is going down)"), dwEventId, lpJobEntry->lpJobQueueEntry->JobId, lpJobEntry->LineInfo->PermanentLineID ); } else { DebugPrintEx( DEBUG_ERR, TEXT("CreateFaxEvent() failed. Event: 0x%08X JobId: %ld DeviceId: (ec: %ld)"), dwEventId, lpJobEntry->lpJobQueueEntry->JobId, lpJobEntry->LineInfo->PermanentLineID, GetLastError()); Assert(FALSE); } } lpJobEntry->FSPIJobStatus.dwJobStatus = lpcFSPJobStatus->dwJobStatus; lpJobEntry->FSPIJobStatus.dwExtendedStatus = lpcFSPJobStatus->dwExtendedStatus; lpJobEntry->FSPIJobStatus.dwExtendedStatusStringId = lpcFSPJobStatus->dwExtendedStatusStringId; if (lpcFSPJobStatus->fAvailableStatusInfo & FSPI_JOB_STATUS_INFO_PAGECOUNT) { lpJobEntry->FSPIJobStatus.dwPageCount = lpcFSPJobStatus->dwPageCount; lpJobEntry->FSPIJobStatus.fAvailableStatusInfo |= FSPI_JOB_STATUS_INFO_PAGECOUNT; } if (lpcFSPJobStatus->fAvailableStatusInfo & FSPI_JOB_STATUS_INFO_TRANSMISSION_START) { lpJobEntry->FSPIJobStatus.tmTransmissionStart = lpcFSPJobStatus->tmTransmissionStart; lpJobEntry->FSPIJobStatus.fAvailableStatusInfo |= FSPI_JOB_STATUS_INFO_TRANSMISSION_START; } if (lpcFSPJobStatus->fAvailableStatusInfo & FSPI_JOB_STATUS_INFO_TRANSMISSION_END) { lpJobEntry->FSPIJobStatus.tmTransmissionEnd = lpcFSPJobStatus->tmTransmissionEnd; lpJobEntry->FSPIJobStatus.fAvailableStatusInfo |= FSPI_JOB_STATUS_INFO_TRANSMISSION_END; } if (NULL != lpcFSPJobStatus->lpwstrRemoteStationId) { if (!ReplaceStringWithCopy(&lpJobEntry->FSPIJobStatus.lpwstrRemoteStationId, lpcFSPJobStatus->lpwstrRemoteStationId)) { DebugPrintEx( DEBUG_ERR, TEXT("ReplaceStringWithCopy() failed. (ec: %ld)"), GetLastError()); } } if (NULL != lpcFSPJobStatus->lpwstrCallerId) { if (!ReplaceStringWithCopy(&lpJobEntry->FSPIJobStatus.lpwstrCallerId, lpcFSPJobStatus->lpwstrCallerId)) { DebugPrintEx( DEBUG_ERR, TEXT("ReplaceStringWithCopy() failed. (ec: %ld)"), GetLastError()); } } if (NULL != lpcFSPJobStatus->lpwstrRoutingInfo) { if (!ReplaceStringWithCopy(&lpJobEntry->FSPIJobStatus.lpwstrRoutingInfo, lpcFSPJobStatus->lpwstrRoutingInfo)) { DebugPrintEx( DEBUG_ERR, TEXT("ReplaceStringWithCopy() failed. (ec: %ld)"), GetLastError()); } } lpJobEntry->ExStatusString[0] = L'\0'; // // Get extended status string // Assert (lpJobEntry->LineInfo != NULL) if (lpJobEntry->FSPIJobStatus.dwExtendedStatusStringId != 0) { Assert (lpJobEntry->FSPIJobStatus.dwExtendedStatus != 0); if ( !_tcsicmp(lpJobEntry->LineInfo->Provider->szGUID,REGVAL_T30_PROVIDER_GUID_STRING) ) { // special case where the FSP is our FSP (fxst30.dll). hLoadInstance = g_hResource; } else { hLoadInstance = lpJobEntry->LineInfo->Provider->hModule; } Size = LoadString (hLoadInstance, lpJobEntry->FSPIJobStatus.dwExtendedStatusStringId, lpJobEntry->ExStatusString, sizeof(lpJobEntry->ExStatusString)/sizeof(WCHAR)); if (Size == 0) { ec = GetLastError(); DebugPrintEx( DEBUG_ERR, TEXT("Failed to load extended status string (ec: %ld) stringid : %ld, Provider: %s"), ec, lpJobEntry->FSPIJobStatus.dwExtendedStatusStringId, lpJobEntry->LineInfo->Provider->ImageName); lpJobEntry->FSPIJobStatus.fAvailableStatusInfo &= ~FSPI_JOB_STATUS_INFO_FSP_PRIVATE_STATUS_CODE; lpJobEntry->FSPIJobStatus.dwExtendedStatusStringId = 0; lpJobEntry->FSPIJobStatus.dwExtendedStatus = 0; goto Error; } } EnterCriticalSection (&g_CsQueue); DWORD dwRes = CreateQueueEvent ( FAX_JOB_EVENT_TYPE_STATUS, lpJobEntry->lpJobQueueEntry); if (ERROR_SUCCESS != dwRes) { DebugPrintEx( DEBUG_ERR, TEXT("CreateQueueEvent(FAX_JOB_EVENT_TYPE_STATUS) failed for job id %ld (ec: %lc)"), lpJobEntry->lpJobQueueEntry->UniqueId, dwRes); } LeaveCriticalSection (&g_CsQueue); Assert (0 == ec); goto Exit; Error: Assert( ec !=0 ); Exit: LeaveCriticalSection( &g_CsJob ); if (ec) { SetLastError(ec); } return (0 == ec); } //********************************************************************************* //* Name: CheckForJobRetry //* Author: Ronen Barenboim //* Date: June 01, 1999 //********************************************************************************* //* DESCRIPTION: //* Checks if a recipient job should be retried. //* Increments the retry count and marks the job as expired if it passed //* the retry limit. //* PARAMETERS: //* [IN/OUT] PJOB_QUEUE lpJobQueue //* A pointer to the JOB_QUEUE structure of the recipient job. //* RETURN VALUE: //* TRUE if the job is to be retried. //* FALSE if it is not to be retried. //********************************************************************************* BOOL CheckForJobRetry (PJOB_QUEUE lpJobQueue) { PJOB_ENTRY lpJobEntry; DWORD dwMaxRetries; DEBUG_FUNCTION_NAME(TEXT("CheckForJobRetry")); Assert(lpJobQueue); lpJobEntry = lpJobQueue->JobEntry; Assert(lpJobEntry); // // Increase the retry count and check if we exceeded maximum retries. // EnterCriticalSection (&g_CsConfig); dwMaxRetries = g_dwFaxSendRetries; LeaveCriticalSection (&g_CsConfig); lpJobQueue->SendRetries++; if (lpJobQueue->SendRetries <= dwMaxRetries) { return TRUE; } else { // // retries exceeded report that the job is not to be retried return FALSE; } } //********************************************************************************* //* Name: FindJobEntryByRecipientNumber() //* Author: Ronen Barenboim //* Date: June 01, 1999 //********************************************************************************* //* DESCRIPTION: //* Finds the first running job that is destined to a certain number. //* //* PARAMETERS: //* [IN ] LPTSTR lptstrNumber //* The number to match. This must be in canonical form. //* //* RETURN VALUE: //* A pointer to the JOB_ENTRY in the g_JobListHead list that is destined to //* the specified number. //* If no such job is found the return value is NULL. //********************************************************************************* PJOB_ENTRY FindJobEntryByRecipientNumber(LPCWSTR lpcwstrNumber) { PLIST_ENTRY lpNext; PJOB_ENTRY lpJobEntry; DEBUG_FUNCTION_NAME(TEXT("FindJobEntryByRecipientNumber")); Assert(lpcwstrNumber); lpNext = g_JobListHead.Flink; Assert(lpNext); while ((ULONG_PTR)lpNext != (ULONG_PTR)&g_JobListHead) { lpJobEntry = CONTAINING_RECORD( lpNext, JOB_ENTRY, ListEntry ); lpNext = lpJobEntry->ListEntry.Flink; if (JT_SEND == lpJobEntry->lpJobQueueEntry->JobType) { if (!_wcsicmp(lpJobEntry->lpJobQueueEntry->RecipientProfile.lptstrFaxNumber, lpcwstrNumber)) { return lpJobEntry; } } } return NULL; } BOOL CreateJobQueueThread(void) { DWORD ThreadId; DWORD ec = ERROR_SUCCESS; DEBUG_FUNCTION_NAME(TEXT("CreateJobQueueThread")); g_hJobQueueThread = CreateThreadAndRefCount( NULL, 0, (LPTHREAD_START_ROUTINE) JobQueueThread, NULL, 0, &ThreadId ); if (NULL == g_hJobQueueThread) { DebugPrintEx( DEBUG_ERR, TEXT("Failed to create JobQueueThread (ec: %ld)."), GetLastError()); goto Error; } Assert( ERROR_SUCCESS == ec); goto Exit; Error: Assert (ERROR_SUCCESS != ec); // // We don't close the already created threads. (They are terminated on process exit). // Exit: if (ERROR_SUCCESS != ec) { SetLastError(ec); } return (ERROR_SUCCESS == ec); } BOOL CreateStatusThreads(void) { int i; DWORD ThreadId; DWORD ec = ERROR_SUCCESS; HANDLE hStatusThreads[MAX_STATUS_THREADS]; DEBUG_FUNCTION_NAME(TEXT("CreateStatusThreads")); memset(hStatusThreads, 0, sizeof(HANDLE)*MAX_STATUS_THREADS); for (i=0; ilpParentJob->dwRecipientJobsCount > 1) { // // Broadcast case // if (lpJobQueue->JobParamsEx.dwReceiptDeliveryType & DRT_GRP_PARENT) { // // Broadcast receipt grouping is requested // if (IsSendJobReadyForDeleting (lpJobQueue)) { // // This is the last job in the broadcast, it's time to send a broadcast receipt // // // As receipt sending is async, there still might be a chance that more than one recipient jobs will reach this point // We must verify that only one receipt is sent per broadcast job // EnterCriticalSection (&g_CsQueue); if (FALSE == lpJobQueue->lpParentJob->fReceiptSent) { PJOB_QUEUE pParentJob = lpJobQueue->lpParentJob; BOOL bPositiveBroadcast = (pParentJob->dwCompletedRecipientJobsCount == pParentJob->dwRecipientJobsCount) ? TRUE : FALSE; // // set the flag so we will not send duplicate receipts for broadcast // lpJobQueue->lpParentJob->fReceiptSent = TRUE; // // Leave g_CsQueue so we will not block the service // LeaveCriticalSection (&g_CsQueue); if (!SendReceipt(bPositiveBroadcast, TRUE, pParentJob, pParentJob->FileName)) { DebugPrintEx( DEBUG_ERR, TEXT("[Job Id: %ld] Failed to send broadcast receipt. (ec: %ld)"), lpJobQueue->JobId, GetLastError()); return FALSE; } } else { // // More than one job reached this point when the broadcast jo was ready for deleting. // Only on receipt is sent // LeaveCriticalSection (&g_CsQueue); } } else { // // More jobs are still not finished, do not send receipt // } } else { // // This is a recipient part of a broadcast but the user was // asking for a receipt for every recipient. // bSingleJobReceipt = TRUE; } } else { // // This is not a broadcast case // bSingleJobReceipt = TRUE; } if (bSingleJobReceipt) { // // Send receipt for this job only // if (!SendReceipt(bPositive, FALSE, lpJobQueue, lpctstrAttachment)) { DebugPrintEx( DEBUG_ERR, TEXT("[Job Id: %ld] Failed to send POSITIVE receipt. (ec: %ld)"), lpJobQueue->JobId, GetLastError()); return FALSE; } } return TRUE; } // SendJobReceipt VOID UpdateDeviceJobsCounter ( PLINE_INFO pLine, BOOL bSend, int iInc, BOOL bNotify ) /*++ Routine name : UpdateDeviceJobsCounter Routine description: Updates the send or receive jobs counter of a device Author: Eran Yariv (EranY), Jul, 2000 Arguments: pLine [in] - Device pointer bSend [in] - Send counter (FALSE = Receive counter) iInc [in] - Increase jobs count (negative means decrease) decrease [in] - Allow events (FAX_EVENT_TYPE_DEVICE_STATUS) Return Value: None. --*/ { DWORD dwOldCount; DWORD dwNewCount; DEBUG_FUNCTION_NAME(TEXT("UpdateDeviceJobsCounter)")); Assert (pLine); if (!iInc) { // // No change // ASSERT_FALSE; return; } EnterCriticalSection (&g_CsLine); dwOldCount = bSend ? pLine->dwSendingJobsCount : pLine->dwReceivingJobsCount; if (0 > iInc) { // // Decrease case // if ((int)dwOldCount + iInc < 0) { // // Weird - should never happen // ASSERT_FALSE; iInc = -(int)dwOldCount; } } dwNewCount = (DWORD)((int)dwOldCount + iInc); if (bSend) { pLine->dwSendingJobsCount = dwNewCount; } else { pLine->dwReceivingJobsCount = dwNewCount; } LeaveCriticalSection (&g_CsLine); if (bNotify && ((0 == dwNewCount) || (0 == dwOldCount))) { // // State change // DWORD ec = CreateDeviceEvent (pLine, FALSE); if (ERROR_SUCCESS != ec) { DebugPrintEx( DEBUG_ERR, TEXT("CreateDeviceEvent() (ec: %lc)"), ec); } } } // UpdateDeviceJobsCounter