/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT (C) Microsoft Corporation, 1989                                 */
/* COPYRIGHT    Copyright (C) 1995 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 WARP source code is provided to you solely for  */
/*    the purpose of assisting you in your development of OS/2 WARP device   */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Device Driver Source Kit for OS/2. This  */
/*    Copyright statement may not be removed.                                */
/*                                                                           */
/*****************************************************************************/
/**************************************************************************
 *
 * SOURCE FILE NAME = utlchnl.c
 *
 * DESCRIPTIVE NAME = PRINTER DRIVER SOURCE
 *
 *
 * VERSION = V2.0
 *
 * DATE
 *
 * DESCRIPTION
 *
 *
 * FUNCTIONS
 *
 *                 AsciiToBin
 *                 InitializeCommPort
 *                 OpenChannel()
 *                 WriteChannel()
 *                 FlushChannel()
 *                  Cvi_R2
 *                 WriteModeString
 *                  HexToChannel
 *                 CloseChannel()
 *                 PrintChannel()
 *                 CheckComPortName ()
 *
 *
 * NOTES
 *
 *
 * STRUCTURES
 *
 * EXTERNAL REFERENCES
 *
 * EXTERNAL FUNCTIONS
 *
*/

#define  INCL_DOSSEMAPHORES
#define  INCL_SPLERRORS
#include "inc\prdinclt.h"
#include <pmdev.h>
#include <pmspl.h>
#define  INCL_WINSHELLDATA
#include <pmshl.h>
#define  MB_ABORTRETRYIGNORE 3         /*            */
#define  MBID_IGNORE   5               /*            */
#define  MB_RETRYCANCEL 2           /* defined in pmwin.h, but running out */
#define  MBID_CANCEL   2               /* of near heap space. */
#define  MBID_RETRY    4
#define  MB_OK         0
#include "inc\utl.h"
#include "inc\prdspl.h"
#include "inc\prdgextf.h"
#include "inc\config.h"
#include "inc\pspagtun.h"             /* V2.174057  Page Tuning */
#define  INCL_GENPLIB_ERROR
#include <genplib.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

#define  OPEN_MODE     0x41            /* deny read/write access to others
                                       and to us access mode write only */
#if 0
extern HMTX tsemCOM1;
extern HMTX tsemCOM2;
extern HMTX tsemCOM3;
extern HMTX tsemLPT1;
extern HMTX tsemLPT2;
extern HMTX tsemLPT3;
#endif
extern HMODULE pscript_module;                 /* memory.c */
extern VOID PrtAbort(HFILE);
BOOL   InitializeCommPort(HFILE,PSZ);
void   R3_FileCheckDialogs(PDDC);
extern SHORT szLength(PSZ);   /*            */
BOOL   CheckComPortName(PSZ); /*            */
PSZ    AsciiToBin(PSZ,int,PSHORT);
USHORT CompressAscii85( PBYTE, PBYTE, USHORT, PDDC);  /* D74609 */
typedef struct
{
  SHORT BaudRate;
  UCHAR DataParityStop[3];
  struct
  {
    SHORT WriteTimeout;
    SHORT ReadTimeout;
    UCHAR Flags1;
    UCHAR Flags2;
    UCHAR Flags3;
    UCHAR ErrorReplacementCharacter;
    UCHAR BreakReplacementCharacter;
    UCHAR XONCharacter;
    UCHAR XOFFCharacter;
  } DeviceCtlBlk;

} COMMINFO;

typedef COMMINFO *PCOMMINFO;

static char szMajorKey[] = "PM_SPOOLER_PORT";

CHAR achEOF[] = "\r\n\004";
#define EOFSIZE 3;  /* Do not include the trailing zero */

/*            */
extern ULONG ULGreVersion;

/***************************************************************************
 *
 * FUNCTION NAME = AsciiToBin
 *
 * DESCRIPTION   = Convert numbers
 *
 * INPUT         = (psz,iDefault,pusDst)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = (psz)
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

PSZ AsciiToBin( PSZ psz, int iDefault, SHORT FAR *pusDst )
{
  register SHORT us = 0;

  /*
  ** If there are no digits, use the default value
  */
  if (!(*psz >= '0' && *psz <= '9'))
  {
    *pusDst = iDefault;
    return( psz );
  }

  /*
  ** Convert the decimal number to binary.
  */
  while (*psz >= '0' && *psz <= '9')
  {
    us = us *10+(*psz-'0');
    ++psz;
  }
  *pusDst = us;
  return( psz );
}

/***************************************************************************
 *
 * FUNCTION NAME = InitializeCommPort
 *
 * DESCRIPTION   = This function attempts to read the comm port settings from
 *                 the OS2.INI file and fills a private structure containing the
 *                 information it found.  It is assumed that the logical address
 *                 has been validated prior to calling this function, and that
 *                 is points to either COM1,COM2, or COM3.  FALSE is returned if
 *                 an error is detected.  Format of an OS2.INI entry for a COM
 *                 port is baud rate;parity;word length;stop bits;handshaking;
 *
 *                 This function initializes the comm port baud rate, stop bits,
 *                 handshaking, etc...  It is called from open_dos_pathway.  If
 *                 first determines if the logical is address is either COM1,
 *                 COM2, or COM3.  If this is true, then we call
 *                 get_ini_comm_info to fill a structure which contains the
 *                 information we need to initialize the comm port.  If we get a
 *                 good return from this function, we will attempt to set up the
 *                 port.
 *
 * INPUT         = (fh,pszLogAddress)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = (SUCCESS)
 *
 * RETURN-ERROR  = (FAILURE)
 *
 **************************************************************************/

