/*
 *  $Log: netware.cpp,v $
 *  Revision 1.4  1998/06/26 08:39:21  cyruspatel
 *  Modified netware.cpp to use new GetTimesliceBaseline() function. Added
 *  netware.h to repository.
 *
 *  Revision 1.3  1998/06/16 09:36:14  cyruspatel
 *  Added $Log support to hbyname.cpp and netware.cpp. Also adjusted
 *  netware.cpp to assign an idle callback to only the first crunch thread.
 *
 *  Revision 1.2  1998/06/09 08:54:38  jlawson
 *  Posted Cyrus' changes to cvs tree
 * 
 *  Revision 1.1  1998/05/25 21:34:24  bovine
 *  Imported Cyrus' NetWare standalone code into the cvs tree
 * 
*/

#if !defined(lint)
static const char *id="@(#)$Id: netware.cpp,v 1.4 1998/06/26 08:39:21 cyruspatel Exp $";
#endif

//  Originally intended to contain NetWare specific stuff so that
//  the common code wouldn't have large NetWare specific sections.

#include "client.h"
#include "netware.h"

#define MAX_IDLELOOP_CALLBACKS (1) // (MAXCPUS)
//#define NOAUTODESTROYSCREEN

/* ------------------------------------------------------------------ */


#ifdef __WATCOMC__
  #define x86ident _x86ident
#endif
extern "C" u32 x86ident( void );

int nwCliInitLoadLevelControl(void)
{
#if 0
  int cputype, cpuspeed, vendorid, cpuidb, cpuclass;
  
  ThreadSwitch();
  cpuspeed = x86cpuspeedtcs(); //burns 3 ticks if tcs supported 
  if (cpuspeed > 0) //only switch again if we actually burned some time
    ThreadSwitch();
  else
    cpuspeed = 66;  //anyone running NetWare < 66Mhz needs to be punished :)
  
  cputype  = x86ident();
  vendorid = (int)(cputype >> 16);
  cpuidb   = (int)(cputype & 0xfff0); // last 4 bits are stepping info

  if ( cpuidb == 0x30 )
    cpuclass = 3;
  if ( cpuidb < 0x500 ) //486 class 
    cpuclass = 4;
  else if (( cpuidb < 0x600 ) || ( vendorid != 0x6547 )) //|| !intelcpu
    cpuclass = 5;
  else 
    cpuclass = 6; //PPro and PII
  
  ConsolePrintf("\r\n~%d Mhz (%d class CPU, vendID = 0x%04x)\r\n", 
                           cpuspeed, cpuclass, vendorid );
#endif
  return 0;
}

/* ------------------------------------------------------------------ */

#define DebugPrintf(x) //ConsolePrintf(x)
#define DebugPrintf2(x,y) //ConsolePrintf(x,y)
#define DebugPrintf3(x,y,z) //ConsolePrintf(x,y,z)
#define DebugPrintf4(x,y,z,a) //ConsolePrintf(x,y,z,a)

/* ------------------------------------------------------------------ */

extern "C" void *ImportPublicSymbol( int nlmHandle, char *lname );
extern "C" int UnImportPublicSymbol( int nlmHandle, char *lname );

static void *ImportSymbol( int nlmHandle, char *name )
{
  char buf[64]; 
  if (!name || !*name || (((buf[0]=strlen(name))+1) >= sizeof(buf)) )
    return NULL;
  memcpy( buf+1, name, buf[0] );
  return ImportPublicSymbol( nlmHandle, buf );
}  
  
static int UnimportSymbol( int nlmHandle, char *name )
{
  char buf[64]; 
  if (!name || !*name || (((buf[0]=strlen(name))+1) >= sizeof(buf)) )
    return NULL;
  memcpy( buf+1, name, buf[0] );
  return (int)UnImportPublicSymbol( nlmHandle, buf );
}

/* ------------------------------------------------------------------ */

static __inline int __PushPopInt( int *x, int y ) { int i=*x; *x=y; return i; }

static int _cliNLMHandle = NULL;
static int CliRetrieveNLMHandle(void) { return _cliNLMHandle; }
static int CliSaveNLMHandle(int h) {return __PushPopInt( &_cliNLMHandle, h); }

/* ------------------------------------------------------------------------ */

static Client *_clientPtr = NULL;
static Client *CliGetClientPointer(void) { return _clientPtr; }
static void CliSetClientPointer(Client *cli) { _clientPtr = cli; return; }

/* ------------------------------------------------------------------------ */

static int _cliIsPollingProhibitedFlag = 0;
static int CliGetIsPollingProhibitedFlag(void) { return _cliIsPollingProhibitedFlag; }
static int CliSetIsPollingProhibitedFlag(int f) { return __PushPopInt( &_cliIsPollingProhibitedFlag, f ); }

/* ------------------------------------------------------------------------ */

static int _cliFileWasAccessedFlag = 0;
static int CliSetFileWasAccessedFlag(void) { _cliFileWasAccessedFlag=1; return 0; }
static int CliGetFileWasAccessedFlag(void) { return __PushPopInt( &_cliFileWasAccessedFlag, 0 ); }

/* ------------------------------------------------------------------------ */

static int _cliMainThreadID = 0; //threadid
static int CliGetMainThreadID(void)  { return _cliMainThreadID; }
static int CliSetMainThreadID(int id)  { return __PushPopInt( &_cliMainThreadID, id ); }

/* ------------------------------------------------------------------------ */

static int cliIsClientRunningFlag = 0;
int CliIsClientRunning(void) { return cliIsClientRunningFlag; }

int CliIsClientRunning(int newState)
{
  int oldState = cliIsClientRunningFlag;
  cliIsClientRunningFlag = newState;
  return( oldState );
}

/* ------------------------------------------------------------------ */

extern "C" unsigned int GetFileServerMajorVersionNumber(void);

static unsigned int CliGetFileServerMajorVersionNumber(void)
{
  static int major = 0;
  if (!major)
    {
    char *fname = "GetFileServerMajorVersionNumber";
    int (*proc)(void) = (int (*)(void))ImportSymbol( CliRetrieveNLMHandle(), fname );
    if (!proc)
      return 3;
    major = (*proc)();
    UnimportSymbol( CliRetrieveNLMHandle(), fname );
    }
  return major;
}  
    
/* ------------------------------------------------------------------ */

extern "C" unsigned int GetNestedInterruptLevel(void);

static unsigned int CliGetNestedInterruptLevel(void)
{ return GetNestedInterruptLevel(); }

/* ------------------------------------------------------------------ */

extern "C" unsigned int GetCurrentTime(void);

unsigned int CliGetCurrentTicks(void)
{ return GetCurrentTime(); }

extern "C" unsigned int ConvertTicksToSeconds( unsigned int ticks, 
                                  unsigned int *secs, unsigned int *hsecs );

unsigned int CliConvertTicksToSeconds( unsigned int ticks, 
                                  unsigned int *secs, unsigned int *hsecs )
{ 
  unsigned int s, h;
  /* ConvertTicksToSeconds( ticks, &s, &h ); */
  _asm
    {
    mov eax, ticks
    mov ecx, 655360
    mul ecx
    add eax, 596590
    adc edx, 0
    mov ecx, 1193180
    div ecx
    xor edx, edx
    mov ecx, 10
    div ecx
    mov s, eax
    mov h, edx
    } 
  if (secs) *secs = s;
  if (hsecs) *hsecs = h;
  return s;
}    
  
