windows-nt/Source/XPSP1/NT/printscan/fax/print/faxmon/faxmon.c
2020-09-26 16:20:57 +08:00

2162 lines
49 KiB
C
Raw Blame History

This file contains invisible Unicode characters

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

/*++
Copyright (c) 1996 Microsoft Corporation
Module Name:
faxmon.c
Abstract:
Implementation of the following print monitor entry points:
InitializePrintMonitor
OpenPort
ClosePort
StartDocPort
EndDocPort
WritePort
ReadPort
Environment:
Windows NT fax print monitor
Revision History:
05/09/96 -davidx-
Remove caching of ports from the monitor.
02/22/96 -davidx-
Created it.
mm/dd/yy -author-
description
--*/
#include "faxmon.h"
#include "tiff.h"
#include "faxreg.h"
#include <splapip.h>
//
// Determine whether we're at the beginning of a TIFF file
//
#define ValidTiffFileHeader(p) \
(((LPSTR) (p))[0] == 'I' && ((LPSTR) (p))[1] == 'I' && \
((PBYTE) (p))[2] == 42 && ((PBYTE) (p))[3] == 0)
//
// Read a DWORD value from an unaligned address
//
#define ReadUnalignedDWord(p) *((DWORD UNALIGNED *) (p))
//
// Write a DWORD value to an unaligned address
//
#define WriteUnalignedDWord(p, value) (*((DWORD UNALIGNED *) (p)) = (value))
//
// Fax monitor name string
//
TCHAR faxMonitorName[CCHDEVICENAME] = TEXT("Windows NT Fax Monitor");
//
// DLL instance handle
//
HANDLE ghInstance = NULL;
//
// Retry parameters when failing to connect to the fax service
// default = infinite retry with 5 seconds interval
//
DWORD connectRetryCount = 0;
DWORD connectRetryInterval = 5;
BOOL
DllEntryPoint(
HANDLE hModule,
ULONG ulReason,
PCONTEXT pContext
)
/*++
Routine Description:
DLL initialization procedure.
Arguments:
hModule - DLL instance handle
ulReason - Reason for the call
pContext - Pointer to context (not used by us)
Return Value:
TRUE if DLL is initialized successfully, FALSE otherwise.
--*/
{
switch (ulReason) {
case DLL_PROCESS_ATTACH:
ghInstance = hModule;
LoadString(ghInstance, IDS_FAX_MONITOR_NAME, faxMonitorName, CCHDEVICENAME);
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
LPMONITOREX
InitializePrintMonitor(
LPTSTR pRegistryRoot
)
/*++
Routine Description:
Initialize the print monitor
Arguments:
pRegistryRoot = Points to a string that specifies the registry root for the monitor
Return Value:
Pointer to a MONITOREX structure which contains function pointers
to other print monitor entry points. NULL if there is an error.
--*/
{
static MONITOREX faxmonFuncs = {
sizeof(MONITOR),
{
FaxMonEnumPorts,
FaxMonOpenPort,
NULL, // OpenPortEx
FaxMonStartDocPort,
FaxMonWritePort,
FaxMonReadPort,
FaxMonEndDocPort,
FaxMonClosePort,
FaxMonAddPort,
FaxMonAddPortEx,
FaxMonConfigurePort,
FaxMonDeletePort,
NULL, // GetPrinterDataFromPort
NULL, // SetPortTimeOuts
}
};
Trace("InitializePrintMonitor");
//
// Get fax service connection retry parameters from the registry
//
if (pRegistryRoot) {
HKEY hRegKey;
LONG status;
status = RegCreateKeyEx(HKEY_LOCAL_MACHINE,
pRegistryRoot,
0,
NULL,
0,
KEY_READ | KEY_WRITE,
NULL,
&hRegKey,
NULL);
if (status == ERROR_SUCCESS) {
connectRetryCount =
GetRegistryDWord(hRegKey, REGVAL_CONNECT_RETRY_COUNT, connectRetryCount);
connectRetryInterval =
GetRegistryDWord(hRegKey, REGVAL_CONNECT_RETRY_INTERVAL, connectRetryInterval);
RegCloseKey(hRegKey);
}
}
return &faxmonFuncs;
}
BOOL
FaxMonOpenPort(
LPTSTR pPortName,
PHANDLE pHandle
)
/*++
Routine Description:
Provides a port for a newly connected printer
Arguments:
pName - Points to a string that specifies the port name
pHandle - Returns a handle to the port
Return Value:
TRUE if successful, FALSE if there is an error
--*/
{
PFAXPORT pFaxPort = NULL;
Trace("OpenPort");
Assert(pHandle != NULL && pPortName != NULL);
//
// Get information about the specified port
//
if ((pFaxPort = MemAllocZ(sizeof(FAXPORT))) &&
(pPortName = DuplicateString(FAX_PORT_NAME)))
{
pFaxPort->startSig = pFaxPort->endSig = pFaxPort;
pFaxPort->pName = pPortName;
pFaxPort->hFile = INVALID_HANDLE_VALUE;
} else {
MemFree(pFaxPort);
pFaxPort = NULL;
}
*pHandle = (HANDLE) pFaxPort;
return (*pHandle != NULL);
}
VOID
FreeFaxJobInfo(
PFAXPORT pFaxPort
)
/*++
Routine Description:
Free up memory used for maintaining information about the current job
Arguments:
pFaxPort - Points to a fax port structure
Return Value:
NONE
--*/
{
//
// Close and delete the temporary file if necessary
//
if (pFaxPort->hFile != INVALID_HANDLE_VALUE) {
CloseHandle(pFaxPort->hFile);
pFaxPort->hFile = INVALID_HANDLE_VALUE;
}
if (pFaxPort->pFilename) {
DeleteFile(pFaxPort->pFilename);
MemFree(pFaxPort->pFilename);
pFaxPort->pFilename = NULL;
}
if (pFaxPort->hPrinter) {
ClosePrinter(pFaxPort->hPrinter);
pFaxPort->hPrinter = NULL;
}
MemFree(pFaxPort->pPrinterName);
pFaxPort->pPrinterName = NULL;
MemFree(pFaxPort->pParameters);
pFaxPort->pParameters = NULL;
ZeroMemory(&pFaxPort->jobParam, sizeof(pFaxPort->jobParam));
//
// Disconnect from the fax service if necessary
//
if (pFaxPort->hFaxSvc) {
if (! pFaxPort->pFaxClose(pFaxPort->hFaxSvc)) {
Error(("FaxClose failed: %d\n", GetLastError()));
}
FreeLibrary( pFaxPort->hWinFax );
pFaxPort->hFaxSvc = NULL;
pFaxPort->pFaxConnectFaxServerW = NULL;
pFaxPort->pFaxClose = NULL;
pFaxPort->pFaxSendDocumentW = NULL;
pFaxPort->pFaxAccessCheck = NULL;
}
}
BOOL
FaxMonClosePort(
HANDLE hPort
)
/*++
Routine Description:
Closes the port specified by hPort when there are no printers connected to it
Arguments:
hPort - Specifies the handle of the port to be close
Return Value:
TRUE if successful, FALSE if there is an error
--*/
{
PFAXPORT pFaxPort = (PFAXPORT) hPort;
Trace("ClosePort");
//
// Make sure we have a valid handle
//
if (! ValidFaxPort(pFaxPort)) {
Error(("Trying to close an invalid fax port handle\n"));
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
//
// Free up memory used for maintaining information about the current job
//
FreeFaxJobInfo(pFaxPort);
MemFree(pFaxPort->pName);
MemFree(pFaxPort);
return TRUE;
}
LPTSTR
CreateTempFaxFile(
VOID
)
/*++
Routine Description:
Create a temporary file in the system spool directory for storing fax data
Arguments:
NONE
Return Value:
Pointer to the name of the newly created temporary file
NULL if there is an error
--*/
{
//TCHAR spoolDir[MAX_PATH];
//HANDLE hServer;
LPTSTR pFilename;
TCHAR TempDir[MAX_PATH];
TCHAR FileName[MAX_PATH];
//
// Allocate a memory buffer for holding the temporary filename
//
if (pFilename = MemAlloc(sizeof(TCHAR) * MAX_PATH)) {
if (!GetTempPath(sizeof(TempDir)/sizeof(TCHAR),TempDir)||
!GetTempFileName(TempDir, TEXT("fax"), 0, FileName))
{
MemFree(pFilename);
pFilename = NULL;
} else {
lstrcpy(pFilename,FileName);
}
}
if (! pFilename)
Error(("Failed to create temporary file in the spool directory\n"));
return pFilename;
}
BOOL
OpenTempFaxFile(
PFAXPORT pFaxPort,
BOOL doAppend
)
/*++
Routine Description:
Open a handle to the current fax job file associated with a port
Arguments:
pFaxPort - Points to a fax port structure
doAppend - Specifies whether to discard existing data in the file or
append new data to it
Return Value:
TRUE if successful, FALSE otherwise
--*/
{
DWORD creationFlags;
Assert(pFaxPort->pFilename && pFaxPort->hFile == INVALID_HANDLE_VALUE);
Verbose(("Temporary fax job file: %ws\n", pFaxPort->pFilename));
//
// Open the file for reading and writing
//
creationFlags = doAppend ? OPEN_ALWAYS : (OPEN_ALWAYS | TRUNCATE_EXISTING);
pFaxPort->hFile = CreateFile(pFaxPort->pFilename,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
creationFlags,
FILE_ATTRIBUTE_NORMAL,
NULL);
//
// If we're appending, then move the file pointer to end of file
//
if (doAppend && pFaxPort->hFile != INVALID_HANDLE_VALUE &&
SetFilePointer(pFaxPort->hFile, 0, NULL, FILE_END) == 0xffffffff)
{
Error(("SetFilePointer failed: %d\n", GetLastError()));
CloseHandle(pFaxPort->hFile);
pFaxPort->hFile = INVALID_HANDLE_VALUE;
}
return (pFaxPort->hFile != INVALID_HANDLE_VALUE);
}
LPCTSTR
ExtractFaxTag(
LPCTSTR pTagKeyword,
LPCTSTR pTaggedStr,
INT *pcch
)
/*++
Routine Description:
Find the value of for the specified tag in a tagged string.
Arguments:
pTagKeyword - specifies the interested tag keyword
pTaggedStr - points to the tagged string to be searched
pcch - returns the length of the specified tag value (if found)
Return Value:
Points to the value for the specified tag.
NULL if the specified tag is not found
NOTE:
Tagged strings have the following form:
<tag>value<tag>value
The format of tags is defined as:
<$FAXTAG$ tag-name>
There is exactly one space between the tag keyword and the tag name.
Characters in a tag are case-sensitive.
--*/
{
LPCTSTR pValue;
if (pValue = _tcsstr(pTaggedStr, pTagKeyword)) {
pValue += _tcslen(pTagKeyword);
if (pTaggedStr = _tcsstr(pValue, FAXTAG_PREFIX))
*pcch = (INT)(pTaggedStr - pValue);
else
*pcch = _tcslen(pValue);
}
return pValue;
}
BOOL
GetJobInfo(
PFAXPORT pFaxPort,
DWORD jobId
)
/*++
Routine Description:
Retrieve recipient information from the devmode associated with the job
Arguments:
pFaxPort - Points to a fax port structure
jobId - Specifies the current job ID
Return Value:
TRUE if the job parameters are successfully retrieved or
the fax job is from a downlevel fax client.
FALSE if there is an error.
--*/
{
JOB_INFO_2 *pJobInfo2;
LPCTSTR pParameters = NULL;
//
// Retrieve the parameter string associated with the specified job.
// If there is no job parameter or the parameter contains the tag
// <$FAXTAG$ DOWNLEVEL>, then we assume the job comes from downlevel client.
//
ZeroMemory(&pFaxPort->jobParam, sizeof(pFaxPort->jobParam));
pFaxPort->jobParam.SizeOfStruct = sizeof( FAX_JOB_PARAM );
if ((pJobInfo2 = MyGetJob(pFaxPort->hPrinter, 2, jobId)) == NULL ||
(pParameters = pJobInfo2->pParameters) == NULL ||
_tcsstr(pParameters, FAXTAG_DOWNLEVEL_CLIENT) != NULL)
{
MemFree(pJobInfo2);
return TRUE;
}
if ((pFaxPort->pParameters = DuplicateString(pParameters)) != NULL) {
//
// Tags used to pass information about fax jobs
//
static LPCTSTR faxtagNames[NUM_JOBPARAM_TAGS] = {
FAXTAG_RECIPIENT_NUMBER,
FAXTAG_RECIPIENT_NAME,
FAXTAG_TSID,
FAXTAG_SENDER_NAME,
FAXTAG_SENDER_COMPANY,
FAXTAG_SENDER_DEPT,
FAXTAG_BILLING_CODE,
FAXTAG_WHEN_TO_SEND,
FAXTAG_SEND_AT_TIME,
FAXTAG_PROFILE_NAME,
FAXTAG_EMAIL_NAME
};
LPTSTR WhenToSend = NULL;
LPTSTR SendAtTime = NULL;
LPTSTR DeliveryReportType = NULL;
LPTSTR *fieldStr[NUM_JOBPARAM_TAGS] = {
(LPTSTR *)&pFaxPort->jobParam.RecipientNumber,
(LPTSTR *)&pFaxPort->jobParam.RecipientName,
(LPTSTR *)&pFaxPort->jobParam.Tsid,
(LPTSTR *)&pFaxPort->jobParam.SenderName,
(LPTSTR *)&pFaxPort->jobParam.SenderCompany,
(LPTSTR *)&pFaxPort->jobParam.SenderDept,
(LPTSTR *)&pFaxPort->jobParam.BillingCode,
&WhenToSend,
&SendAtTime,
(LPTSTR *)&pFaxPort->jobParam.DeliveryReportAddress,
&DeliveryReportType
};
INT fieldLen[NUM_JOBPARAM_TAGS];
INT count;
pParameters = pFaxPort->pParameters;
Verbose(("JOB_INFO_2.pParameter = %ws\n", pParameters));
//
// Extract individual fields out of the tagged string
//
for (count=0; count < NUM_JOBPARAM_TAGS; count++) {
*fieldStr[count] = (LPTSTR)ExtractFaxTag(faxtagNames[count],
pParameters,
&fieldLen[count]);
}
//
// Null-terminate each field
//
for (count=0; count < NUM_JOBPARAM_TAGS; count++) {
if (*fieldStr[count]) {
(*fieldStr[count])[fieldLen[count]] = NUL;
}
}
if (WhenToSend) {
if (_tcsicmp( WhenToSend, TEXT("cheap") ) == 0) {
pFaxPort->jobParam.ScheduleAction = JSA_DISCOUNT_PERIOD;
} else if (_tcsicmp( WhenToSend, TEXT("at") ) == 0) {
pFaxPort->jobParam.ScheduleAction = JSA_SPECIFIC_TIME;
}
}
if (SendAtTime) {
if (_tcslen(SendAtTime) == 5 && SendAtTime[2] == L':' &&
_istdigit(SendAtTime[0]) && _istdigit(SendAtTime[1]) &&
_istdigit(SendAtTime[3]) && _istdigit(SendAtTime[4]))
{
DWORDLONG FileTime;
SYSTEMTIME LocalTime;
INT Minutes;
INT SendMinutes;
SendAtTime[2] = 0;
//
// Calculate the number of minutes from now to send and add that to the current time.
//
GetLocalTime( &LocalTime );
SystemTimeToFileTime( &LocalTime, (LPFILETIME) &FileTime );
SendMinutes = min(23,_ttoi( &SendAtTime[0] )) * 60 + min(59,_ttoi( &SendAtTime[3] ));
Minutes = LocalTime.wHour * 60 + LocalTime.wMinute;
Minutes = SendMinutes - Minutes;
// Account for passing midnight
//
if (Minutes < 0) {
Minutes += 24 * 60;
}
FileTime += (DWORDLONG)(Minutes * 60I64 * 1000I64 * 1000I64 * 10I64);
FileTimeToSystemTime((LPFILETIME) &FileTime, &pFaxPort->jobParam.ScheduleTime );
SendAtTime[2] = L':';
}
}
if (DeliveryReportType) {
if (_tcsicmp( DeliveryReportType, TEXT("email") ) == 0) {
pFaxPort->jobParam.DeliveryReportType = DRT_EMAIL;
} else if (_tcsicmp( DeliveryReportType, TEXT("inbox") ) == 0) {
pFaxPort->jobParam.DeliveryReportType = DRT_INBOX;
}
}
if (pFaxPort->jobParam.RecipientNumber == NULL) {
Error(("Missing recipient phone number.\n"));
SetJob(pFaxPort->hPrinter, jobId, 0, NULL, JOB_CONTROL_PAUSE);
SetLastError(ERROR_INVALID_PARAMETER);
}
}
MemFree(pJobInfo2);
return (pFaxPort->jobParam.RecipientNumber != NULL);
}
BOOL
FaxMonStartDocPort(
HANDLE hPort,
LPTSTR pPrinterName,
DWORD JobId,
DWORD Level,
LPBYTE pDocInfo
)
/*++
Routine Description:
Spooler calls this function to start a new print job on the port
Arguments:
hPort - Identifies the port
pPrinterName - Specifies the name of the printer to which the job is being sent
JobId - Identifies the job being sent by the spooler
Level - Specifies the DOC_INFO_x level
pDocInfo - Points to the document information
Return Value:
TRUE if successful, FALSE if there is an error
--*/
{
PFAXPORT pFaxPort = (PFAXPORT) hPort;
Verbose(("Entering StartDocPort: %d ...\n", JobId));
//
// Make sure we have a valid handle
//
if (! ValidFaxPort(pFaxPort)) {
Error(("StartDocPort is given an invalid fax port handle\n"));
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
//
// Check if we're at the beginning of a series of chained jobs
//
if (! pFaxPort->hFaxSvc) {
PJOB_INFO_1 pJobInfo1;
PORT_INFO_3 portInfo3;
HANDLE hPrinter = NULL;
BOOL offline = FALSE;
DWORD count = connectRetryCount;
DWORD jobStatus = 0;
Assert(pFaxPort->pPrinterName == NULL &&
pFaxPort->hPrinter == NULL &&
pFaxPort->pParameters == NULL &&
pFaxPort->pFilename == NULL &&
pFaxPort->hFile == INVALID_HANDLE_VALUE);
//
// load the winfax dll
//
pFaxPort->hWinFax = LoadLibrary( L"winfax.dll" );
if (pFaxPort->hWinFax == NULL) {
Error(("LoadLibrary failed loading winfax.dll\n"));
return FALSE;
}
//
// get the function addresses
//
pFaxPort->pFaxConnectFaxServerW = (PFAXCONNECTFAXSERVERW) GetProcAddress( pFaxPort->hWinFax, "FaxConnectFaxServerW" );
pFaxPort->pFaxClose = (PFAXCLOSE) GetProcAddress( pFaxPort->hWinFax, "FaxClose" );
pFaxPort->pFaxSendDocumentW = (PFAXSENDDOCUMENTW) GetProcAddress( pFaxPort->hWinFax, "FaxSendDocumentW" );
pFaxPort->pFaxAccessCheck = (PFAXACCESSCHECK) GetProcAddress( pFaxPort->hWinFax, "FaxAccessCheck" );
if (pFaxPort->pFaxConnectFaxServerW == NULL ||
pFaxPort->pFaxClose == NULL ||
pFaxPort->pFaxSendDocumentW == NULL ||
pFaxPort->pFaxAccessCheck == NULL) {
Error(("GetProcAddress failed loading winfax.dll\n"));
return FALSE;
}
//
// Connect to the fax service and obtain a session handle
//
while (! pFaxPort->pFaxConnectFaxServerW(NULL, &pFaxPort->hFaxSvc)) {
Error(("FaxConnectFaxServer failed: %d\n", GetLastError()));
pFaxPort->hFaxSvc = NULL;
if (! offline) {
portInfo3.dwStatus = PORT_STATUS_OFFLINE;
portInfo3.pszStatus = NULL;
portInfo3.dwSeverity = PORT_STATUS_TYPE_WARNING;
if (! SetPort(NULL, pFaxPort->pName, 3, (LPBYTE) &portInfo3))
Error(("SetPort failed: %d\n", GetLastError()));
}
offline = TRUE;
Sleep(connectRetryInterval * 1000);
//
// Check if the job has been deleted or restarted
//
if (!hPrinter && !OpenPrinter(pPrinterName, &hPrinter, NULL)) {
Error(("OpenPrinter failed: %d\n", GetLastError()));
hPrinter = NULL;
} else if (pJobInfo1 = MyGetJob(hPrinter, 1, JobId)) {
jobStatus = pJobInfo1->Status;
}
MemFree(pJobInfo1);
if (--count == 0 || (jobStatus & (JOB_STATUS_DELETING|
JOB_STATUS_DELETED|
JOB_STATUS_RESTART)))
{
break;
}
}
//
// Remove the offline status on the port
//
if (offline) {
portInfo3.dwStatus = 0;
portInfo3.pszStatus = NULL;
portInfo3.dwSeverity = PORT_STATUS_TYPE_INFO;
if (! SetPort(NULL, pFaxPort->pName, 3, (LPBYTE) &portInfo3)) {
Error(("SetPort failed: %d\n", GetLastError()));
}
}
if (hPrinter) {
ClosePrinter(hPrinter);
}
if (pFaxPort->hFaxSvc) {
if (!pFaxPort->pFaxAccessCheck(pFaxPort->hFaxSvc, FAX_JOB_SUBMIT) ) {
FreeFaxJobInfo(pFaxPort);
Error(("FaxAccessCheck failed : %d\n", GetLastError() ));
SetLastError(ERROR_ACCESS_DENIED);
return FALSE;
}
// HANDLE hToken;
//
// The monitor runs in the context of the current job's owner.
// In order to create temporary files in the spool directory,
// we need to revert to the spooler context first.
//
/* if (! (hToken = RevertToPrinterSelf()))
Error(("RevertToPrinterSelf failed: %d\n", GetLastError()));
*/
//
// Remember the printer name because we'll need it at EndDocPort time.
// Get a temporary filename and open it for reading and writing.
// Remember other job related information
//
if (! (pFaxPort->pPrinterName = DuplicateString(pPrinterName)) ||
! OpenPrinter(pPrinterName, &pFaxPort->hPrinter, NULL) ||
! GetJobInfo(pFaxPort, JobId) ||
! (pFaxPort->pFilename = CreateTempFaxFile()) ||
! OpenTempFaxFile(pFaxPort, FALSE))
{
FreeFaxJobInfo(pFaxPort);
} else
pFaxPort->jobId = pFaxPort->nextJobId = JobId;
//
// Switch back to the original context if necessary
//
/* if (hToken && !ImpersonatePrinterClient(hToken))
Error(("ImpersonatePrinterClient failed: %d\n", GetLastError())); */
}
} else {
Assert(pFaxPort->jobId == JobId);
}
return (pFaxPort->hFaxSvc != NULL);
}
INT
FixUpFaxFile(
PFAXPORT pFaxPort
)
/*++
Routine Description:
Fixed up the saved print job data into a well-formed TIFF file
Arguments:
pFaxPort - Points to a fax port structure
Return Value:
error code FAXERR_*
--*/
{
DWORD fileSize;
PBYTE pFileEnd, pFileHdr;
PBYTE pFileView = NULL;
HANDLE hFileMap = NULL;
INT result = FAXERR_BAD_TIFF;
//
// Get the size of print job file
//
FlushFileBuffers(pFaxPort->hFile);
if ((fileSize = GetFileSize(pFaxPort->hFile, NULL)) == 0)
return FAXERR_IGNORE;
if (fileSize == 0xffffffff)
return FAXERR_FATAL;
__try {
//
// Map the fax job file into memory
//
if ((hFileMap = CreateFileMapping(pFaxPort->hFile, NULL, PAGE_READWRITE, 0, 0, NULL)) &&
(pFileHdr = pFileView = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, fileSize)) &&
ValidTiffFileHeader(pFileHdr))
{
DWORD ifdOffset, maxOffset, fileOffset;
PBYTE pIfdOffset;
//
// A fax print job may contain multiple TIFF files. Each each iteration
// of the outer loop below deals with a single embedded TIFF file.
//
pFileEnd = pFileHdr + fileSize;
ifdOffset = ReadUnalignedDWord(pFileHdr + sizeof(DWORD));
do {
Verbose(("Reading embedded TIFF file ...\n"));
maxOffset = 0;
fileOffset = (DWORD)(pFileHdr - pFileView);
//
// Each iteration of the following loops processes one IFD
// from an embedded TIFF file.
//
do {
PTIFF_IFD pIfd;
PTIFF_TAG pIfdEntry;
INT ifdCount;
DWORD size, index, stripCount = 0;
PDWORD pStripOffsets = NULL;
pIfd = (PTIFF_IFD) (pFileHdr + ifdOffset);
Assert( (PBYTE) pIfd < pFileEnd);
if ((PBYTE) pIfd >= pFileEnd) {
result = FAXERR_FATAL;
__leave;
}
ifdOffset += sizeof(WORD) + pIfd->wEntries * sizeof(TIFF_TAG);
pIfdOffset = pFileHdr + ifdOffset;
Assert(pIfdOffset < pFileEnd);
if (pIfdOffset >= pFileEnd) {
result = FAXERR_FATAL;
__leave;
}
if ((ifdOffset + sizeof(DWORD)) > maxOffset)
maxOffset = ifdOffset + sizeof(DWORD);
//
// We should add the file offset to any non-zero IFD offset
//
if ((ifdOffset = ReadUnalignedDWord(pIfdOffset)) != 0)
WriteUnalignedDWord(pIfdOffset, ifdOffset + fileOffset);
//
// Now go through each IFD entry and calculate the largest offset
//
pIfdEntry = (PTIFF_TAG) ((PBYTE) pIfd + sizeof(WORD));
ifdCount = pIfd->wEntries;
Verbose((" Reading IFD: %d entries ...\n", ifdCount));
while (ifdCount-- > 0) {
//
// Figure the size of various TIFF data types
//
switch (pIfdEntry->DataType) {
case TIFF_ASCII:
case TIFF_BYTE:
case TIFF_SBYTE:
case TIFF_UNDEFINED:
size = 1;
break;
case TIFF_SHORT:
case TIFF_SSHORT:
size = 2;
break;
case TIFF_LONG:
case TIFF_SLONG:
case TIFF_FLOAT:
size = 4;
break;
case TIFF_RATIONAL:
case TIFF_SRATIONAL:
case TIFF_DOUBLE:
size = 8;
break;
default:
Warning(("Unknown TIFF data type: %d\n", pIfdEntry->DataType));
size = 1;
break;
}
//
// Look for StripOffsets and StripByteCounts tags
//
if (pIfdEntry->TagId == TIFFTAG_STRIPOFFSETS ||
pIfdEntry->TagId == TIFFTAG_STRIPBYTECOUNTS)
{
DWORD n = pIfdEntry->DataCount;
if ((pIfdEntry->DataType == TIFF_LONG) &&
(stripCount == 0 || stripCount == n) &&
(pStripOffsets || (pStripOffsets = MemAllocZ(sizeof(DWORD)*n))))
{
if ((stripCount = n) == 1) {
pStripOffsets[0] += pIfdEntry->DataOffset;
if (pIfdEntry->TagId == TIFFTAG_STRIPOFFSETS)
pIfdEntry->DataOffset += fileOffset;
} else {
DWORD UNALIGNED *p;
Verbose(("Multiple strips per page: %d\n", n));
p = (DWORD UNALIGNED *) (pFileHdr + pIfdEntry->DataOffset);
for (index=0; index < stripCount; index++) {
n = *p;
pStripOffsets[index] += n;
if (pIfdEntry->TagId == TIFFTAG_STRIPOFFSETS)
*p = n + fileOffset;
p = (DWORD UNALIGNED *) ((LPBYTE) p + sizeof(DWORD));
}
}
} else
Error(("Bad StripOffsets/StripByteCounts tag\n"));
}
//
// For a composite value, IFDENTRY.value is an offset
//
if (size * pIfdEntry->DataCount > sizeof(DWORD)) {
if (pIfdEntry->DataOffset > maxOffset)
maxOffset = pIfdEntry->DataOffset;
pIfdEntry->DataOffset += fileOffset;
}
pIfdEntry++;
}
//
// Make sure to skip the image data when search for the next file
//
if (pStripOffsets) {
for (index=0; index < stripCount; index++) {
if (pStripOffsets[index] > maxOffset)
maxOffset = pStripOffsets[index];
}
MemFree(pStripOffsets);
}
} while (ifdOffset);
//
// Search for the beginning of next TIFF file
//
pFileHdr += maxOffset;
while (pFileHdr < pFileEnd) {
if (ValidTiffFileHeader(pFileHdr)) {
//
// Modify the offset in the last IFD
//
ifdOffset = ReadUnalignedDWord(pFileHdr + sizeof(DWORD));
WriteUnalignedDWord(pIfdOffset, ifdOffset + (DWORD)(pFileHdr - pFileView));
break;
}
pFileHdr++;
}
} while (pFileHdr < pFileEnd);
result = FAXERR_NONE;
}
} __finally {
//
// Perform necessary cleanup before returning to caller
//
if (pFileView)
UnmapViewOfFile(pFileView);
if (hFileMap)
CloseHandle(hFileMap);
CloseHandle(pFaxPort->hFile);
pFaxPort->hFile = INVALID_HANDLE_VALUE;
}
return result;
}
INT
CheckJobRestart(
PFAXPORT pFaxPort
)
/*++
Routine Description:
Check if the job has been restarted.
If not, get the ID of the next job in the chain.
Arguments:
pFaxPort - Points to a fax port structure
Return Value:
FAXERR_RESTART or FAXERR_NONE
--*/
{
JOB_INFO_3 *pJobInfo3;
JOB_INFO_2 *pJobInfo2;
INT status = FAXERR_NONE;
//
// If not, get the ID of the next job in the chain.
//
Verbose(("Job chain: id = %d\n", pFaxPort->nextJobId));
if (pJobInfo3 = MyGetJob(pFaxPort->hPrinter, 3, pFaxPort->jobId)) {
pFaxPort->nextJobId = pJobInfo3->NextJobId;
MemFree(pJobInfo3);
} else
pFaxPort->nextJobId = 0;
//
// Determine whether the job has been restarted or deleted
//
if (pJobInfo2 = MyGetJob(pFaxPort->hPrinter, 2, pFaxPort->jobId)) {
if (pJobInfo2->Status & (JOB_STATUS_RESTART | JOB_STATUS_DELETING))
status = FAXERR_RESTART;
MemFree(pJobInfo2);
}
return status;
}
BOOL
FaxMonEndDocPort(
HANDLE hPort
)
/*++
Routine Description:
Spooler calls this function at the end of a print job
Arguments:
hPort - Identifies the port
Return Value:
TRUE if successful, FALSE if there is an error
--*/
{
PFAXPORT pFaxPort = (PFAXPORT) hPort;
INT status;
LPTSTR pAtSign, pNewRecipName = NULL;
//HANDLE hToken;
DWORD FaxJobId;
BOOL Rslt;
JOB_INFO_2 *pJobInfo2;
Trace("EndDocPort");
//
// Make sure we have a valid handle
//
if (! ValidFaxPort(pFaxPort) || ! pFaxPort->hFaxSvc) {
Error(("EndDocPort is given an invalid fax port handle\n"));
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
//
// Check if the job has been restarted. If not, get the ID of
// the next job in the chain.
//
if ((status = CheckJobRestart(pFaxPort)) != FAXERR_NONE)
goto ExitEndDocPort;
//
// Check if we're at the end of a job chain
//
if (pFaxPort->nextJobId != 0 && pFaxPort->pParameters != NULL) {
SetJob(pFaxPort->hPrinter, pFaxPort->jobId, 0, NULL, JOB_CONTROL_SENT_TO_PRINTER);
return TRUE;
}
//
// The monitor runs in the context of the current job's owner.
// In order to create temporary files in the spool directory,
// we need to revert to the spooler context first.
//
/* if (! (hToken = RevertToPrinterSelf()))
Error(("RevertToPrinterSelf failed: %d\n", GetLastError()));
*/
//
// Check if we're dealing with fax jobs from win31 or win95 clients
//
if ((pFaxPort->pParameters == NULL) &&
(status = ProcessDownlevelFaxJob(pFaxPort)) != FAXERR_NONE)
{
goto ExitEndDocPort;
}
//
// Fix up the temporary fax data into a properly formatted TIFF file.
//
if ((status = FixUpFaxFile(pFaxPort)) != FAXERR_NONE) {
goto ExitEndDocPort;
}
//
// Call the fax service to send the TIFF file
//
#if DBG
if (_debugLevel > 0) {
DbgPrint("Send document to fax service:\n");
DbgPrint(" Printer Name: %ws\n", pFaxPort->pPrinterName);
DbgPrint(" Job ID: %d\n", pFaxPort->jobId);
DbgPrint(" File Name: %ws\n", pFaxPort->pFilename);
DbgPrint(" Recipient Number: %ws\n", pFaxPort->jobParam.RecipientNumber);
DbgPrint(" Recipient Name: %ws\n", pFaxPort->jobParam.RecipientName);
DbgPrint(" TSID: %ws\n", pFaxPort->jobParam.Tsid);
DbgPrint(" Sender Name: %ws\n", pFaxPort->jobParam.SenderName);
DbgPrint(" Sender Company: %ws\n", pFaxPort->jobParam.SenderCompany);
DbgPrint(" Sender Dept: %ws\n", pFaxPort->jobParam.SenderDept);
DbgPrint(" Billing Code: %ws\n", pFaxPort->jobParam.BillingCode);
}
#endif
//
// fixup the fax address
//
if (pAtSign = _tcschr(pFaxPort->jobParam.RecipientNumber, TEXT('@'))) {
*pAtSign++ = NUL;
if (pFaxPort->jobParam.RecipientName == NULL)
pNewRecipName = (LPTSTR) pFaxPort->jobParam.RecipientName = (LPTSTR)DuplicateString(pFaxPort->jobParam.RecipientNumber);
_tcscpy((LPTSTR)pFaxPort->jobParam.RecipientNumber, pAtSign);
}
//
// send the fax
//
pJobInfo2 = MyGetJob( pFaxPort->hPrinter, 2, pFaxPort->jobId );
if (pJobInfo2) {
pFaxPort->jobParam.DocumentName = pJobInfo2->pDocument;
} else {
pFaxPort->jobParam.DocumentName = NULL;
}
/* if (hToken && !ImpersonatePrinterClient(hToken)) {
Error(("ImpersonatePrinterClient failed: %d\n", GetLastError()));
} */
pFaxPort->jobParam.Reserved[0] = 0xffffffff;
pFaxPort->jobParam.Reserved[1] = pFaxPort->jobId;
Rslt = pFaxPort->pFaxSendDocumentW( pFaxPort->hFaxSvc, pFaxPort->pFilename, &pFaxPort->jobParam, NULL, &FaxJobId );
/* if (! (hToken = RevertToPrinterSelf())) {
Error(("RevertToPrinterSelf failed: %d\n", GetLastError()));
} */
if (pJobInfo2) {
MemFree( pJobInfo2 );
pFaxPort->jobParam.DocumentName = NULL;
}
if (Rslt) {
status = FAXERR_NONE;
SetJob(pFaxPort->hPrinter, pFaxPort->jobId, 0, NULL, JOB_CONTROL_SENT_TO_PRINTER);
DeleteFile( pFaxPort->pFilename );
} else {
status = GetLastError();
Error(("FaxSendDocumentForSpooler failed: %d\n", GetLastError()));
}
ExitEndDocPort:
if (status == FAXERR_NONE) {
//
// If the job was successfully sent to the fax service, then
// the service will delete the temporary file when it's done
// with it. So we don't need to delete it here.
//
MemFree(pFaxPort->pFilename);
pFaxPort->pFilename = NULL;
} else {
//
// If the job wasn't successfully sent to the fax service,
// inform the spooler that there is an error on the job.
//
// Or if the print job has no data, simply ignore it.
//
switch (status) {
case FAXERR_RESTART:
Warning(("Job restarted or deleted: id = %d\n", pFaxPort->jobId));
case FAXERR_IGNORE:
SetJob(pFaxPort->hPrinter, pFaxPort->jobId, 0, NULL, JOB_CONTROL_SENT_TO_PRINTER);
break;
default:
Error(("Error sending fax job: id = %d\n", pFaxPort->jobId));
SetJob(pFaxPort->hPrinter, pFaxPort->jobId, 0, NULL, JOB_CONTROL_PAUSE);
SetJobStatus(pFaxPort->hPrinter, pFaxPort->jobId, status);
break;
}
}
if (pNewRecipName) {
MemFree(pNewRecipName);
pFaxPort->jobParam.RecipientName = NULL;
}
FreeFaxJobInfo(pFaxPort);
//
// Switch back to the original context if necessary
//
/* if (hToken && !ImpersonatePrinterClient(hToken))
Error(("ImpersonatePrinterClient failed: %d\n", GetLastError()));*/
return (status < FAXERR_SPECIAL);
}
BOOL
FaxMonWritePort(
HANDLE hPort,
LPBYTE pBuffer,
DWORD cbBuf,
LPDWORD pcbWritten
)
/*++
Routine Description:
Writes data to a port
Arguments:
hPort - Identifies the port
pBuffer - Points to a buffer that contains data to be written to the port
cbBuf - Specifies the size in bytes of the buffer
pcbWritten - Returns the count of bytes successfully written to the port
Return Value:
TRUE if successful, FALSE if there is an error
--*/
{
PFAXPORT pFaxPort = (PFAXPORT) hPort;
//
// Make sure we have a valid handle
//
if (! ValidFaxPort(pFaxPort) || ! pFaxPort->hFaxSvc) {
Error(("WritePort is given an invalid fax port handle\n"));
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
Assert(pFaxPort->hFile != INVALID_HANDLE_VALUE);
return WriteFile(pFaxPort->hFile, pBuffer, cbBuf, pcbWritten, NULL);
}
BOOL
FaxMonReadPort(
HANDLE hPort,
LPBYTE pBuffer,
DWORD cbBuf,
LPDWORD pcbRead
)
/*++
Routine Description:
Reads data from the port
Arguments:
hPort - Identifies the port
pBuffer - Points to a buffer where data read from the printer can be written
cbBuf - Specifies the size in bytes of the buffer pointed to by pBuffer
pcbRead - Returns the number of bytes successfully read from the port
Return Value:
TRUE if successful, FALSE if there is an error
--*/
{
Trace("ReadPort");
SetLastError(ERROR_NOT_SUPPORTED);
return FALSE;
}
BOOL
FaxMonEnumPorts(
LPTSTR pServerName,
DWORD Level,
LPBYTE pPorts,
DWORD cbBuf,
LPDWORD pcbNeeded,
LPDWORD pReturned
)
/*++
Routine Description:
Enumerates the ports available on the specified server
Arguments:
pServerName - Specifies the name of the server whose ports are to be enumerated
dwLevel - Specifies the version of the structure to which pPorts points
pPorts - Points to an array of PORT_INFO_1 structures where data describing
the available ports will be writteno
cbBuf - Specifies the size in bytes of the buffer to which pPorts points
pcbNeeded - Returns the required buffer size identified by pPorts
pReturned - Returns the number of PORT_INFO_1 structures returned
Return Value:
TRUE if successful, FALSE if there is an error
--*/
#define MAX_DESC_LEN 64
{
TCHAR portDescStr[MAX_DESC_LEN];
INT descStrSize, faxmonNameSize;
DWORD cbNeeded;
BOOL status = TRUE;
PORT_INFO_1 *pPortInfo1 = (PORT_INFO_1 *) pPorts;
PORT_INFO_2 *pPortInfo2 = (PORT_INFO_2 *) pPorts;
INT strSize;
Trace("EnumPorts");
if (pcbNeeded == NULL || pReturned == NULL || (pPorts == NULL && cbBuf != 0)) {
Error(("Invalid input parameters\n"));
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
//
// Load the fax port description string
//
if (! LoadString(ghInstance, IDS_FAX_PORT_DESC, portDescStr, MAX_DESC_LEN))
portDescStr[0] = NUL;
descStrSize = SizeOfString(portDescStr);
faxmonNameSize = SizeOfString(faxMonitorName);
switch (Level) {
case 1:
cbNeeded = sizeof(PORT_INFO_1) + SizeOfString(FAX_PORT_NAME);
break;
case 2:
cbNeeded = sizeof(PORT_INFO_2) + descStrSize + faxmonNameSize + SizeOfString(FAX_PORT_NAME);
break;
}
*pReturned = 1;
*pcbNeeded = cbNeeded;
if (cbNeeded > cbBuf) {
//
// Caller didn't provide a big enough buffer
//
SetLastError(ERROR_INSUFFICIENT_BUFFER);
status = FALSE;
} else {
//
// Strings must be packed at the end of the caller provided buffer.
// Otherwise, spooler will mess up.
//
pPorts += cbBuf;
//
// Copy the requested port information to the caller provided buffer
//
strSize = SizeOfString(FAX_PORT_NAME);
pPorts -= strSize;
CopyMemory(pPorts, FAX_PORT_NAME, strSize);
switch (Level) {
case 1:
pPortInfo1->pName = (LPTSTR) pPorts;
Verbose(("Port info 1: %ws\n", pPortInfo1->pName));
pPortInfo1++;
break;
case 2:
pPortInfo2->pPortName = (LPTSTR) pPorts;
//
// Copy the fax monitor name string
//
pPorts -= faxmonNameSize;
pPortInfo2->pMonitorName = (LPTSTR) pPorts;
CopyMemory(pPorts, faxMonitorName, faxmonNameSize);
//
// Copy the fax port description string
//
pPorts -= descStrSize;
pPortInfo2->pDescription = (LPTSTR) pPorts;
CopyMemory(pPorts, portDescStr, descStrSize);
pPortInfo2->fPortType = PORT_TYPE_WRITE;
pPortInfo2->Reserved = 0;
Verbose(("Port info 2: %ws, %ws, %ws\n",
pPortInfo2->pPortName,
pPortInfo2->pMonitorName,
pPortInfo2->pDescription));
pPortInfo2++;
break;
}
}
return status;
}
BOOL
DisplayErrorNotImplemented(
HWND hwnd,
INT titleId
)
/*++
Routine Description:
Display an error dialog to tell the user that he cannot manage
fax devices in the Printers folder.
Arguments:
hwnd - Specifies the parent window for the message box
titleId - Message box title string resource ID
Return Value:
FALSE
--*/
{
TCHAR title[128];
TCHAR message[256];
LoadString(ghInstance, titleId, title, 128);
LoadString(ghInstance, IDS_CONFIG_ERROR, message, 256);
MessageBox(hwnd, message, title, MB_OK|MB_ICONERROR);
SetLastError(ERROR_SUCCESS);
return FALSE;
}
BOOL
FaxMonAddPort(
LPTSTR pServerName,
HWND hwnd,
LPTSTR pMonitorName
)
/*++
Routine Description:
Adds the name of a port to the list of supported ports
Arguments:
pServerName - Specifies the name of the server to which the port is to be added
hwnd - Identifies the parent window of the AddPort dialog box
pMonitorName - Specifies the monitor associated with the port
Return Value:
TRUE if successful, FALSE if there is an error
--*/
{
Trace("AddPort");
return DisplayErrorNotImplemented(hwnd, IDS_ADD_PORT);
}
BOOL
FaxMonAddPortEx(
LPTSTR pServerName,
DWORD level,
LPBYTE pBuffer,
LPTSTR pMonitorName
)
/*++
Routine Description:
Adds the name of a port to the list of supported ports
Arguments:
pServerName - Specifies the name of the server to which the port is to be added
hwnd - Identifies the parent window of the AddPort dialog box
pMonitorName - Specifies the monitor associated with the port
Return Value:
TRUE if successful, FALSE if there is an error
--*/
{
Trace("AddPortEx");
SetLastError(ERROR_NOT_SUPPORTED);
return FALSE;
}
BOOL
FaxMonDeletePort(
LPTSTR pServerName,
HWND hwnd,
LPTSTR pPortName
)
/*++
Routine Description:
Delete the specified port from the list of supported ports
Arguments:
pServerName - Specifies the name of the server from which the port is to be removed
hwnd - Identifies the parent window of the port-deletion dialog box
pPortName - Specifies the name of the port to be deleted
Return Value:
TRUE if successful, FALSE if there is an error
--*/
{
Trace("DeletePort");
return DisplayErrorNotImplemented(hwnd, IDS_CONFIGURE_PORT);
}
BOOL
FaxMonConfigurePort(
LPWSTR pServerName,
HWND hwnd,
LPWSTR pPortName
)
/*++
Routine Description:
Display a dialog box to allow user to configure the specified port
Arguments:
pServerName - Specifies the name of the server on which the given port exists
hwnd - Identifies the parent window of the port-configuration dialog
pPortName - Specifies the name of the port to be configured
Return Value:
TRUE if successful, FALSE if there is an error
--*/
{
Trace("ConfigurePort");
return DisplayErrorNotImplemented(hwnd, IDS_CONFIGURE_PORT);
}
LPTSTR
DuplicateString(
LPCTSTR pSrcStr
)
/*++
Routine Description:
Make a duplicate of the given character string
Arguments:
pSrcStr - Specifies the string to be duplicated
Return Value:
Pointer to the duplicated string, NULL if there is an error
--*/
{
LPTSTR pDestStr;
INT strSize;
if (pSrcStr != NULL) {
strSize = SizeOfString(pSrcStr);
if (pDestStr = MemAlloc(strSize))
CopyMemory(pDestStr, pSrcStr, strSize);
else
Error(("Memory allocation failed\n"));
} else
pDestStr = NULL;
return pDestStr;
}
PVOID
MyGetJob(
HANDLE hPrinter,
DWORD level,
DWORD jobId
)
/*++
Routine Description:
Wrapper function for spooler API GetJob
Arguments:
hPrinter - Handle to the printer object
level - Level of JOB_INFO structure interested
jobId - Specifies the job ID
Return Value:
Pointer to a JOB_INFO structure, NULL if there is an error
--*/
{
PBYTE pJobInfo = NULL;
DWORD cbNeeded;
if (!GetJob(hPrinter, jobId, level, NULL, 0, &cbNeeded) &&
GetLastError() == ERROR_INSUFFICIENT_BUFFER &&
(pJobInfo = MemAlloc(cbNeeded)) &&
GetJob(hPrinter, jobId, level, pJobInfo, cbNeeded, &cbNeeded))
{
return pJobInfo;
}
Error(("GetJob failed: %d\n", GetLastError()));
MemFree(pJobInfo);
return NULL;
}
BOOL
SetJobStatus(
HANDLE hPrinter,
DWORD jobId,
INT statusStrId
)
/*++
Routine Description:
Update the status information of a print job
Arguments:
hPrinter - Specifies the printer on which the job is printed
jobId - Specifies the job identifier
statusStrID - Specifies the status string resource ID
Return Value:
TRUE if successful, FALSE if there is an error
--*/
#define MAX_MESSAGE_LEN 256
{
JOB_INFO_1 *pJobInfo1;
BOOL result = FALSE;
TCHAR message[MAX_MESSAGE_LEN];
//
// Get the current job information
//
if (pJobInfo1 = MyGetJob(hPrinter, 1, jobId)) {
//
// Update the status field
//
if (LoadString(ghInstance, statusStrId, message, MAX_MESSAGE_LEN))
pJobInfo1->pStatus = message;
else {
pJobInfo1->pStatus = NULL;
pJobInfo1->Status = JOB_STATUS_ERROR;
}
pJobInfo1->Position = JOB_POSITION_UNSPECIFIED;
if (! (result = SetJob(hPrinter, jobId, 1, (PBYTE) pJobInfo1, 0)))
Error(("SetJob failed: %d\n", GetLastError()));
MemFree(pJobInfo1);
}
return result;
}
DWORD
GetRegistryDWord(
HKEY hRegKey,
LPTSTR pValueName,
DWORD defaultValue
)
/*++
Routine Description:
Retrieve a DWORD value from the registry
Arguments:
hRegKey - Handle to the user info registry key
pValueName - Specifies the name of the string value in registry
defaultValue - Specifies the default value to be used in case of an error
Return Value:
Requested DWORD value from the user info registry key
--*/
{
DWORD size, type, value;
//
// Retrieve the country code value from the registry.
// Use the default value if none exists.
//
size = sizeof(value);
if (RegQueryValueEx(hRegKey, pValueName, NULL, &type, (PBYTE) &value, &size) != ERROR_SUCCESS ||
type != REG_DWORD)
{
value = defaultValue;
}
return value;
}
#if DBG
//
// Variable for controlling the amount of debug messages generated
//
INT _debugLevel = 1;
LPCSTR
StripDirPrefixA(
LPCSTR pFilename
)
/*++
Routine Description:
Strip the directory prefix off a filename
Arguments:
pFilename - Pointer to filename string
Return Value:
Pointer to the last component of a filename (without directory prefix)
--*/
{
LPCSTR pstr;
if (pstr = strrchr(pFilename, PATH_SEPARATOR))
return pstr + 1;
return pFilename;
}
#endif