BOOL InitializeCommPort( HFILE fh, PSZ pszLogAddress )
{
  char        szIni[32];
  PSZ         psz;
  COMMINFO    ci;
  SHORT       usData;

  /*
  ** Load the string from os2.ini
  */
  PrfQueryProfileString( HINI_SYSTEMPROFILE, (PSZ) szMajorKey,
                         (PSZ) pszLogAddress, (PSZ) ";;;;;", (PSZ) szIni,
                         (ULONG) sizeof( szIni ) );
  psz = (PSZ) szIni;

  /*
  ** Default to 1200 baud
  */
  psz = AsciiToBin( psz, 1200, &ci.BaudRate );

  if (*psz++ != ';')
  {
    goto ERROR;
  }

  /*
  ** Default to no parity
  */
  psz = AsciiToBin( psz, 0, &usData );

  if (*psz++ != ';')
  {
    goto ERROR;
  }

  ci.DataParityStop[1] = (UCHAR) usData;

  /*
  ** Word length: default to 8 bits/bytes
  */
  psz = AsciiToBin( psz, 8, &usData );

  if (*psz++ != ';')
  {
    goto ERROR;
  }

  ci.DataParityStop[0] = (UCHAR) usData;

  /*
  ** Number of stop bits
  */
  switch (*psz)
  {
  case ';':
       usData = 0;
       break;

  case '1':
       if (psz[1] == '.' && psz[2] == '5')
       {
         usData = 1;
         psz += 3;
       }
       else
       {
         usData = 0;
         psz += 1;
       }
       break;

  case '2':
       usData = 2;
       psz += 1;
       break;

  default:
       goto ERROR;
  }

  if (*psz++ != ';')
  {
    goto ERROR;
  }

  ci.DataParityStop[2] = (UCHAR) usData;

  /*
  ** Initialize the handshaking parameters and by calling
  ** PrtDevIOCtl and then modify them according to what's in
  ** os2.ini
  ** The output handshaking is controlled by bits 3, 4 and 5
  ** of the control block flags1, and bit 0 of flags2.
  ** If the next entry in the INI buffer is a semicolon or
  ** '0' then hardware handshaking is disabled.  If it is '1'
  ** '1', then hardware handshaking is enabled.
  */
  if (!PrtDevIOCtl((PVOID) &ci.DeviceCtlBlk, (PVOID) 0L, 0x73, 1, fh))
  {
    if (*psz == ';' || *psz == '0')
    {
      ci.DeviceCtlBlk.Flags1 &= 0xc7;
      ci.DeviceCtlBlk.Flags2 |= 0x01;
      ci.DeviceCtlBlk.XONCharacter = 17;
      ci.DeviceCtlBlk.XOFFCharacter = 19;
    }
    else
    {
      ci.DeviceCtlBlk.Flags1 = (ci.DeviceCtlBlk.Flags1&0xdf) | 0x18;
      ci.DeviceCtlBlk.Flags2 &= 0xfe;
    }
  }
  PrtDevIOCtl( (PVOID) 0L, (PVOID) &ci.BaudRate, 0x41, 1, fh);
  PrtDevIOCtl( (PVOID) 0L, (PVOID) &ci.DataParityStop[0], 0x42, 1, fh);
  PrtDevIOCtl( (PVOID) 0L, (PVOID) &ci.DeviceCtlBlk, 0x53, 1, fh);

  return( SUCCESS );

ERROR:

  return( FAILURE );
}

/***************************************************************************
 *
 * FUNCTION NAME = OpenChannel()
 *
 * DESCRIPTION   = Open an output I/O channel.
 *
 * INPUT         = (pddc)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = SUCCESS (TRUE) if successful
 *
 * RETURN-ERROR  = (FAILURE)
 *
 **************************************************************************/