extern "C" unsigned int ConvertSecondsToTicks( unsigned int secs, 
                                  unsigned int hsecs, unsigned int *ticks );
unsigned int CliConvertSecondsToTicks( unsigned int secs, 
                                  unsigned int hsecs, unsigned int *ticks )
{ 
  unsigned int t;
  /* ConvertSecondsToTicks( secs, hsecs, &t ); */
  _asm
    {
    mov eax, secs
    mov ecx, 10
    mul ecx
    add eax, hsecs
    mov ecx, 1193180
    mul ecx
    add eax, 327680
    adc edx, 0
    mov ecx, 655360
    cmp edx, ecx
    jge _end
    div ecx
    _end:
    mov t, eax
    }
  if (ticks) *ticks = t;
  return t;
}    

/* ------------------------------------------------------------------ */

extern "C" int AllocateResourceTag(int NLMHandle, char *desc, int resType);

static int CliGetResourceTag(int tagType)
{
  static int aespTag = 0, plprTag = 0, evntTag = 0;
  int *sel = NULL;
  char *desc;

  DebugPrintf("CliGetResourceTag: beginning\r\n");
  
  if (tagType == 'PSEA' )  /* AESProcessSignature */
    {
    sel = &aespTag;
    desc = "AES Process Call-Back";
    }
  else if (tagType == 'RPLP')
    {
    sel = &plprTag;
    desc = "Polling Procedure Call-Back";
    }
  else if (tagType == 'TNVE')
    {
    sel = &evntTag;
    desc = "Event Notification Call-Back";
    }
  
  if (sel && !*sel)
    *sel = AllocateResourceTag( CliRetrieveNLMHandle(), desc, tagType );

  DebugPrintf("CliGetResourceTag: ended\r\n");
  return ((sel)?(*sel):(0));
}

/* ------------------------------------------------------------------ */

static unsigned int CliGetConnectionsInUseCount(void)
{
  static unsigned int (*_GetConnectionLicensesInUse)(void) = 
                                                 (unsigned int (*)())0x1;
  static unsigned int (*_StationsInUseCount);
  if (_GetConnectionLicensesInUse == (unsigned int (*)())0x1 )
    {
    _GetConnectionLicensesInUse = 
                        (unsigned int (*)())ImportSymbol( CliRetrieveNLMHandle(), 
                               /* "\x1a\" */ "GetConnectionLicensesInUse" );
    if (!_GetConnectionLicensesInUse)
      _StationsInUseCount = (unsigned int (*))ImportSymbol( CliRetrieveNLMHandle(), 
                               "StationsInUseCount" );
      
    }                           
  if (_GetConnectionLicensesInUse)
    return (*_GetConnectionLicensesInUse)();
  if (_StationsInUseCount) 
    return (*_StationsInUseCount);
  return 0;  
}    

static unsigned int CliGetDiskIOsPending(void)
{
  static unsigned int (*_DiskIOsPending)=((unsigned int *)(0x01));

  if (_DiskIOsPending==((unsigned int *)(0x01)))
    _DiskIOsPending = (unsigned int *)ImportSymbol( CliRetrieveNLMHandle(), "DiskIOsPending" );
  return ((_DiskIOsPending)?(*_DiskIOsPending):(0));
}  

static unsigned int CliGetNDirtyBlocks(void)
{
  static unsigned int (*_NDirtyBlocks)=((unsigned int *)(0x01));

  if (_NDirtyBlocks==((unsigned int *)(0x01)))
    _NDirtyBlocks = (unsigned int *)ImportSymbol( CliRetrieveNLMHandle(), "NDirtyBlocks" );
  return ((_NDirtyBlocks)?(*_NDirtyBlocks):(0));
}

/* ------------------------------------------------------------------ */

static void (*_ThreadSwitch)(void) = ThreadSwitch;
static void (*_RunnerSwitch)(void) = ThreadSwitch;

static void CliSimulatedThreadSwitchWithDelay(void) //can't use delay
{ /* ResumeThread(GetThreadID()); SuspendThread(GetThreadID()); */
  int i; for (i=0;i<10;i++) (*_ThreadSwitch)(); }   //and don't bother with CRescheduleFromInterrupt

static void (*_ThreadSwitchWithDelay)(void) = CliSimulatedThreadSwitchWithDelay;
static void (*_RunnerSwitchWithDelay)(void) = CliSimulatedThreadSwitchWithDelay;

static void CliSimulatedThreadSwitchLowPriority(void)
{
  int temp;
  (_ThreadSwitch)(); //ThreadSwitch/thr_yield/CRescheduleMyself/CYieldIfNeeded
  while (((temp=CliGetDiskIOsPending())!=0) || CliGetNDirtyBlocks())
    (*_ThreadSwitch)();
  return;
}  

static void (*_ThreadSwitchLowPriority)(void) = ThreadSwitch; //CliSimulatedThreadSwitchLowPriority;
static void (*_RunnerSwitchLowPriority)(void) = ThreadSwitch; //CliSimulatedThreadSwitchLowPriority;

void CliThreadSwitch(void) { (*_ThreadSwitch)(); }
void CliThreadSwitchWithDelay(void) { (*_ThreadSwitchWithDelay)(); }
void CliThreadSwitchLowPriority(void) { (*_ThreadSwitchLowPriority)(); }
void CliRunnerSwitch(void) { (*_RunnerSwitch)(); }
void CliRunnerSwitchWithDelay(void) { (*_RunnerSwitchWithDelay)(); }
void CliRunnerSwitchLowPriority(void) { (*_RunnerSwitchLowPriority)(); }

static void CliInitThreadSwitchSchemes(void)
{
  static int isInit = 0;
  int haveRunner;
  void *tempScheme;
  char *fname;
  
  if (!isInit)
    {
  DebugPrintf("CliInitThreadSwitchSchemes: beginning\r\n");

    isInit = 1;
    
    haveRunner = 0;
    if (CliGetFileServerMajorVersionNumber() > 3)
      {
      if (CliGetNumberOfProcessors() > 1)  
        {
        tempScheme = ImportSymbol( CliRetrieveNLMHandle(), "thr_yield" );
        if (tempScheme) 
          {
          _RunnerSwitchWithDelay = _RunnerSwitchLowPriority =
            _RunnerSwitch = (void (*)(void))(tempScheme);
          haveRunner = 1;
ConsolePrintf("have_runner!\r\n");          
          }
        }
      }
    fname = "CYieldIfNeeded"; //==CRescheduleMyself
    if (ImportSymbol( CliRetrieveNLMHandle(), fname+1 )!=NULL)
      {
      UnimportSymbol( CliRetrieveNLMHandle(), fname+1 );
      tempScheme = ImportSymbol( CliRetrieveNLMHandle(), fname );
      if (tempScheme) _ThreadSwitch = (void (*)(void))(tempScheme);
      }
    fname = "CYieldWithDelay"; //==CReschedule[Last]WithDelay
    if (ImportSymbol( CliRetrieveNLMHandle(), fname+1 )!=NULL)
      {
      UnimportSymbol( CliRetrieveNLMHandle(), fname+1 );
      tempScheme = ImportSymbol( CliRetrieveNLMHandle(), fname );
      if (tempScheme) _ThreadSwitchWithDelay = (void (*)(void))(tempScheme);
      }
    fname = "CYieldUntilIdle"; //==CRescheduleLastLowPriority
    if (ImportSymbol( CliRetrieveNLMHandle(), fname+1 )!=NULL)
      {
      UnimportSymbol( CliRetrieveNLMHandle(), fname+1 );
      tempScheme = ImportSymbol( CliRetrieveNLMHandle(), fname );
      if (tempScheme) _ThreadSwitchLowPriority = (void (*)(void))(tempScheme);
      }
    if (!haveRunner)
      {
      _RunnerSwitch = _ThreadSwitch;
      _RunnerSwitchWithDelay = _ThreadSwitchWithDelay;
      _RunnerSwitchLowPriority = _ThreadSwitchLowPriority;
      }
  DebugPrintf("CliInitThreadSwitchSchemes: ended\r\n");
    }
  return;
}
    