BOOL OpenChannel( PDDC pddc )
  /* PDDC pddc;                            Ptr to the DC instance handle   */
{
  ULONG         DosAction;        /*            made to a long */
  LONG          lTemp;            /* Added             */
  BOOL          fSuccess;
  int           nParams;          /* The number of parameters in the
                                     DEVOPENSTRUC                    */
  PSZ FAR      *ppsz;
  register PCN  pcn;              /* Far ptr to the output channel   */
  int           i;
  PSZ           pszLogAddress;
/*  BOOL          fSemNeeded;   */    /* TRUE if semaphore needed */
/*  SHORT         usSemReturn; */
/*  HMTX          tsemWanted = 0; */
  PDV           pdv;
  /*            */
  CHAR          aErrorString[ 255 ];

  PrintLog( (PSZ) "OpenChannel: PDDC = %lp\n", pddc );

  /*
  ** Shorten direction
  */
  pdv = pddc->pdv;

  /*
  ** whether output goes direct to device or a file
  */
  if (pdv->iDestnType != SYSTEM)
  {
    if (pddc->iType == OD_QUEUED)
    {
      pddc->iType = OD_DIRECT;
    }

    if (pdv->szDestnFile[0] == '\0')
    {
      szCopy( (PSZ) pdv->szDestnFile, (PSZ) "ps.txt",
              sizeof (pdv->szDestnFile ) );
    }
    pdv->dop.pszLogAddress = (PSZ) pdv->szDestnFile;
  }
  pszLogAddress = pdv->dop.pszLogAddress;
  pcn = &pddc->pdv->cn;
  pcn->fChannelError = TRUE;
  pcn->fChannelIsValid = FALSE;

  /*            */
  WinLoadMessage( (HAB) 0, pscript_module, (SHORT) IDM_OfflineError,
                  sizeof( aErrorString ), aErrorString );

  /*
  ** fChannelOpen is TRUE when we try to open the channel, even if
  ** there is an error.  it is simply a flag to say we tried to
  ** open the channel.  ps_status will catch errors if any exist.
  */
  pcn->fChannelOpen = TRUE;

  switch ((SHORT) pddc->iType)
  {
  case OD_QUEUED:
       /*
       ** Find the index of the last valid pointer in the DEVOPENSTRC
       ** so that the parameter count can be passed to SplQmOpen.
       */
       ppsz = &pddc->pdv->dop.pszLogAddress;
       nParams = 0;

       for (i = 1; i <= (sizeof(pdv->dop)/4); ++i)
       {
         if (*ppsz++)
         {
           nParams = i;
         }
       }

       if (nParams < 4)
       {
         nParams = 4;
       }

       /*
       ** pcn->fh = (HSPL)SplQmOpen( (PSZ)"*",(LONG)nParams, (PQMOPENDATA)&pddc->pdv->dop );
       */

       pcn->fh = (ULONG)SplQmOpen( (PSZ)"*", (LONG)nParams, (PQMOPENDATA) &pddc->pdv->dop );

       if (pcn->fh == 0)                /* make sure we have valid handle    */
       {
         GplErrSetError(  PMERR_DOSOPEN_FAILURE );
         return( FALSE );
       }
       pcn->fChannelIsValid = TRUE;

       /*
       **           
       ** Start the async output.
       */
       if (pddc->pdv->usDataType == PM_Q_RAW)
       {
         GplThreadStart( pddc->pdv->hThread, pcn->fh, NULL, 0, 0, 0, 0, 0, 0,
                         achEOF, pszLogAddress, aErrorString,
                         pddc->pdv->dev_dop.DriveData.szDeviceName,
                         pddc->iType );
       }
       break;

  case OD_DIRECT:
       /*
       ** semaphore not needed.  prtwrite handles serialization.
       ** set up for semaphore protection.  it is only needed when
       ** we have a direct DC.
       */
       /*
       ** CR!!! rework the way an error can be passed back if the port is
       ** busy, perhaps through StartDoc
       */
#if 0
//     fSemNeeded = TRUE;
//
//     if (szIsEqual( pszLogAddress, (PSZ)"COM1" ))
//     {
//       tsemWanted = tsemCOM1;
//     }
//     else if (szIsEqual( pszLogAddress, (PSZ)"COM2" ))
//     {
//       tsemWanted = tsemCOM2;
//     }
//     else if (szIsEqual( pszLogAddress, (PSZ)"COM3" ))
//     {
//       tsemWanted = tsemCOM3;
//     }
//     else if (szIsEqual( pszLogAddress, (PSZ)"LPT1" ))
//     {
//       tsemWanted = tsemLPT1;
//     }
//     else if (szIsEqual( pszLogAddress, (PSZ)"LPT2" ))
//     {
//       tsemWanted = tsemLPT2;
//     }
//     else if (szIsEqual( pszLogAddress, (PSZ)"LPT3" ))
//     {
//       tsemWanted = tsemLPT3;
//     }
//     else                   /* we are going to a file */
//     {
//       fSemNeeded = FALSE;
//     }
//
//     /*
//     ** save semaphore handle.
//     */
//     pddc->ptsemPort = 0L;
//
//     if (fSemNeeded)
//     {
//       lTemp = DosRequestMutexSem( tsemWanted, SEM_IMMEDIATE_RETURN );
//
//       /*
//       ** if (lTemp)
//       ** {
//       **   RIP( "OpenChannel: com or lpt port in use; direct printing aborted");
//       **   pddc->ptsemPort = 0L;
//       **   GplErrSetError(  PMERR_PRN_ADDR_IN_USE);
//       **   return( FALSE );
//       ** }
//       */
//
//       pddc->ptsemPort = tsemWanted;
//     }
#endif

       /*
       ** if output is going to a file this section will check that the
       ** file is not being overwritten.  If the file is being
       ** overwritten a dialog box will appear, prompting the user
       ** to enter a new filename, overwrite, or cancel
       */
       if (pdv->iDestnType != SYSTEM)
       {
         pddc->fCancelOp = FALSE;
         R3_FileCheckDialogs( pddc );     /* fCancelOp will be TRUE if user */
                                          /* presses cancel buton */

         if (pddc->fCancelOp)           /* user aborted operation            */
         {
           GplErrSetError(  PMERR_DOSOPEN_FAILURE );
           return( FAILURE );
         }
         pdv->dop.pszLogAddress = (PSZ)pdv->szDestnFile;
         pszLogAddress = pdv->dop.pszLogAddress;
       }

       if (PrtOpen( (PSZ) pszLogAddress, (PHFILE) &pcn->fh,(PSHORT)&DosAction, 0L,
                    FILE_NORMAL, FILE_CREATE | FILE_TRUNCATE, OPEN_MODE, 0L ))
       {
         GplErrSetError(  PMERR_DOSOPEN_FAILURE );
         return( FALSE );
       }

       /*
       **           
       ** DosAction, used in PrtOpen above, will now contain a bit indicating
       ** if the port is through a LAN or not.  Clear the DIRECT bit if TRUE.
       ** The flag HANDTYPE_NETWORK is used for 2.2 or higher.
       */
       if ( ULGreVersion >= RASTER_ENGINE_22 )
       {
         if (DosAction & HANDTYPE_NETWORK)
         {
           CLEARFLAG( pdv->ulGenFlags, IS_DIRECT_PORT);
         }
       }

       /*
       **           
       ** Start the async thread.
       */
       GplThreadStart( pddc->pdv->hThread, NULL, pcn->fh, 0, 0, 0, 0, 0, 0,
                       achEOF, pszLogAddress, aErrorString,
                       pddc->pdv->dev_dop.DriveData.szDeviceName,
                       pddc->iType );

       /*            */
       /*
       ** Hard-coding of Port Names.
       ** Any port name starting with "COM" and
       ** appended with any integer >= 1
       ** will be a valid port name, viz. COM1, COM2, ...COM15561, ...
       */
       if (CheckComPortName( pszLogAddress ))
       {
         /*            */
         /*
         ** Ignore the return value of InitializeCommPort().
         */
         InitializeCommPort( (HFILE) pcn->fh, pszLogAddress );
       }
       pcn->fChannelIsValid = TRUE;
       break;

  default:
       pcn->fChannelIsValid = FALSE;
       break;
  }

  /*
  ** Prepare the channel for operation
  */
  pcn->nbBuf = 0;                      /* zero number of bytes in buffer    */
  pcn->fUsed = FALSE;                  /* mark the channel as clean         */
  pcn->fChannelError = FALSE;          /* No error on I/O channel           */

  /*            */
  /*
  ** Send Initialization string to printer forcing it into PS
  ** emulation mode if supported                           @1462 AND @728969
  */
  if ((SHORT)pddc->iType == OD_DIRECT)
  {
    /*           
    ** If this is direct to port other than file send header
    */
//  if ( CHECKFLAG( pdv->ulGenFlags, IS_DIRECT_PORT ))  Remove cause of Novel
//  {                                                   servers
      if (( pdv->usInitLength > 0)      &&
          (pdv->fInitStringSent == FALSE))
      {
        WriteModeString( pddc, pdv->szInitString, pdv->usInitLength );
        pdv->fInitStringSent = TRUE;
      }                                    /* end if                            */
//  }
  }
  return( SUCCESS );
}                                      /* end function                      */

/**            **/
/*****************************************************************************\
**
** FUNCTION NAME = DoAbort
**
** DESCRIPTION   = Writes out a ctl-D cr/lf to printer.  The channel is
**                 aborted and closed.
**
** INPUT         =  pddc
**
** OUTPUT        = NONE
**
** RETURN-NORMAL = NONE
**
** RETURN-ERROR  = NONE
**
\*****************************************************************************/

#if 0
VOID _Optlink DoAbort( PDDC pddc )
{
//PCN   pcn;
//ULONG ulWritten;
//
///*
//** force EOJ out to printer
//*/
///*
//**           
//** Abort now handled by GENPLIB.
//*/
//
//PrtWrite( (HFILE) pcn->fh, achEOF, pcn->nbBuf, &ulWritten);
//pcn->nbBuf = 0;
//PrtAbort( pcn->fh );
//PrtClose( pcn->fh );
//pddc->fDocWasAborted = FALSE;
//ndif
//pcn = &pddc->pdv->cn;
//pcn->nbBuf = EOFSIZE;
//
//if (pddc->pdv->hThread)
//{
//  GplThreadAbortDoc( pddc->pdv->hThread );
//  GplThreadResetAbortDoc( pddc->pdv->hThread );
//  GplThreadEnd( pddc->pdv->hThread );
//}
//
///*
//** This may seem strange, but there have been some occurances where the
//** term character is not sent to the device.
//** Until the driver is modified to better work with GENPLIB, as a safety
//** measure, write the term character.
//*/
//PrtWrite( (HFILE) pcn->fh, achEOF, pcn->nbBuf, &ulWritten);
//
//PrtClose( pddc->pdv->cn.fh );
//
//if (pddc->pdv->hThread)
//{
//  GplThreadDeleteInstance( &pddc->pdv->hThread );
//}
//pddc->pdv->hThread = 0;
//pddc->pdv->cn.fh = 0;
//pddc->fDocWasAborted = FALSE;
//
//return;
}
#endif


/***************************************************************************
 *
 * FUNCTION NAME = WriteChannel()
 *
 * DESCRIPTION   = Send output data to the spooler or directly to
 *                 the output port depending on the state of the
 *                 spool flag.
 *
 * INPUT         = (pddc,pbSrc,nbSrc)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

void WriteChannel( PDDC pddc, PB pbSrc, USHORT nbSrc )
  /*  PDDC pddc;                            Far pointer to the DC instance  */
  /*  PB pbSrc;                             Far ptr to the output buffer to
  /*                                        send                            */
  /*  USHORT nbSrc;                        Size of the output buffer        */
  /*            */
{
  PCN            pcn;                   /* Far ptr to the channel descriptor */
  register SHORT nbFree;
  register PB    pbDst;

  #if      DEBUG
    LogCall( "WriteChannel(%lp, %lp, %d)", ((PB) &pddc) + sizeof(pddc) );
  #endif

  pcn = &pddc->pdv->cn;

  /*
  ** if we have just endded a document, do nothing until we have
  ** started another document.
  */
  if (pddc->fEOJSent)
  {
    return;
  }


  /*
  ** If the output channel is not valid, then fail
  */
  if (!pcn->fChannelIsValid)
  {
    return;
  }

#if 0
//if ( pddc->fDocWasAborted )
//{
//  DoAbort( pddc );        /*            */
//  return;
//}
#endif

  /*
  ** a DEVESC_STARTDOC call must come before any code is generated.
  */
  if (!pddc->pdv->fDocStarted)
  {
    return;
  }

  #ifdef   POLITICS
    if ((pddc->iType == OD_QUEUED) && (!pddc->pdv->fDocStarted))
    {
      return;
    }
  #endif

  /*
  ** Loop once for each byte and stuff each byte into the output
  ** buffer.  When the buffer becomes full, call FlushChannel to
  ** empty it.
  */
  while (nbSrc > 0)
  {
    /*
    ** Mark the channel as "used" if there's something to write
    */
    pcn->fUsed = TRUE;
    pbDst = ((PB)pcn->abBuf)+pcn->nbBuf;
/** nbFree = sizeof(pcn->abBuf)-pcn->nbBuf; **/
    nbFree = ABBUF_SIZE - pcn->nbBuf;

    if (nbSrc < nbFree)
    {
      /*
      ** Control comes here if the amount of data to write
      ** will fit in the channel buffer.
      */
      pcn->nbBuf += nbSrc;

      while (nbSrc-- > 0)
      {
        *pbDst++ = *pbSrc++;
      }
      return;
    }

    /*
    ** Control comes here if there isn't enough room in
    ** the channel buffer for the entire write.  The buffer
    ** is guranteed to be filled up so a FlushChannel call
    ** is done after the buffer copy.
    */
    nbSrc -= nbFree;

    while (nbFree-- > 0)
    {
      *pbDst++ = *pbSrc++;
    }
/** pcn->nbBuf = sizeof( pcn->abBuf ); **/
    pcn->nbBuf = ABBUF_SIZE;
    FlushChannel( pddc );
  }
}

/***************************************************************************
 *
 * FUNCTION NAME = FlushChannel()
 *
 *
 * DESCRIPTION   = Flush the buffered output data through the output
 *                 channel.  If any errors occur, the output channel
 *                 is closed.
 *
 *
 * INPUT         = (pddc)
 *
 *
 * OUTPUT        = NONE
 *
 *
 *
 *
 * RETURN-NORMAL = NONE
 *
 *
 * RETURN-ERROR  = NONE
 *
 *
 *
 **************************************************************************/

void FlushChannel( PDDC pddc )
  /* PDDC pddc;                            Ptr to the DC instance data       */