/* ------------------------------------------------------------------ */

int CliIsNetworkAvailable(int unused) /* returns !0 if available */
{
  int _offlinemode = 1;
  unused = unused;

  if (FindNLMHandle( "TCPIP.NLM" )!= NULL)
    {
    char *fname = "gethostid";
    long (*_gethostid)() = 
      (long (*)())ImportSymbol( CliRetrieveNLMHandle(), fname );
    if (_gethostid)
      {
      _offlinemode = (*_gethostid)();
      UnimportSymbol( CliRetrieveNLMHandle(), fname );
      _offlinemode = ((_offlinemode == 0 || _offlinemode == -1) ? 1 : 0);
      }
    }
  return (_offlinemode==0);
}

/* ------------------------------------------------------------------------ */

//used to rename threads and for messages
static char _cliApplicationName[10]="RC5DES\0";

const char *CliGetApplicationName(void) 
{ return _cliApplicationName; }

static void CliSetApplicationName(char *p)
{
  if (p && *p)
    {
    char *q = p+(strlen(p)-1);
    while (*q!='\\' && *q!='/' && *q!=':' && q>p)
      q--;
    if (*q=='\\' || *q=='/' || *q==':')
      q++;
    int i=0;
    while (*q && *q!='.' && i<8)
      _cliApplicationName[i++]=*q++;
    _cliApplicationName[i]=0;
    }
  return;
}    

/* ------------------------------------------------------------------ */

extern "C" CValidatePointer(void *);

static int CliValidatePointer(void *ptr)  //need to do a domain check
{
  if (!ptr) return 0;
  char *fname = "CValidatePointer";
  int (*_valiptr)(void *) = (int (*)(void *))ImportSymbol(CliRetrieveNLMHandle(), fname);
  if (_valiptr)
    {
    int isv = (*_valiptr)(ptr);
    UnimportSymbol( CliRetrieveNLMHandle(), fname );
    return isv;
    }
  return 1;
}  

/* ------------------------------------------------------------------ */

static int CliIsThreadRunning( int threadID )
{
  if (threadID == GetThreadID())
    return 1;
  delay(55*5);
  return 0;
  /*
  char threadName[20];
  if (!CliValidatePointer((void *)(threadID)))
    return 0;
  return ( GetThreadName( threadID, threadName ) != EBADHNDL );
  */
}

/* ------------------------------------------------------------------ */

int CliWaitForThreadExit( int threadID )
{
  if (threadID)
    {
    if (threadID == GetThreadID())
      return -1; /* ExitThread( TSR_THREAD, 0 ); -1, 0 */
    while (CliIsThreadRunning( threadID ))
      delay(110);
    }  
  return 0;  
}    

/* ------------------------------------------------------------------ */

static unsigned int _cliHighestMTPos = 0;

static int CliGetNumberOfChildThreads(void)
{ return _cliHighestMTPos; }

int CliSetThreadName( int threadID, int mtPos )  // 0 = main, 1 = first, etc
{
  char threadName[20];       //"RC5DES Crunch #16"  // max 17 chars
  if (mtPos > _cliHighestMTPos)
    _cliHighestMTPos = mtPos;
  sprintf( threadName, ((mtPos)?("%s Crunch #%02d"):("%s Main")), 
                                  CliGetApplicationName(), mtPos );
  return RenameThread( threadID, threadName );
}  

/* ------------------------------------------------------------------ */

int CliClearThreadContextSpecifier( int threadID ) //doesn't need clib-context 
{                  //will only fail if thread is not in a clib-"thread-group"
   static int (*_SetThreadContextSpecifier)(int, int) = (int (*)(int,int))0xffffffff;
   if (_SetThreadContextSpecifier == (int (*)(int,int))0xffffffff )
     _SetThreadContextSpecifier = (int (*)(int, int))ImportSymbol(
                  GetNLMHandle(), /* "\x19\ */ "SetThreadContextSpecifier" );
   if (_SetThreadContextSpecifier)
     return (*_SetThreadContextSpecifier)( threadID, 0 /*NO_CONTEXT*/ );
   return -1;
}   

/* ------------------------------------------------------------------ */

static int CliDisableUpgradeLowPriorityThreadsSetting( void )
{
  /*
  int (*_NWSMSetDynamicCmdIntValue)(int, char *, int ) =
    (int (*)( int, char *, int ))CliGetSymbolVector( "NWSMSetDynamicCmdIntValue" );
  if (_NWSMSetDynamicCmdIntValue) //(conn #, name, new value)
    ( *_NWSMSetDynamicCmdIntValue )(0,"Upgrade Low Priority Threads", 0);
  */
  return 0;
}                                     

/* ------------------------------------------------------------------ */

static unsigned int CliGetSystemUtilization(void)
{
  static int (*_MaximumNumberOfPollingLoops) = NULL;
  static int (*_NumberOfPollingLoops) = NULL;
  static int (*_CPU_Combined) = NULL;
  
  if (!_MaximumNumberOfPollingLoops)
    {
    _CPU_Combined = (int *)ImportSymbol( CliRetrieveNLMHandle(), "CPU_Combined" );
    if (!_CPU_Combined)
      {
      char *fname = "MaximumNumberOfPollingLoops";
      _MaximumNumberOfPollingLoops = (int *)ImportSymbol( CliRetrieveNLMHandle(), fname );
      if (_MaximumNumberOfPollingLoops)
        {
        _NumberOfPollingLoops = 
                     (int *)ImportSymbol(CliRetrieveNLMHandle(), "NumberOfPollingLoops");
        if (!_NumberOfPollingLoops)
          {
          UnimportSymbol( CliRetrieveNLMHandle(), fname );
          _MaximumNumberOfPollingLoops = NULL;
          }
        }
      }
    }
  if (_CPU_Combined)
    return (100 * (*_CPU_Combined));    
  if (_MaximumNumberOfPollingLoops && _NumberOfPollingLoops)
    {
    unsigned int m, n;    
    m = (*_MaximumNumberOfPollingLoops);
    n = (*_NumberOfPollingLoops);
    return (10000-(((n*10000)+(m>>1))/m));
    }
  return 0;
}  

/* ======================================================================== */

static int (*_thr_type)(void) = (int (*)())(0xffffffff);

int CliIsSMPAvailable(void)
{
  if (_thr_type == ((int (*)())(0xffffffff)))
    _thr_type = (int (*)())ImportSymbol(CliRetrieveNLMHandle(), "thr_type" );
   //ConsolePrintf("thr_type: %08x\r\n", _thr_type );
  return (_thr_type!=NULL);
}  

int CliIsThreadOnMP(void)
{
  if (CliIsSMPAvailable())
    {
    //ConsolePrintf("thr_type(): %08x\r\n", (*_thr_type)() );
    return (*_thr_type)();
    }
  return 0;
}  

unsigned int CliGetCurrentProcessor()  //misnomer. Only says if in SMP control
{ return CliIsThreadOnMP(); }  

int CliMigrateThreadToSMP(void)
{
  static int (*_NWSMPThreadToMP)(void) = ((int (*)())(0xffffffff));
  static int (*_thr_preempt_on)(void) = ((int (*)())(0xffffffff));
  if (CliIsSMPAvailable())
    {
    if (_NWSMPThreadToMP == ((int (*)())(0xffffffff)))
      {
      _thr_preempt_on=(int (*)())ImportSymbol(CliRetrieveNLMHandle(),
                                "thr_preempt_on" /* "NWSMPThreadToMP" */);
      _NWSMPThreadToMP=(int (*)())ImportSymbol(CliRetrieveNLMHandle(),
                                "thr_yield_to_mp" /* "NWSMPThreadToMP" */);
      if (!_NWSMPThreadToMP || !_thr_preempt_on)
        {
        ConsolePrintf("%s: Unable to initialize SMP functions. The client will continue\r\n"
        "  normally but will not be able to take advantage of MP features.",
                       CliGetApplicationName() );
        }
      }
    if (_NWSMPThreadToMP)
      {
      int i=(*_NWSMPThreadToMP)();
      if (CliIsThreadOnMP() && _thr_preempt_on)
        {
        //ConsolePrintf("thr_preempt_on()");
        (*_thr_preempt_on)();        
        }
      return i;
      }
    }  
  return 0;
}    

/* ------------------------------------------------------------------------ */

int CliGetNumberOfProcessors(void)
{
  static int processorcount = 0;

  if (!processorcount)
    {
    if (CliIsSMPAvailable())
      {
      char *fname = "GetNumberOfRegisteredProcessors";
      int (*_GetNumberOfRegisteredProcessors)() = 
        (int (*)())ImportSymbol( CliRetrieveNLMHandle(), fname );
      if (_GetNumberOfRegisteredProcessors)
        {
        processorcount = ((* _GetNumberOfRegisteredProcessors)());
        (int (*)())UnimportSymbol( CliRetrieveNLMHandle(), fname );
        }
      }
    if (processorcount <= 0);
      processorcount = 1;
    }
  return processorcount;
}  

/* ------------------------------------------------------------------------ */

#if 0
int CliValidateProcessorCount( int numcpu )
{
  static unsigned int numcputemp = 0;

  if (numcputemp == 0)
    numcputemp = CliGetNumberOfProcessors();
  if (numcpu <= 0 )
    {
    /*
    ConsolePrintf("%s: Client will use %d%s CPU%c\r\n", 
      CliGetApplicationName(), min(numcputemp,MAXCPUS),
      ((numcputemp>MAXCPUS)?(" (client limit)"):("")), 
      ((numcputemp>1)?('s'):(' ')));
    */
    numcpu = numcputemp;               
    }
  else if (numcpu > numcputemp)                            
    {
    /*
    ConsolePrintf("%s: Overriding detected number of CPUs (%d)."
       " Using %d%s CPU%c.\r\n", CliGetApplicationName(), numcputemp, 
        min(numcpu,MAXCPUS), ((numcpu>MAXCPUS)?(" (client limit)"):("")), 
        ((numcpu>1)?('s'):(' ')));
    */
    }
  if (numcpu > MAXCPUS)
    numcpu = MAXCPUS;
  return numcpu;
}    
#endif
    
/* ------------------------------------------------------------------------ */

static int CliInitSMP(void)
{
  CliIsSMPAvailable();  //this done all the importing
  return 0;
}


/* ======================================================================== */

int CliGetSystemConsoleScreen(void)
{
  static int _consoleScreen = -1;

DebugPrintf("CliGetSystemConsoleScreen: beginning\r\n");  
  if (_consoleScreen==-1)
    {
    _consoleScreen = 0;
    char *fname = "systemConsoleScreen";
    int (*_systemConsoleScreen)=(int (*))ImportSymbol( GetNLMHandle(), fname );
    if (_systemConsoleScreen)
      {
      _consoleScreen = (*_systemConsoleScreen);
      (int (*))UnimportSymbol( GetNLMHandle(), fname );
      }
    else 
      {
      char *fname = "GetSystemConsoleScreen";
      int (*_GetSystemConsoleScreen)(void)=(int (*)())ImportSymbol( GetNLMHandle(), "GetSystemConsoleScreen" );
      if (_GetSystemConsoleScreen)
        {
        _consoleScreen = (*_GetSystemConsoleScreen)();
        (int (*))UnimportSymbol( GetNLMHandle(), fname );
        }
      }
    }
DebugPrintf2("CliGetSystemConsoleScreen: ended: %08x\r\n",_consoleScreen);  
  return _consoleScreen;
}  

/* ------------------------------------------------------------------------ */

extern "C" int ActivateScreen(int screenID);

int CliActivateScreen(int screenID)
{ return (!screenID || screenID==-1)?(-1):(ActivateScreen(screenID)); }

int CliActivateConsoleScreen(void)
{ return CliActivateScreen(CliGetSystemConsoleScreen()); }

extern "C" void OutputToScreenWithPointer( int, char *, void * );
extern "C" void OutputToScreen( int, char *, ... );

void xxCliConsolePrintf( char *format, ... )
{
  int scrid;
  if ((scrid = CliGetSystemConsoleScreen())!=0)
    {
    char **x=&format;
    OutputToScreen( scrid, "%s: ", CliGetApplicationName() );
    OutputToScreenWithPointer( scrid, format, (((char *)(x))+sizeof(char*)) );
    }
  return;
}  

/* ------------------------------------------------------------------------ */

static int nwScreenID = 0;     //used in 
static int nwScreenHandle = 0; //used in 
static char nwCliScreenName[30]={0};
  
static int CliIsClientScreenActive(void)
{ return 1; /* (activeScreen == nwScreenID); */ }

static char *CliGetScreenName(void)
{ return nwCliScreenName; }

static char *CliGetScreenMemPointer(void)
{ 
  static int isValidated=0;
  if (!isValidated)
    {
    if (nwScreenID && (*((int *)(((char *)(nwScreenID))+0x00))==(int)(0x34561289)))
      {
      unsigned char buff[2], orig[2];
      char *mem = *((char **)(((char *)(nwScreenID))+0x20));
      orig[0]=mem[0]; orig[1]=mem[1];
      buff[0]='C'; buff[1]='Y';
      CopyToScreenMemory( 1, 1, buff, 0, 0 );
      if (mem[0]!=buff[0] || mem[1]!=buff[1])
        isValidated=-1;
      else
        isValidated=1;
      mem[0]=orig[0]; mem[1]=orig[1];
      }
    }
  if (isValidated>0)
    return (*((char **)(((char *)(nwScreenID))+0x20)));
  return NULL;
}    

/* ------------------------------------------------------------------------ */
    
static int _cliAutoDestroyScreen = 1;

int CliGetScreenDestructionMode(void)
{ return _cliAutoDestroyScreen; }
    
int CliSetScreenDestructionMode(int mode)
{ int i=_cliAutoDestroyScreen; _cliAutoDestroyScreen=mode; 
  return SetAutoScreenDestructionMode( mode ); } //mode may be change in -test etc

/* ------------------------------------------------------------------------ */