{
  int         nbWritten;               /* The number of bytes actually
                                          written                           */
  BOOL        fSuccess;                /* TRUE if flush channel is
                                          successful                        */
  PCN         pcn;
  SHORT       PrtResult;               /* ** jean **                        */
  CHAR       *pBuffer;
  LONG        nOut;                    /* D74609 */

  pcn = &pddc->pdv->cn;
  PrintLog( (PSZ) "\nFlushChannel:  pcn->nbBuf = %d\n", pcn->nbBuf );
  PrintLog( (PSZ) "   pcn->fChannelIsValid = %d\n", pcn->fChannelIsValid );

  /*
  **            If standard then we can return
  */
  if ( pddc->fEOJSent ||
       !pcn->fChannelIsValid ||
       (pddc->pdv->usDataType == PM_Q_STD) )
  {
    pcn->nbBuf = 0;
    return;
  }

  /*
  ** Assume that FlushChannel is succesful
  */
  fSuccess = TRUE;

  /*
  ** if there is data in buffer, write it out
  */
  if (pcn->nbBuf > 0)
  {
    switch ((SHORT)pddc->iType)
    {
    case OD_QUEUED:
         PrintLog( (PSZ)"SplQmWrite(%lp, %ld, %lp)\n", (LONG)pcn->fh,
                   (LONG) pcn->nbBuf, (PB)pcn->abBuf);

         /*
         ** write data in buffer out to spooler file
         */
         if ( pddc->pdv->Compress85 )   /* Do CompressAscii85 D74609 */
         {
           nOut = CompressAscii85((PB)pcn->abBuf, pddc->pdv->Compress85, pcn->nbBuf, pddc );
           fSuccess = SplQmWrite( (HSPL)pcn->fh, (LONG)nOut, pddc->pdv->Compress85 );
         }
         else
         {
           fSuccess = SplQmWrite( (HSPL)pcn->fh, (LONG)pcn->nbBuf, (PB)pcn->abBuf );
         }
         break;

    case OD_DIRECT:
         PrintLog( (PSZ)"PrtWrite(%lp, %ld, %lp)\n", (LONG)pcn->fh,
                   (LONG) pcn->nbBuf, (PB)pcn->abBuf);

         if ( pddc->pdv->Compress85 )   /* Do CompressAscii85  D74609 */
         {
           nOut = CompressAscii85((PB)pcn->abBuf, pddc->pdv->Compress85, pcn->nbBuf, pddc );
           pcn->nbBuf = nOut;    // number of bytes that should be written

           /*
           **           
           ** Async output - bitmap compression.
           */
#if 0
//         PrtResult = PrtWrite( (HFILE)pcn->fh, pddc->pdv->Compress85,
//                                            pcn->nbBuf, (PSHORT) &nbWritten);
#endif
           if (pddc->pdv->hThread)
           {
             GplThreadOutput( pddc->pdv->hThread, pddc->pdv->Compress85,
                              pcn->nbBuf, THREAD_DT_BINARY );

           }
         }
         else
         {
           /*
           ** write the bytes from our print buffer to the output channel
           */
           /*
           **           
           ** Async output - non bitmap compression.
           */
#if 0
//         PrtResult = PrtWrite( (HFILE)pcn->fh, pcn->abBuf, pcn->nbBuf,
//                               (PSHORT) &nbWritten);
#endif
           if (pddc->pdv->hThread)
           {
             GplThreadOutput( pddc->pdv->hThread, pcn->abBuf,
                              pcn->nbBuf, THREAD_DT_PRINTERLANGUAGE );

           }
         }

         /*
         ** check to see if someone has aborted the print job before we
         ** continue.  if they have, then send the EOJ marker to the
         ** printer, tell the spooler the job is aborted, then close
         ** the output channel.
         */
#if 0    /*            comment out */
//       if (pddc->fDocWasAborted == TRUE)
//       {
//         /*
//         ** force EOJ out to printer
//         */
//         DoAbort( pddc );
//
//         if (pddc->ptsemPort)
//         {
//           DosReleaseMutexSem( pddc->ptsemPort );
//         }
//
//         fSuccess = FALSE;
//         break;
//       }
#endif

         /*
         ** assume success
         */
         fSuccess = TRUE;

         /*
         ** if the number of bytes written is not equal to the number
         ** of requested bytes, there must have been a problem
         ** we need to test for a     rc on PrtWrite
         */
         /*
         **           
         ** Error handling now perfomed by GENPLIB.
         */
#if 0
//       if (PrtResult || (nbWritten != pcn->nbBuf))    /* ** jean ** */
//       {
//         if ( pddc->pdv->Compress85 )   /* D74609 Needed for */
//           pBuffer = pddc->pdv->Compress85;   /* paper out        */
//         else
//           pBuffer = pcn->abBuf;
//
//         while (PrtResult || (nbWritten != pcn->nbBuf))   /* *jean* */
//         {
//           SHORT usRet;
//
//           /*
//           ** display a message box to the user, then act appropriately
//           */
//           if (pddc->pdv->iDestnType == SYSTEM)
//           {
//             usRet = SplMessageBox( (PSZ)pddc->pdv->dop.pszLogAddress,
//                     ( SPLINFO_DDERROR | SPLINFO_ERROR | SPLINFO_USERINTREQD ),
//                     SPLDATA_PRINTERJAM, NULL, NULL, 0, MB_ABORTRETRYIGNORE );
//
//             /*
//             ** jsj MB_ message changed from
//             ** MB_RETRYCANCEL to
//             ** MB_ABORTRETRYIGNORE also process
//             */
//             /*            */
//           }
//           else
//           {
//             CHAR szMsg[MAX_MESSAGE_SIZE];
//
//             WinLoadMessage( (HAB) NULL, pscript_module, (SHORT) IDM_FileWriteError,
//                             sizeof( szMsg ), szMsg );
//             usRet = SplMessageBox( (PSZ) pddc->pdv->dop.pszLogAddress,
//                     ( SPLINFO_DDERROR | SPLINFO_ERROR | SPLINFO_USERINTREQD ),
//                     SPLDATA_UNEXPECTERROR, szMsg, NULL, 0, MB_ABORTRETRYIGNORE );
//                                       /* jsj MB_ message changed from
//                                       ** MB_RETRYCANCEL to
//                                       ** MB_ABORTRETRYIGNORE also process
//                                       */
//           }
//
//           if ((usRet == MBID_RETRY) || (usRet == MBID_IGNORE)) /*            */
//           {
//             /*
//             ** check to see if someone has aborted the print job before we
//             ** continue.  if they have, then send the EOJ marker to the
//             ** printer, tell the spooler the job is aborted, then close
//             ** the output channel.
//             */
//             if (pddc->fDocWasAborted == TRUE)
//             {
//               /*
//               ** force EOJ out to printer
//               */
//               DoAbort( pddc );     /*            */
//               fSuccess = FALSE;
//               break;
//             }
//             else
//             {
//               /*
//               ** try to recover from the error
//               */
//               pBuffer += nbWritten;
//               pcn->nbBuf -= nbWritten;
//               if ( pddc->pdv->Compress85 )     /* D74609 */
//               {
//                 PrtResult = PrtWrite( (HFILE)pcn->fh, pddc->pdv->Compress85, pcn->nbBuf,
//                                                        (PSHORT) &nbWritten);
//               }
//               else
//               {
//                 PrtResult = PrtWrite( (HFILE)pcn->fh, pBuffer, pcn->nbBuf,
//                                                   (PSHORT) &nbWritten);
//               }
//             }
//           }
//           else
//           {
//             /*
//             ** time to bag out.  close the output channel,
//             ** then free up the semaphore holding it.
//             */
//             DoAbort( pddc );     /*            */
//
//             if (pddc->ptsemPort)
//             {
//               DosReleaseMutexSem( pddc->ptsemPort );
//             }
//
//             fSuccess = FALSE;
//             break;
//           }
//         }
//       }
#endif
         break;

    case OD_INFO :
         fSuccess = TRUE;
         break;
    }
  }

  /*
  ** If there was an error, close the file and mark it as closed
  */
  if (!fSuccess)
  {
    PrintLog( (PSZ) "FlushChannel failed\n" );
    PrintLog( (PSZ) "fhChannel = %lp\n", pcn->fh );
    PrintLog( (PSZ)"FlushChannel: Byte count mismatch\n" );
    PrintLog( (PSZ)"              nbBuf = %ld\n", (long)pcn->nbBuf );
    PrintLog( (PSZ)"              nbWritten = %ld\n", (long)nbWritten );

    /*
    ** if we are printing to an OD_QUEUED DC, close the file.  if we were
    ** printing OD_DIRECT and there was an error, the PrtClose will have
    ** already been done.
    */
    if (pddc->iType == OD_QUEUED)
    {
      PrintLog( (PSZ)"SplQmClose(%lp)\n", (LONG)pcn->fh );
      SplQmClose( (HSPL)pcn->fh );
    }
    pcn->fChannelIsValid = FALSE;
    pcn->fChannelError = TRUE;
  }
  pcn->nbBuf = 0;
}