#ifndef _NWTHREAD_H_
struct AESProcessStructure
{                                                      /* set by or to... */
   struct AESProcessStructure *ALink;           /* AES will set*/
   LONG                        AWakeUpDelayAmount; /* ticks to wait */
   LONG                        AWakeUpTime;     /* AES will set */
   void                      (*AProcessToCall)(struct AESProcessStructure *);
                                                /* set to function to call */
   LONG                        ARTag;           /* set to resource tag */
   LONG                        AOldLink;        /* set to NULL */
};


extern "C" int CancelSleepAESProcessEvent( struct AESProcessStructure *EventNode );
extern "C" int ScheduleSleepAESProcessEvent( struct AESProcessStructure *EventNode );
#endif
static struct AESProcessStructure cliScreenStatusAESStruct;
static int cliScreenStatusProcessorStatus = 0;
void (*cliScreenStatusProc)(void) = NULL;

static AESProcessStructure *CliScreenStatusProcessorExec( struct AESProcessStructure *aesP )
{
  if (cliScreenStatusProcessorStatus < 0 || SignalTriggered || UserBreakTriggered)
    cliScreenStatusProcessorStatus = 0;
  else
    {
    cliScreenStatusProcessorStatus = 1;
    ScheduleSleepAESProcessEvent(aesP);
    }
  if (cliScreenStatusProc)
    (*cliScreenStatusProc)();
  return aesP;
}  

static int CliInitScreenStatusProcessor(void)
{
  if (cliScreenStatusProcessorStatus)
    return 0;

  if (CliGetScreenMemPointer()==NULL)
    return -1;

  #define AESProcessSignature            0x50534541  /* 'AESP' */
  cliScreenStatusAESStruct.AOldLink = 0;
  cliScreenStatusAESStruct.AWakeUpDelayAmount = 9;  /* in ticks */
  #ifdef _SYS_TIMEVAL_H_ //using nwsdk, watcom sdk calls it _SYS_TIME_H_INCLUDED
  cliScreenStatusAESStruct.AProcessToCall = (void (*)(void *))CliScreenStatusProcessorExec;
  #else
  cliScreenStatusAESStruct.AProcessToCall = (void (*)(AESProcessStructure *))CliScreenStatusProcessorExec;
  #endif
  cliScreenStatusAESStruct.ARTag = CliGetResourceTag(AESProcessSignature);
  
  if (!cliScreenStatusAESStruct.ARTag)
    return -1;
  
  ScheduleSleepAESProcessEvent(
                   (struct AESProcessStructure *)&cliScreenStatusAESStruct);
  int count = 0;
  cliScreenStatusProcessorStatus = 0;
  while (!cliScreenStatusProcessorStatus && ((++count)<=10)) //5 secs
    delay(110*cliScreenStatusAESStruct.AWakeUpDelayAmount); /* let process run */
  if (!cliScreenStatusProcessorStatus)
    CancelSleepAESProcessEvent(
                   (struct AESProcessStructure *)&cliScreenStatusAESStruct);
  return(cliScreenStatusProcessorStatus!=0);
}                   

/* ------------------------------------------------------------------------ */

static int CliSetScreenStatusProcessor( void (*_statusProc)(void ) )
{
  cliScreenStatusProc = _statusProc;
  return 0;
}  

/* ------------------------------------------------------------------------ */

static int CliDestroyScreenStatusProcessor(void) //this must be clib-call free
{
  if (cliScreenStatusProcessorStatus)
    {
    cliScreenStatusProcessorStatus=-1;
    unsigned int timeout = CliGetCurrentTicks()+(10*cliScreenStatusAESStruct.AWakeUpDelayAmount);
    while (cliScreenStatusProcessorStatus && (CliGetCurrentTicks()<timeout))
      CliThreadSwitchWithDelay();  /* let process run */
    if (cliScreenStatusProcessorStatus)
      {
      CancelSleepAESProcessEvent(
                 (struct AESProcessStructure *)&cliScreenStatusAESStruct);
      cliScreenStatusProcessorStatus = 0;
      }
    }
  return 0;                  
}                 

static int CliUninitClientScreen(void)
{ 
  CliDestroyScreenStatusProcessor();
  return 0;
}

/* ------------------------------------------------------------------------ */

static int CliInitClientScreen(void)
{
  if (!CliGetSystemConsoleScreen())
    return -1;

  if (!CliGetScreenDestructionMode()) 
    strcpy( nwCliScreenName, "System Console" );
  else
    sprintf(nwCliScreenName, "%s v2.%d.%d client",
       CliGetApplicationName(), CLIENT_CONTEST*100 + CLIENT_BUILD, CLIENT_BUILD_FRAC );
  
  int scr=CreateScreen(nwCliScreenName, DONT_AUTO_ACTIVATE | 
                     DONT_CHECK_CTRL_CHARS );  //| AUTO_DESTROY_SCREEN 

  if (scr==0 || scr==-1)
    return -1;

  nwScreenHandle = scr;
  nwScreenID = __GetScreenID(scr);

  DisplayScreen(scr);
  SetCtrlCharCheckMode( 0 ); //disable ^C, ^S checks. SIGINT causes hang!

  if (CliGetScreenDestructionMode()) 
    {
    CliInitScreenStatusProcessor(); 
    SetAutoScreenDestructionMode( 1 ); //this may be change in -test etc  
    }
  return 0;
}   

/* ======================================================================== */

static char cliBasePathBuffer[128+1]; //used in verifypaths and InitClient
static int cliCwdIsValid = -1;

int CliInitPaths(char *master)
{
  unsigned int pos, c;

  if (cliCwdIsValid==-1 && master && *master)
    {
    pos = strlen(master);
    while (pos)
      {
      if (master[pos]=='\\' || master[pos]=='/' || master[pos]==':')
        {
        if (master[pos]==':')
          {
          pos++;
          if (master[pos]=='\\' || master[pos]=='/') //never happens
            pos++;
          }
        cliCwdIsValid = 0;
        break;
        }
      pos--;
      }
    if (cliCwdIsValid==0 && (pos+1)<= sizeof(cliBasePathBuffer))
      {  
      memcpy( cliBasePathBuffer, master, pos );
      cliBasePathBuffer[pos+1]=0;
      if (chdir( cliBasePathBuffer )==0)
        {
        getcwd(cliBasePathBuffer,sizeof(cliBasePathBuffer)-1);
        cliCwdIsValid = 1;
        }
      }
    else if (getcwd(cliBasePathBuffer,sizeof(cliBasePathBuffer)-1)==NULL)
      strcpy(cliBasePathBuffer,"SYS:");
    }
  return 0;
}  

const char *CliGetClientDirectory(void)
{ //if (cliCwdIsValid == -1) CliInitPaths(NULL);
  return cliBasePathBuffer; 
}

/* ====================================================================== */

/* ====================================================================== */
/* -- all the polling related functions must be totally clib-call free -- */

static struct callbacksched 
{
  Problem *problem;
  int timeslice;
  int run;
  int cpu_i;
}  
#if (MAX_IDLELOOP_CALLBACKS < 1)
  callbackschedtable[1];
#else  
  callbackschedtable[MAX_IDLELOOP_CALLBACKS];  
#endif  

static int callbackschedinit = 0;
static int callbackschedinproc = 0;  //CliAllocIdleCallback() and CliFreeIdleCallback() aren't reentrant
static int callbackschedrestag;
static int (*_RemovePollingProcedure)( void (*proc)(void) ) = NULL; 
static int (*_AddPollingProcedureRTag)( void (*proc)(void), int resTag ) = NULL;