/***************************************************************************
 *
 * FUNCTION NAME =  Cvi_R2
 *
 * DESCRIPTION   =  Converts a string of ascii digits to integer value.  The
 *                  string is in HEX
 *
 * INPUT         = *pint
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = (i)
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

SHORT Cvi_R2( PB *pint )
{
  register SHORT i = 0;
  register CHAR  cDigit;

  cDigit = **pint;

  while (1)
  {
    if (cDigit >= '0' && cDigit <= '9')
    {
      cDigit -= '0';
    }
    else if (cDigit >= 'a' && cDigit <= 'f')
    {
      cDigit -= ('a' - 10);
    }
      else if (cDigit >= 'A' && cDigit <= 'F')
    {
      cDigit -= ('A' - 10);
    }
    else
    {
      break;
    }

    i = i * 16 + cDigit;
    *pint += sizeof( BYTE );
    cDigit = (CHAR) **pint;
  }

  return( i );
}

/***************************************************************************
 *
 * FUNCTION NAME = WriteModeString
 *
 * DESCRIPTION   = Will write out the "InitPostScriptMode" and
 *                 "TermPostScriptMode" strings that are in the PPD file.  The
 *                 string can be the actual characters and escape sequences.
 *                 The escape sequence is in the form of:
 *
 *                                              <value count>
 *                 where it starts with a less than sign < followed immediately
 *                 by the value in hex (eg ESC = 1B), one space and an optional
 *                 repeat count in HEX followed immediately by a closing greater
 *                 than sign.
 *
 *                 This code does not do any error checking since it's in a
 *                 "trusted" PPD file
 *
 *                 For example the string 1b 5b 4b 10 20 00 00 00 can be entered
 *                 as:
 *
 *                 <1b>[K<10> <0 3>
 *
 * INPUT         = (pddc,pbSrc,nbSrc)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

VOID WriteModeString( PDDC pddc, PB pbSrc, SHORT nbSrc )
  /* PDDC pddc;                            Far pointer to the DC instance */
  /* PB pbSrc;                             Far ptr to the output buffer to send */
  /* SHORT nbSrc;                         Size of the output buffer */
{
  CHAR           buffer[256];
  register SHORT i;                   /* Number of bytes available */
  register CHAR  cElement;
  SHORT          sCount;
  SHORT          j;

  i = 0;

  /*
  ** For each character in the string...
  */
  while ((cElement = *pbSrc) > 0)
  {
    /*
    ** If the the character is not the "marker" copy to buffer
    */
    if (cElement != '<')
    {
      buffer[i++] = cElement;
    }
    else                               /* Marker indicates escape seq */
    {                                  /* Get the value of the character */
      *pbSrc++;                        /* Move past marker */
      buffer[i++] = cElement = (CHAR)Cvi_R2( (PB *) &pbSrc );

      if (*pbSrc != '>')            /* Then there must be a following count */
      {
        pbSrc++;                       /* Hop over the space */
        sCount = Cvi_R2( (PB *)&pbSrc) - 1; /* Get the repeat count minus */

        for (j = 0; j < sCount; j++)   /* Write in count number of chars */
        {                              /* count of 0 is same as 1 */
          /*
          ** If the buffer is full then write it out
          */
          if (i == sizeof( buffer))
          {
            WriteChannel( pddc, (PB)buffer, sizeof( buffer) );
            i = 0;
          }
          buffer[i++] = cElement;
        }
      }
    }

    /*
    ** If the buffer is full then write it out
    */
    if (i == sizeof( buffer ))
    {
      WriteChannel( pddc, (PB)buffer, sizeof( buffer ) );
      i = 0;
    }
    pbSrc++;                           /* On to next char */
  }

  /*
  ** Write out any chars left in buffer
  */
  if (i)
  {
    WriteChannel( pddc, (PB)buffer, i );
  }

  return;
}