static void CliIdleCallbackHandler( void )
{
  static unsigned int index = 0;
  if (!GetNestedInterruptLevel())
    {
    unsigned int i=index;
    while (callbackschedtable[i].problem==NULL || callbackschedtable[i].run!=0)
      {
      ++i;
      if (i>=MAX_IDLELOOP_CALLBACKS)
        i=0;
      if (i==index) //nothing to do
        return;
      }
    callbackschedtable[i].problem->Run(callbackschedtable[i].timeslice,
                                              callbackschedtable[i].cpu_i);
    index = ++i;
    if (index>=MAX_IDLELOOP_CALLBACKS)
      index = 0;
    }
  return;
}

static void CliDummyCallbackHandler( void ) //for resetting polling loop counters
{
  return; 
}

static struct callbacksched *CliAllocIdleCallback( Problem *problem, int timeslice, int cpu_i )
{
  static unsigned int nextResetTime = 0;
  struct callbacksched *cbs = NULL;

  #if (MAX_IDLELOOP_CALLBACKS < 1)
  CliSetIsPollingProhibitedFlag(1);  
  #endif
  
  if (CliGetIsPollingProhibitedFlag())  
    return NULL;

  while (callbackschedinproc)  //mutex or semaphore takes too long
    CliThreadSwitchLowPriority();
  callbackschedinproc=1;
  
  if (!callbackschedinit)
    {
    for (int i=0;i<MAX_IDLELOOP_CALLBACKS;i++)
      {
      callbackschedtable[i].problem = NULL;
      callbackschedtable[i].run = 0;
      }
    callbackschedrestag = CliGetResourceTag( 'RPLP' ); 
    if (callbackschedrestag) //only assigned once - see CliGetResourceTag()
      {
      if (!_AddPollingProcedureRTag)
        _AddPollingProcedureRTag = (int (*)(void (*)(void),int))ImportSymbol( CliRetrieveNLMHandle(), "AddPollingProcedureRTag" );
      if (!_RemovePollingProcedure)
        _RemovePollingProcedure = (int (*)(void (*)(void)))ImportSymbol( CliRetrieveNLMHandle(), "RemovePollingProcedure" );
      if (_AddPollingProcedureRTag && _RemovePollingProcedure)
        callbackschedinit = -1;
      }
    }
  if (callbackschedinit == -1)
    {
       //don't know if it makes a difference, but set the callback context to
       //nothing, so that the poll-process callback is being forced to switch
       //context the-trillion-or-so times per sec.
    CliClearThreadContextSpecifier( GetThreadID() );  //doesn't need clib-context (but will fail if not in thr group)
      //now add the polling process
    if (!(*_AddPollingProcedureRTag)( CliIdleCallbackHandler, callbackschedrestag))
      {
      callbackschedinit = 1;
      nextResetTime = CliGetCurrentTicks()+(18*60*10); //every ten minutes
      }
    }
  if (callbackschedinit == 1)
    {
    for (int i=0;i<MAX_IDLELOOP_CALLBACKS;i++)
      {
      if (callbackschedtable[i].problem == problem)
        {
        callbackschedtable[i].timeslice = timeslice;
        callbackschedtable[i].cpu_i = cpu_i;
        callbackschedtable[i].run = 0;
        cbs = &(callbackschedtable[i]);
        break;
        }
      }
    if (!cbs)
      {
      for (int i=0;i<MAX_IDLELOOP_CALLBACKS;i++)
        {
        if (callbackschedtable[i].problem == NULL)
          {
          callbackschedtable[i].problem = problem;
          callbackschedtable[i].timeslice = timeslice;
          callbackschedtable[i].cpu_i = cpu_i;
          callbackschedtable[i].run = 0;
          cbs = &(callbackschedtable[i]);
          break;
          }
        }
      }
    if (cbs)
      {
      unsigned int i = CliGetCurrentTicks();
      if (CliGetFileWasAccessedFlag() || (i >= nextResetTime))
        {
        nextResetTime = i+(18*60*10); //every ten minutes
        if (!(*_AddPollingProcedureRTag)( CliDummyCallbackHandler, callbackschedrestag))
          {
          (*_RemovePollingProcedure)( CliDummyCallbackHandler );
          }
        }
      }
    }
  callbackschedinproc=0;
  return cbs;
}  

static int CliFreeIdleCallback( Problem *problem, int timeslice, int cpu_i )
{
#ifdef DELETE_CALLBACK_ON_EMPTY
  while (callbackschedinproc)  //mutex or semaphore takes too long
    CliThreadSwitchLowPriority();
  callbackschedinproc=1;

  if (callbackschedinit == 1)
    {
    int found = 0;
    for (int i=0;i<MAX_IDLELOOP_CALLBACKS;i++)
      {
      if (callbackschedtable[i].problem == problem)
        {
        callbackschedtable[i].problem = NULL;
        callbackschedtable[i].run = 0;
        }
      else if (callbackschedtable[i].problem != NULL)
        {
        found = 1;
        break;
        }
      }
    if (!found)
      {
#ifdef DELETE_CALLBACK_ON_EMPTY
      callbackschedinit = -1;
      (*_RemovePollingProcedure)( CliIdleCallbackHandler );
#endif      
      }
    }
  callbackschedinproc=0;
#endif
  return 0;  
}    

static void CliUninitPollingProcedures(void) //this must be clib-call free
{
  if (callbackschedinit == 1)
    {
    for (int i=0;i<MAX_IDLELOOP_CALLBACKS;i++)
      callbackschedtable[i].problem = NULL;
    callbackschedinit = -1;
    (*_RemovePollingProcedure)( CliIdleCallbackHandler );
    }
  return;
}  

/* ------------------------------------------------------------------------ */

//static int _cliThreadGroupID = 0; //used in CliInitClient and CliExitClient

extern "C" unsigned char SetCurrentNameSpace( unsigned char );
extern "C" unsigned char SetTargetNameSpace( unsigned char );
extern "C" unsigned int SetCurrentConnection( unsigned int );

int CliInitClient( int argc, char *argv[], void *client )
{
DebugPrintf("CliInitClient: beginning\r\n");

  //_cliThreadGroupID = GetThreadGroupID();
  CliSaveNLMHandle(GetNLMHandle());
  CliSetApplicationName(argv[0]);
  CliSetMainThreadID(GetThreadID());
  CliSetClientPointer((Client *)client); //so we can get at filenames and niceness level

  SetCurrentConnection(0); //this is said to be neccesary on NetWare 4.x
  SetCurrentNameSpace(0); //will this help with chdir/getcwd problems?
  SetTargetNameSpace(0); //will this help with chdir/getcwd problems?

  CliSetThreadName( GetThreadID(), 0 );          /* -> "RC5DES Main" */
  CliClearThreadContextSpecifier( GetThreadID() ); //doesn't need clib-context (but will fail if not in thr group)
  CliDisableUpgradeLowPriorityThreadsSetting();

  CliInitPaths( argv[0] );
  CliInitThreadSwitchSchemes();
  CliInitSMP();

  nwCliInitLoadLevelControl(); //will thread switch here

  int autoclose=1;
  int nopoll=0;
  for (int i = 1; i < argc; i++)
    {
    if ( strcmpi(argv[i], "-benchmark" ) == 0 ||
         strcmpi(argv[i], "-benchmark2" ) == 0 ||
         strcmpi(argv[i], "-fetch" ) == 0 || 
         strcmpi(argv[i], "-flush" ) == 0 || 
         strcmpi(argv[i], "-update" ) == 0 || 
         strcmpi(argv[i], "-test" ) == 0 )
      { 
      autoclose = 0;
      }
    else if ( strcmpi(argv[i], "-nopoll" ) == 0 )
      {
      argv[i] = "";
      nopoll = 1;
      }
    }
  if (nopoll)
    CliSetIsPollingProhibitedFlag(1);
  #ifndef NOAUTODESTROYSCREEN
  if (!autoclose)
  #endif
    CliSetScreenDestructionMode(0); //wait, so that we can see the results

  if (CliInitClientScreen())
    {
    ConsolePrintf("%s: Unable to create client screen. Quitting...\r\n", 
                                                 CliGetApplicationName() );
    return -1;                        
    } 
  //void CliScreenStatusUpdateProcedure( void ); /* aes process proto */
  //CliSetScreenStatusProcessor( CliScreenStatusUpdateProcedure );
  //moved to problem runner

  CliIsClientRunning(1);

DebugPrintf("CliInitClient: ended\r\n");
  return 0;
}

/* ----------------------------------------------------------------- */

int CliExitClient(void)         //make sure every function in here is
{                               //clib call free (there is no "threadgroup")
DebugPrintf2("CliExitClient: beginning: CliIsClientRunning()->%d\r\n",CliIsClientRunning());
  if (CliIsClientRunning())
    {
    CliUninitPollingProcedures();
    CliDestroyScreenStatusProcessor();
    CliIsClientRunning(0); 
    }
DebugPrintf2("CliExitClient: ended:     CliIsClientRunning()->%d\r\n",CliIsClientRunning());
  return 0;
} 

// right now, CliForceClientShutdown() is called from sig handler

void CliForceClientShutdown(void) { CliExitClient(); }

/* ------------------------- used elsewhere ------------------------ */

int CliValidateSinglePath( char *dest, unsigned int destsize,
                  char *defaultval, int defaultnoneifempty, char *source )
{
  static char dir[48+1+256+1];
  unsigned int i;
  char *p, *q, *s;
  const char *invdirmsg = "NetWare::Invalid directory specified for '%s'.\n"
                    "         Reverting to default directory specification\n";
  const char *none = "none";                  

  if (!defaultval) 
    defaultval="";
    
  if (!source || !*source)
    {
    if (defaultnoneifempty)
      {
      strcpy( dest, none );
      return 0;
      }
    if (!*defaultval)
      {
      *dest = 0;
      return 0;
      }
    source = defaultval;
    }
  if ( strcmpi( source, none )==0 )
    {
    if (!defaultnoneifempty)
      *dest = 0;
    else
      strcpy( dest, none );
    return 0;
    }

DebugPrintf2("CliValidateSinglePath: source: \"%s\"", source );
    {
    q = source;
    while (*q) 
      {
      if (*q=='\\')
        *q='/';
      q++;
      }
    q = strrchr( source, ':' );
    p = strrchr( source, '/' );

    if (q && q>p)
      {
      p = source+strlen(source)+1;
      while (*p!=':') 
        {
        *p=*(p-1); 
        p--; 
        }
      *(++p)='/';
      }  
    if (!p)
      strcpy(dir,".");
    else
      {
      *p = 0;
      i = chdir( source );
      *p = '/';
      s = source;
      source = p+1;
      if (i!=0 || getcwd( dir, sizeof(dir)-1 )==NULL )
        {
        printf(invdirmsg, s );
        strcpy( dir, "." );
        }
      else if (strcmp( dir, CliGetClientDirectory() )==0)
        strcpy( dir, "." );
      else
        {
        chdir( CliGetClientDirectory() );
        p = strchr( dir, '/' ); //find vol name
        if (memcmp( dir, CliGetClientDirectory(), (p+1)-dir)==0)
          {
          p++;
          q = dir;
          while (*p)
            *q++ = *p++;
          *q=0;
          }
        }
      }
    
    strcat( dir, "/" );
    strcat( dir, source );
    if (strlen(dir) >= destsize)
      {
      printf(invdirmsg, dir );
      strcpy( dir, "./" );
      strcat( dir, defaultval );
      }
    strcpy(dest,dir);
    }

DebugPrintf2("CliValidateSinglePath: dest: \"%s\"\r\n", dest );
  return 0;
}


/* ------------------------- used elsewhere ------------------------ */

extern "C" int GetFileServerName(int conn, char *buff);

//The NetWare client will run offline if TCP isn't detected, and thus 
//cannot rely on gethostname() being available. It returns the NetWare
//server name instead.
int CliGetHostName( char *buffer, unsigned int buflen )
{
  char *fname = "gethostname";
  int (*_gethostname)(char *, int ) = 
    (int (*)(char *, int))ImportSymbol( CliRetrieveNLMHandle(), fname );
  if (_gethostname)
    {
    int havehname = (((* _gethostname)(buffer, buflen)) == 0);
    UnimportSymbol( CliRetrieveNLMHandle(), fname );
    if (havehname) return (strlen(buffer));
    }
  char buff[64];
  buff[0]=0;
  GetFileServerName(0,buff); //proto in client.h (conn #, buf[48+1])
  int len=strlen(buff);
  if (len>buflen) len=buflen;
  if (len) strncpy(buffer,buff,len);
  buffer[len]=0;
  strlwr(buffer);
  return(len);
}    

/* ------------------------- used elsewhere ------------------------ */

// stat and fstat have problems if the directory is in cache/locked

int sizonly_stat( const char *filename, struct stat *statblk )
{
  int rescode = -1;
  int file;
  
  CliSetFileWasAccessedFlag();
  
  if (filename && filename[0] && statblk && (access(filename,0)==0) && 
    (file=sopen(filename, O_RDONLY|O_BINARY, SH_DENYNO, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP ))!=-1)
    {                          
    memset(statblk,0,sizeof(struct stat));
    statblk->st_size = filelength(file);  /* fcntl call or lseek */
    if (((signed long)(statblk->st_size))==-1 && lseek(file,0,SEEK_END)!=-1)
      statblk->st_size = tell(file);
    if (((signed long)(statblk->st_size))!=-1)
      rescode=0;
    close(file);
    }
  return rescode;
}

/* ------------------------- used elsewhere ------------------------ */

int unlink_checkpoint( const char *filename )
{
  FileHeader header;
  FILE *file = fopen(filename, "wb" ); //can't use write() for some reason
  if (file) 
    {
    header.lock = htonl( 64+0 );          
    header.count = htonl( 0 );          
    header.count = fwrite( &header, 1, sizeof(FileHeader), file );
    fclose(file);
    if (header.count == sizeof(FileHeader))
      return 0;
    }
  return -1;
}

extern "C" int ScanErasedFiles( char *path, long *nextEntryNumber, DIR *deletedFileInfo);
extern "C" int PurgeErasedFile( char *pathname, long sequenceNumber); 