/***************************************************************************
 *
 * FUNCTION NAME =  HexToChannel
 *
 * DESCRIPTION   =  This function takes a character string and
 *                  converts each character to its hex equivalent
 *                  then outputs it to the output buffer for the DC
 *                  instance passed in.
 *
 *                  eg.  Formatted hex string = "A0z"
 *                       so we would output 6 bytes "41","30","7a"
 *                       of hex data to the output buffer.
 *
 *
 * INPUT         = (pddc,pbSrc,nbSrc)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

VOID HexToChannel( PDDC pddc, PB pbSrc, register SHORT nbSrc )
 /* PDDC pddc;                            pointer to the DC instance */
 /* PB pbSrc;                             ptr to the output buffer to send */
 /* register SHORT nbSrc;                Size of the output buffer */
{

  PCN            pcn;                 /* ptr to the channel descriptor */
  register SHORT nwFree;              /* Number of 16 bit words available */
  register PB    pbDst;
  int            b1, b2;

  /*
  ** a DEVESC_STARTDOC must be done before any output goes to the
  ** printer.
  */
  if (!pddc->pdv->fDocStarted)
  {
    return;
  }

  /*
  ** If the PostScript header wasn't sent then send it now.  The
  ** reason that it was delayed till now is to handle the case
  ** where the spooler is just copying raw data to the driver.
  ** If this is the case, then the driver must be careful not
  ** to send multiple headers.
  */
  if (!pddc->fHeaderSent)
  {
    ps_startdoc( pddc );
  }
  pcn = &pddc->pdv->cn;

  /*
  ** If the output channel is not valid, then fail
  */
  if (!pcn->fChannelIsValid)
  {
    return;
  }

  /*
  ** Loop once for each byte and stuff each byte into the output
  ** buffer.  When the buffer becomes full, call FlushChannel to
  ** empty it.
  */
  while (nbSrc > 0)
  {
    /*
    ** Mark the channel as "used" if there's something to write
    */
    pcn->fUsed = TRUE;
    pbDst = ((PB)pcn->abBuf) + pcn->nbBuf;
/** nwFree = (sizeof( pcn->abBuf) - pcn->nbBuf) / 2; **/
    nwFree = (ABBUF_SIZE - pcn->nbBuf) / 2;

    if (nbSrc < nwFree)
    {
      /*
      ** Control comes here if the amount of data to write
      ** will fit in the channel buffer.
      */
      pcn->nbBuf += nbSrc *2;

      while (nbSrc-- > 0)
      {
        /*
        ** Get a byte and convert it to two hex digits
        */
        b1 = *pbSrc++;
        b2 = (b1 >> 4) & 0x0f;
        b1 &= 0x0f;

        if (b2 >= 10)
        {
          b2 = b2+('A'-10 );
        }
        else
        {
          b2 = b2+'0';
        }

        if (b1 >= 10)
        {
          b1 = b1+('A'-10 );
        }
        else
        {
          b1 = b1+'0';
        }
        *pbDst++ = (char)b2;
        *pbDst++ = (char)b1;
      }                                /* end while                         */
      return;
    }                                  /* end if                            */

    /*
    ** Control comes here if there isn't enough room in
    ** the channel buffer for the entire write.  The buffer
    ** is guranteed to be filled up so a FlushChannel call
    ** is done after the buffer copy.
    */
    nbSrc -= nwFree;
    pcn->nbBuf += nwFree *2;

    while (nwFree-- > 0)
    {
      /*
      ** Get a byte and convert it to two hex digits
      */
      b1 = *pbSrc++;
      b2 = (b1 >> 4) & 0x0f;
      b1 &= 0x0f;

      if (b2 >= 10)
      {
        b2 = b2 + ('A' - 10 );
      }
      else
      {
        b2 = b2+'0';
      }

      if (b1 >= 10)
      {
        b1 = b1 + ('A' - 10 );
      }
      else
      {
        b1 = b1 + '0';
      }
      *pbDst++ = (char) b2;
      *pbDst++ = (char) b1;
    }                                  /* end while                         */

    /*
    ** Buffer is full so flush it out
    */
    FlushChannel( pddc );
  }                                    /* end while                         */
}                                      /* end procedure                     */

/***************************************************************************
 *
 * FUNCTION NAME = CloseChannel( )
 *
 * DESCRIPTION   = This routine closes the printer's output channel.
 *                 Note that the output channel could be connected
 *                 to the spooler, or directly to the output port.
 *
 * INPUT         = (hdc,pddc)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = (fSuccess)
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

BOOL CloseChannel( HDC hdc, PDDC pddc )
 /* PDDC pddc;                            ptr to the device context     */
{
  BOOL        fSuccess;
  PCN         pcn;                   /* pointer to channel descriptor     */
/*  SHORT       usSemIndex; */
  PSZ         pszLogAddress;
  int         nbWritten;               

  PrintLog( (PSZ) "CloseChannel: PDDC = %lp\n", pddc );

  /*
  ** @V3.0100101
  ** If the channel is not open, this function should not be used.
  ** Return TRUE.
  */
  if ( ! pddc->pdv->cn.fChannelOpen )
  {
    return( TRUE );
  }

#if 0
//if (!pddc->pdv->cn.fChannelIsValid)
//{
//  if (pddc->iType == OD_DIRECT)
//  {
//    if (pddc->pdv->iDestnType != ENCAPS)
//    {
//      PrintChannel( pddc, (PSZ) "%c", 4 );
//    }
//  }
//  return( TRUE );
//}
#endif

  #ifdef   POLITICS
    /*
    ** Make sure to generate the matching code for ps_startdoc
    */
    ps_enddoc( hdc, pddc );
  #else

    /*
    ** DEVESC_STARTDOC will set fDocStarted = TRUE.  DEVESC_ENDDOC will
    ** set it to FALSE.  If it is TRUE at this point, it means that a
    ** STARTDOC was called to start this document, but an ENDDOC was not
    ** called to end it.
    */
    if (pddc->pdv->fDocStarted == TRUE)
    {
      RIP( "DEVESC_ENDDOC was not called to end this document." );
      ps_enddoc( hdc, pddc );
    }
  #endif

  pcn = &pddc->pdv->cn;
  fSuccess = TRUE;

  /*
  **           
  ** Close the async channel.
  */
  if (pddc->pdv->hThread)
  {
    GplThreadEnd( pddc->pdv->hThread );
  }

  if (pddc->pdv)                       /* make sure we have a valid device
                                          descriptor                        */
  {
    switch ((SHORT)pddc->iType)
    {
    case OD_QUEUED:
         if (pddc->pdv->cn.fChannelIsValid)
         {
           fSuccess = SplQmClose( (HSPL)pddc->pdv->cn.fh );
         }
         else
         {
           fSuccess = FALSE;
         }

         /*
         **            If we have been spooling standard then close the buffer
         */
         if ( pddc->pdv->usDataType == PM_Q_STD )
         {
           SplStdClose( hdc );
         }
         break;

    case OD_DIRECT:
         /*
         **  PTR OSDD B734518 - send C-D to reset printer
         */
         /*
         **  send C-D to reset printer
         */
#if 0
//       if (pddc->fDocWasAborted == TRUE)
//       {
//         DoAbort( pddc );     /*            */
//       }
//       else if (pddc->pdv->cn.fh)
//       {
#endif
           fSuccess = !PrtClose( pddc->pdv->cn.fh );
/*         } */
         break;

    case OD_INFO:
         fSuccess = TRUE;
         break;
    }
    pddc->pdv->cn.fChannelIsValid = FALSE;
    pddc->pdv->cn.fChannelOpen = FALSE;
  }

#if 0
//if (pddc->iType == OD_DIRECT && pddc->ptsemPort)
//{
// DosReleaseMutexSem( pddc->ptsemPort );
//}
#endif

  if (!fSuccess)
  {
    GplErrSetError(  PMERR_BASE_ERROR );
  }
  return( fSuccess );
}

/***************************************************************************
 *
 * FUNCTION NAME = PrintChannel()
 *
 * DESCRIPTION   = This routine sends formatted output to the
 *                 output channel.  A printf-style format string
 *                 specifies the format of the output.
 *
 * INPUT         = (pddc,pszFormat)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

void PrintChannel( PDDC pddc, PSZ pszFormat,... )
  /* PDDC pddc;                            Far ptr to the DC instance        */
  /* PSZ pszFormat;                        Near ptr to the format string     */
{
  register int nb;                     /* Number of bytes deposited by
                                          kprintf                           */
  register PCN pcn;                    /* Far ptr to the channel descriptor */
  register int i;

  va_list argPtr;
  LONG    lPercentCount = 0;
  PSZ     pszPercent;
  LONG    laArgs[16];

  /*
  ** Do nothing if this is an InfoContext DC.
  */
  if ( (pddc->iType == OD_INFO) ||
       (pddc->pdv->usDataType == PM_Q_STD) )
  {
    return;
  }

#if 0
//if ( pddc->fDocWasAborted )
//{
//  DoAbort( pddc );    /*            */
//  return;
//}
#endif

  /*
  ** a DEVESC_STARTDOC call must come before any code is generated.
  */
  if (!pddc->pdv->fDocStarted)
  {
    return;
  }

  /*
  ** If the PostScript header wasn't sent then send it now.  The
  ** reason that it was delayed till now is to handle the case
  ** where the spooler is just copying raw data to the driver.
  ** If this is the case, then the driver must be careful not
  ** to send multiple headers.
  */
  if (!pddc->fHeaderSent)
  {
    ps_startdoc( pddc );
  }
  pcn = &pddc->pdv->cn;

  /*
  ** @V3.0106550
  ** Fix for powerPC
  ** Can't access stack directly in PPC so count number of percents then copy
  ** that number of LONGs int array.  We that the largest value on stack is
  ** is a LONG so we won't be short
  */
  pszPercent = pszFormat;
  while ( pszPercent = strchr( pszPercent, '%' ) )
  {
    lPercentCount++;
    pszPercent++;
  }

  if ( lPercentCount )    /* load array */
  {
    va_start( argPtr, pszFormat );

    /*
    ** For each % copy 4 bytes (LONG) in to stack
    */
    for ( i = 0; i < lPercentCount; i++ )
    {
      laArgs[i] = va_arg( argPtr, LONG );
    }

    va_end( argPtr );

  }
  /* end of @V3.0106550 additions */

  nb = kprintf( FALSE, (PB)pcn->abPrintf, sizeof(pcn->abPrintf), pszFormat,
                (ULONG *)&laArgs );

  if (pddc->pddcb->path.fPathIsOpen)
  {
    if ((LONG)nb >= pddc->pddcb->lPathBufCount)
    {
       /* int3() */  ;
       return ;
    }

    pddc->pddcb->lPathBufCount -= (LONG) nb;

    for (i = 0; i < nb; i++)
    {
      *pddc->pddcb->lpPathBuf++ = pcn->abPrintf[i];
    }
  }
  else
  {
    WriteChannel( pddc, (PB)pcn->abPrintf, nb );
  }
}
/*            */

/***************************************************************************
 *
 * FUNCTION NAME = CheckComPortName ()
 *
 * DESCRIPTION   = This routine checks if the passed COM port is any name
 *                 starting with "COM" and appended with any integer >= 1.
 *
 * INPUT         = (pPortName)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = TRUE for valid name otherwise FALSE.
 *
 * RETURN-ERROR  = FALSE.
 *
 **************************************************************************/

BOOL CheckComPortName( PSZ pPortName )
{
  BOOL  bRetCode = FALSE;
  SHORT usLen;
  CHAR  szTemp[4];

  /*
  ** Check the physical port name.
  */
  /*            */
  /*
  ** Hard-coding of Port Names.
  ** Any port name starting with "COM" and appended with any integer >= 1
  ** will be a valid port name, viz. COM1, COM2, ...COM15561, ...
  */
  usLen = szLength(pPortName) - 1;   /* szLength in fonts.c returns the */
                                     /* length including the treminating NULL. */

  /*
  ** Port name should start with "COM" and
  ** appended with atleast one integer.
  */
  if (usLen > 3)
  {
    szTemp[0] = *pPortName++;
    szTemp[1] = *pPortName++;
    szTemp[2] = *pPortName++;
    szTemp[3] = '\0';

    /*
    ** Port name should start with "COM".
    */
    if (szIsEqual((PSZ) szTemp, (PSZ) "COM"))
    {

      /*
      ** The first integer should >= 1. Zero is not allowed.
      */

      if (*pPortName >= '1' && *pPortName++ <= '9')
      {
        bRetCode = TRUE;

        /*
        ** The second through last integer.
        */

        for (usLen -= 4; usLen > 0; --usLen)
        {

          if (*pPortName < '0' || *pPortName++ > '9')
          {
            bRetCode = FALSE;
            break;
          }
        }
      }
    }
  }
  return( bRetCode );
}