//warning: change d_name to d_nameDOS if using SDK instead of Watcom includes
//double warning!: do not call it unlink! watcom clib remove() calls unlink()
int purged_unlink( const char *filename ) /* returns 0 on success */
{
  long entrynumber;
  DIR deletedFileInfo;
  DIR *readdirInfo;
  char dirname[255+1];
  int pathlen;
  int retcode=-1;

  if (strcmp(filename, CliGetClientPointer()->checkpoint_file[0])==0 ||
      strcmp(filename, CliGetClientPointer()->checkpoint_file[1])==0)
    return unlink_checkpoint( filename );

  if ((readdirInfo=opendir(filename))!=NULL)
    {
    if (readdir(readdirInfo)!=NULL)
      {
      if ((retcode=remove( filename ))==0)
        {
        dirname[0]=0;
    
        for (pathlen=strlen(filename);pathlen!=0;pathlen--)
          {
          if (filename[pathlen]=='\\' || filename[pathlen]=='/' || 
                        filename[pathlen]==':')
            {
            if (filename[pathlen]==':')
              pathlen++;
            memcpy(dirname,filename,pathlen);
            dirname[pathlen]=0;
            break;
            }
          }

        entrynumber=-1;
        while (ScanErasedFiles( dirname, &entrynumber, &deletedFileInfo )==0)
          {
          if (strcmp( readdirInfo->d_name, deletedFileInfo.d_name )==0)
            {
            dirname[pathlen]='\\';
            dirname[pathlen+1]=0;
            strcat( dirname, deletedFileInfo.d_name );
            PurgeErasedFile( dirname, entrynumber);
            break;
            }
          }
        }
      }
    closedir(readdirInfo);
    }
  return retcode;
}       

/* ------------------------------------------------------------------- */

static struct 
 {
 unsigned int timeslice;
 unsigned int yieldrate;
 unsigned int kps;
 } syncStats = {0};

static int yieldUp = 0;

static void CliScreenStatusUpdateProcedure( void )
{
  static union {
    char cStatusLine[80<<1];
    struct {
      char scrName[62<<1];
      char count1, cattr1, count2, cattr2, count3, cattr3, count4, cattr4, count5, cattr5;
      char blankx1, bxattr;
      char util1, uattr1, util2, uattr2, util3, uattr3, utuld, utilr;
      char util4, uattr4, util5, uattr5;
      char perc, pattr;
      char blank1, battr1, blank2, battr2;
      char heart, hattr;
      char blank3, battr3;
      } aStatusLine;
    } statusLine;

  static unsigned int hrepeat, lasttime = 0, lastutil;
  unsigned int util = CliGetSystemUtilization();
  unsigned int time = CliGetCurrentTicks();
  int health = 0;

  if (lasttime == 0)
    {
    statusLine.cStatusLine[0] = 0;
    lastutil = 0;
    hrepeat = 0;
    }
  else if ((time-lasttime) > 9)  //update interval
    {
    if ((++hrepeat)>=10) health = -1;
    }
  else 
    {
    hrepeat=0;
    if ((time-lasttime) < 9)
      health = 1; // should never happen
    }

  char *scrP;
  if ((scrP=CliGetScreenMemPointer())!=NULL)
    {
    int i,n;
    char *p, *r;

    if (SignalTriggered | UserBreakTriggered)    
      {
      if (statusLine.cStatusLine[0] != 0)
        {
        for (i=0;i<sizeof(statusLine.cStatusLine);i+=2)
          { *scrP++ = ' '; *scrP++ = 0; }
        statusLine.cStatusLine[0] = 0;
        }
      }
    else 
      {
      if (statusLine.cStatusLine[0] == 0)
        {
        p=CliGetScreenName();
        r=statusLine.cStatusLine;
        for (i=0;i<sizeof(statusLine.cStatusLine);i+=2)
          { *r++=((!*p)?(' '):(*p++)); *r++=(7<<4)+1; }
        lastutil = 0xffff;
        statusLine.aStatusLine.perc  = '%';
        }
      if (lastutil!=util)
        {
        lastutil = util;
        p=&statusLine.aStatusLine.util5;
        statusLine.aStatusLine.util1 = statusLine.aStatusLine.util2 = ' ';
        for (i=0;i<6;i++)
          {
          if (i==2)
            *p--='.';
          else
            {
            *p--=('0'+(util%10));
            if ((util/=10)==0 && i>2)
              break;
            }
          p--;
          }
        }

      statusLine.aStatusLine.heart = ((time%18<9)?(3):(' '));
      i = (7<<4)+((health<0)?(4):((health>0)?(2):(1))); //(blue, red, green)
      statusLine.aStatusLine.uattr1 = statusLine.aStatusLine.uattr2=
         statusLine.aStatusLine.uattr3 = statusLine.aStatusLine.pattr=i;
      statusLine.aStatusLine.hattr=((yieldUp)?((7<<4)+2):(i));  //green or (blue || red)

      p=statusLine.cStatusLine;
      for (i=0;i<(sizeof(statusLine.cStatusLine));i++)
        *scrP++ = *p++;
      }
    }

  lasttime = time;
  return;    
}

/* ------------------------------------------------------------------- */

static int CliCheckForUserBreak(void)
{
  int i;
  if (SignalTriggered | UserBreakTriggered)
    return 1;
  if (kbhit() && (((i=getch())==0x0 && getch()==0x03) || i==0x03))
    {
    SignalTriggered = UserBreakTriggered=1;  /* raise(SIGTERM); */
    CliSetScreenDestructionMode(0); //allow screen to stay open
    return 1;
    }
  return 0;
}  

/* ------------------------------------------------------------------- */

int CliRunProblemAsCallback( Problem *problem, int timeslice, int cpu_i, int niceness )
{
  static int hardslice, softslice, nice=-1;   //for speed
  struct callbacksched *cbs;
  u32 perc = problem->CalcPercent();
  u32 run=0; 

  if (nice==-1)
    {
DebugPrintf("CliRunProblemAsCallback: beginning first callback init\r\n");
    CliSetScreenStatusProcessor( CliScreenStatusUpdateProcedure );
DebugPrintf("CliRunProblemAsCallback: CliSetScreenStatusProcessor has been set\r\n");
    nice = niceness; //CliGetClientPointer()->niceness;
    softslice = GetTimesliceBaseline();
    hardslice = softslice-((softslice/5)*(3-nice));
    softslice = ((softslice+54)/100) << (nice); //was PIPELINE_COUNT << ( 2 + nice );
//ConsolePrintf("Timeslice on CPU 0: %d ... on other CPUs: %d\r\n", softslice, hardslice );
    }
  timeslice = softslice;
  
  if ( cpu_i >= MAX_IDLELOOP_CALLBACKS || /* nice==2 ||*/ CliIsThreadOnMP() || 
     (cbs=CliAllocIdleCallback(problem, timeslice, cpu_i))==NULL)
    {
    //if (CliIsThreadOnMP())//thread will be preemptive ("synthetic-preemption")
      timeslice = hardslice; 
DebugPrintf2("CliRunProblemAsCallback: Beginning NON-polling: %d\r\n",perc);
    while (!run && (!CliCheckForUserBreak()) &&
            (problem->CalcPercent() == perc))
       {     
       run = problem->Run( timeslice, cpu_i ); 
       CliRunnerSwitchLowPriority(); //ThreadSwitchLowPriority() if non-smp
       }                             //or thr_yield()/ThreadSwitch() if smp.
DebugPrintf("ended non-polling\r\n");
    }
  else
    {
DebugPrintf2("CliRunProblemAsCallback: Beginning polling: %d\r\n",perc);
    while ((run = cbs->run) == 0 && (!CliCheckForUserBreak()) &&
            (problem->CalcPercent() == perc))
      delay(2000);
    CliFreeIdleCallback( problem, timeslice, cpu_i );  
DebugPrintf("ended polling\r\n");
    }
  return run;
}  
