/*
 * misc (unsorted) stuff that gets called from client/common code.
 * for historical reasons, many of the functions are only stubs 
 * to real functions elsewhere
 *
 * written by Cyrus Patel <cyp@fb14.uni-mainz.de>
 *
*/

#ifdef __cplusplus
extern "C"
{
#endif
  #include <stdio.h>    /* sprintf() */
  #include <string.h>   /* strlen(), memcpy() */
  #include <unistd.h>   /* chdir() */
  #include <limits.h>   /* ULONG_MAX */
  #include <stdlib.h>   /* malloc(), free() */
  #include <process.h>  /* GetThreadID() */
  #include <nwcntask.h> /* SetCurrentConnection() */
  #include <nwfile.h>   /* SetTargetNameSpace(), SetCurrentNameSpace() */
  #include <nwadv.h>    /* AllocateResourceTag() */
#ifdef __cplusplus
}
#endif

#include "nwlemu.h"  /* kernel goodies */
#include "nwcmisc.h" /* ourselves */
#include "nwcpprun.h"
#include "nwcus.h"   /* nwCliUtilSuppressorControl */
#include "util.h"    /* utilGetAppName() */
#include "iniread.h" 

static struct
{
  int fullypreemptive;
  int restartablethreads;
  int nompforcrunch1;
  int pollallowed;
  int utilsupression;
} nwcstatics = {-1,-1,-1,-1,-1};

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

int nwCliGetPollingAllowedFlag(void)
{
  if (nwcstatics.pollallowed < 0)
  {
    nwcstatics.pollallowed = 1;
    if (GetNumberOfRegisteredProcessors()>1)
      nwcstatics.pollallowed = 0;
    //else if (GetFileServerMajorVersionNumber()>=4) 
    //  nwcstatics.pollallowed = 0;
  }    
  return (nwcstatics.pollallowed != 0);
}

int nwCliGetUtilizationSupressionFlag(void)
{
  return (nwcstatics.utilsupression > 0);
}

int nwCliAreCrunchersRestartable(void)
{
  return (nwcstatics.restartablethreads != 0); /* <0 == uninitialized */
}

int nwCliAllowCruncherToMP(int cruncher_index)
{
  return (cruncher_index != 1 || nwcstatics.nompforcrunch1 <= 0);
}

int nwCliIsPreemptiveEnv(void)
{
  if (nwcstatics.fullypreemptive > 0) /* <0 == uninitialized */
  {
    static int vermajor = -1;
    if (vermajor == -1)
      vermajor = GetFileServerMajorVersionNumber();
    return (vermajor >= 5);
  }
  return 0;
}

static void __nwCliLoadSettings(const char *inifile)
{
  if (access(inifile, 0)==0)
  {
    int i;
    const char *sect = "netware";
    if ((i=GetPrivateProfileIntB( sect,"fully-preemptive",-123,inifile))!=-123)
      nwcstatics.fullypreemptive = ((i == 0) ? (0) : (1));
    if ((i=GetPrivateProfileIntB( sect,"restartable-threads",-123,inifile))!=-123)
      nwcstatics.restartablethreads = ((i == 0) ? (0) : (1));
    if ((i=GetPrivateProfileIntB( sect,"nompforcrunch1",-123,inifile))!=-123)
      nwcstatics.nompforcrunch1 = ((i == 0) ? (0) : (1));
    if ((i=GetPrivateProfileIntB( sect,"use-polling-loop",-123,inifile))!=-123)
      nwcstatics.pollallowed = ((i == 0) ? (0) : (1));
    else if ((i=GetPrivateProfileIntB( sect,"pollallowed",-123,inifile))!=-123)
      nwcstatics.pollallowed = ((i == 0) ? (0) : (1));
    if ((i=GetPrivateProfileIntB( sect,"squelch-util-indicators",-123,inifile))!=-123)
      nwcstatics.utilsupression = ((i == 0) ? (0) : (1));

//ConsolePrintf("%s:fully-preemptive = %d\r\n", inifile, nwCliIsPreemptiveEnv() );
//ConsolePrintf("%s:restartable-threads = %d\r\n", inifile, nwCliAreCrunchersRestartable() );
////ConsolePrintf("%s:nompforcrunch1 = %d\r\n", inifile, !nwCliAllowCruncherToMP(1) );
//ConsolePrintf("%s:use-polling-loop = %d\r\n", inifile, nwCliGetPollingAllowedFlag() );
//ConsolePrintf("%s:squelch-util-indicators = %d\r\n", inifile, nwCliGetUtilizationSupressionFlag() );
  }
  return;
}  

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

int nwCliIsSMPAvailable(void)
{
  return NWSMPIsAvailable();
}

#if 0
int __nwCliIsThreadOnMP(void)
{
  static int (*_thr_type)(void) = ((int (*)(void))(0xffffffff));
  if (_thr_type == ((int (*)(void))(0xffffffff))) /* or "thr_type" */
    _thr_type = (int (*)())ImportSymbol(GetNLMHandle(), "thread_type" );
  if (_thr_type)    
    return (*_thr_type)();
  return 0;
}  
#endif

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

int nwCliRebootThread(int threadindex, void (*gomt)(void *), void *thrparams)
{
  if (nwCliAreCrunchersRestartable()) /* < 0 == uninitialized */
  {
    /* Try and circumvent NetWare's scheduling optimization for our
       threads. (NetWare sees us doing lots of work, and ends up
       rescheduling us quickly because it thinks we need it). By
       re-chaining to a child, we not only zero our cruncher's stats,
       we also give MP a chance to better shuffle things about.

       This is in a stub function so we can turn it on/off without
       mucking with common source.
    */
    int newid;
    NWThreadToNetWare();
    newid = BeginThread( gomt, NULL, 8192, (void *)thrparams );
    if (newid != -1)
      return newid;
    nwCliInitializeThread(threadindex);
  }
  return -1;
}

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

int nwCliCreateThread(void (*gomt)(void *), void *thrparams)
{
  return BeginThread( gomt, NULL, 8192, (void *)thrparams );
}

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

#if 0
  /* ThreadSwitch() is _always_ an SMP thread switch when SMP.NLM was loaded
     before CLIB. However, if smp.mdisabled then THREADS.NLM won't CLIB'ify 
     the other SMP apis and *both* NWSMPIsAvailable() and NWSMPIsLoaded() 
     return false.
     On NetWare 5, SMP is always loaded before CLIB, but on NetWare 4 this
     is not necessarily so, but I just assume it is.
  */
  if (NWSMPIsLoaded())
  {
    ThreadSwitch();
    ThreadSwitch();
  }
  else
  {
    int i=0;
    do
    { ThreadSwitchLowPriority(); 
    } while (((++i) < 10) && GetDiskIOsPending());
  }
#endif

static void __cruncher_switch_fixup(void); /* forward reference */
static void (*__cruncher_switch)(void) = __cruncher_switch_fixup;
static void __cruncher_switch_nw3(void)
{
  ThreadSwitch();
  //ThreadSwitch();
}
static void __cruncher_switch_nw456(void)
{
  ThreadSwitchLowPriority();
  //ThreadSwitchLowPriority(); /* == ThreadSwitch + Handicap=1 */
}
static void __cruncher_switch_smp(void)
{
  //ThreadSwitch();
  ThreadSwitchLowPriority();
}
static void __cruncher_switch_fixup(void)
{
  __cruncher_switch = &__cruncher_switch_nw456;
  if (GetFileServerMajorVersionNumber() < 4)
    __cruncher_switch = &__cruncher_switch_nw3;
  else if (NWSMPIsAvailable())
    __cruncher_switch = &__cruncher_switch_smp;
}
void nwCliThreadSwitchLowPriority(void)
{
  (*__cruncher_switch)();
  return;
}  
int nwCliInitializeThread(int threadindex /* 0 is main */)
{
  static struct
  {
    /* thread_bind is *not* the DECThreads one (ie, not "affinity") */
    int (*_thread_bind)(int, void *);
    void *(*_GetProcessorControlBlock)(unsigned int);
    void (*_thr_yield_to_mp)(void);
    int (*_thread_type)(void); /* (== thr_type()) */
    #if 0 /* caution: following thr_* functions are UNIX International Threads API */
    typedef unsigned int thread_t;
    int (*_thr_setprio)(thread_t, int prio)
    thread_t (*_thr_self)(void);
    int (*_thr_getprio)(thread_t, int *prio);
    #endif
    int (*_kBindThread)(int thrid, int cpuid);
    int (*_RunningProcess);
    int (*_NXThreadBind)(int cpuid);
  } vectors;
  static int initialized = -1;

  char namebuf[64];
  int thrid = GetThreadID();

  if (initialized == -1)
  {
    memset( (void *)&vectors, 0, sizeof(vectors));
    nwCliThreadSwitchLowPriority(); /* self initializing */
    initialized = 0;

    if (NWSMPIsAvailable())
    {
      unsigned int nlmHandle = GetNLMHandle();
      initialized = +1;

      #if 0
      vectors._thr_setprio = (int (*)(thread_t, int prio))
         ImportSymbol( nlmHandle, "thr_setprio" );
      vectors._thr_self = (thread_t (*)(void))
         ImportSymbol( nlmHandle, "thr_self" );
      vectors._thr_getprio = (int (*)(thread_t, int *))
         ImportSymbol( nlmHandle, "thr_getprio" );
      #endif
      /*--*/

      //vectors._thr_yield_to_mp = (void (*)(void))
      // ImportSymbol( nlmHandle, "thr_yield_to_mp" );

      if (GetFileServerMajorVersionNumber() >= 5)
      {    /* NKS doesn't support NetWare 4.11 thread_bind() */
           /* and on NW5 is really just a patched jump to kBindThread */
        vectors._NXThreadBind = (int (*)(int))
           ImportSymbol( nlmHandle, "NXThreadBind" );
      }
      vectors._kBindThread = (int (*)(int, int ))
         ImportSymbol( nlmHandle, "kBindThread" );
      vectors._RunningProcess = (int *)
         ImportSymbol( nlmHandle, "RunningProcess" );
      vectors._GetProcessorControlBlock = (void *(*)(unsigned int))
         ImportSymbol( nlmHandle, "GetProcessorControlBlock" );
      vectors._thread_type = (int (*)(void))
         ImportSymbol( nlmHandle, "thread_type" );
      vectors._thread_bind = (int (*)(int, void *))
         ImportSymbol( nlmHandle, "thread_bind" ); /* note: "thrEAD_" */
       
//ConsolePrintf("NW4: thread_type=%p thread_bind=%p GetProcessorControlBlock=%p\r\n",
//               vectors._thread_type, vectors._thread_bind,  vectors._GetProcessorControlBlock);
//ConsolePrintf("NW5: BindThread=%p RunningProcess=%p\r\n",
//               vectors._kBindThread, vectors._RunningProcess );
//ConsolePrintf("NW6: NXThreadBind=%p\r\n", vectors._NXThreadBind );
//     ThreadSwitch();               
    }
  }

  strncpy( namebuf, nwCliGetNLMBaseName(), 6 );
  namebuf[6] = '\0';
  strcat( namebuf, " " );

  if (threadindex == 0) /* Main */
  {
    strcat( namebuf, "Main" ); 
    RenameThread( thrid, namebuf );
  }
  else
  {
    const char *fmt = "crunch #%02x";
    if (threadindex > 0xff)
      fmt = "crunch %03x";
    sprintf( &namebuf[strlen(namebuf)], fmt, threadindex );
    RenameThread( thrid, namebuf );

    //SetThreadHandicap( thrid, 1 );

    if (nwCliAllowCruncherToMP(threadindex))
    {
      int bound = -1;
      unsigned int prefcpu = GetNumberOfRegisteredProcessors();
      if (prefcpu < 1) 
        prefcpu = 1;
      else
        prefcpu = prefcpu - ((threadindex-1) % prefcpu);


      NWThreadToNetWare(); /* flush */
      NWThreadToMP();
      #ifdef SMPify_EVEN_IF_MDISABLEd
      if (!NWSMPIsAvailable())   /* CLIB hasn't revectored SMP.NLM's exports */
      {                          /* meaning, the NWThreadToMP() was a NOP, */
        if (_thr_yield_to_mp)    /* but SMP.NLM has sooo many advantages, so */
          (*_thr_yield_to_mp)(); /* try and use it anyway */
      }
      #endif

      if (vectors._NXThreadBind) 
      {
        bound = 0;
//ConsolePrintf("%s: trying NKS SMP bind to preferred cpu %u...\r\n", namebuf, prefcpu-1 );
//ThreadSwitch();
        if (0 == ((*(vectors._NXThreadBind))( prefcpu )) ) // one based prefcpu
          bound = 1;
      }
      if (vectors._kBindThread && vectors._RunningProcess) /* NetWare 5 */
      {                                                   
        bound = 0;
//ConsolePrintf("%s: trying NW5 SMP bind to preferred cpu %u...\r\n", namebuf, prefcpu-1 );
//ThreadSwitch();
        if (0 == (*(vectors._kBindThread))( (*(vectors._RunningProcess)), prefcpu-1 ))
          bound = 1;
      }
      else if (vectors._thread_type && vectors._GetProcessorControlBlock && vectors._thread_bind)
      {                     /* restrict the bind to only smp'ified threads */
        bound = 0;
        if (((*vectors._thread_type)()))
        {
          void *cpu_pcb;                                  //zero based prefcpu
          cpu_pcb = (*vectors._GetProcessorControlBlock)( prefcpu-1 ); 
          if (cpu_pcb) 
          {
            if (((*vectors._thread_bind)( thrid, cpu_pcb )) == 0)
              bound = 1;
          }
        }
      }
//if (bound >= 0)      
//{
//  ConsolePrintf("%s: SMP bind to preferred cpu %u => %s\r\n", 
//   namebuf, prefcpu-1, ((bound==0)?("failed!"):("ok!")) );
//}   
    }
  }
  return 0;
}

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

static int  __DOWN_handler_control( int mode ); /* forward reference */
static void __DOWN_handler_event(LONG) {__DOWN_handler_control(0);}
static int  __DOWN_handler_control( int mode )
{
  static int seen_exit = -1;
  static int in_handler = 0;
  static int event_handle = -1;
  static int threadGroupID = -1;

  if (mode > 0) /* install */
  {
    int tgid = GetThreadGroupID();
    if (event_handle != -1)
      return 0;
    if (tgid == 0 || tgid == -1)
      return -1;
    event_handle = RegisterForEvent( EVENT_DOWN_SERVER, 
                                     __DOWN_handler_event, NULL );
    if (event_handle == -1)
      return -1;
    in_handler = 0;
    threadGroupID = tgid;
    seen_exit = 0;
  }
  else if (mode < 0) /* uninstall */
  {
    seen_exit = +1;
    if (event_handle != -1 /* && !in_handler */)
    {
      if (UnregisterForEvent( event_handle ) != 0)
        return -1;
      event_handle = -1;
    }
  }
  else /* the event itself */ 
  {
    in_handler = 1;
    if (seen_exit == 0 && threadGroupID != -1)
    {
      unsigned long lasttime;
      unsigned int ticks = ((20*182)/10); /* 20 seconds */
      int tgid = SetThreadGroupID(threadGroupID);
      RaiseExitRequestTrigger();
      ConsolePrintf("%s: waiting for threads to end...\n", 
                                       nwCliGetNLMBaseName());
      SetThreadGroupID(tgid);
      while (ticks && seen_exit == 0)
      {
        unsigned long currtime = GetCurrentTime();
        if (lasttime != currtime)
          ticks--;
        lasttime = currtime;
        CYieldWithDelay(); /* sleep allowed! */
      }
    }
    in_handler = 0;
  }
  return 0;
}

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

unsigned long nwCliGetPollingProcedureResourceTag(void)
{
  static unsigned long plrpResourceTag = 0xfffffffful;
  if (plrpResourceTag == 0xfffffffful)
  {
    unsigned int thrgrid = GetThreadGroupID();
    if (thrgrid != -1 && thrgrid != 0)
    {
      char *cadd ="AddPollingProcedureRTag";
      char *crem = "RemovePollingProcedure";
      unsigned int nlmHandle = GetNLMHandle();
      void *add = ImportSymbol( nlmHandle, cadd );
      void *rem = ImportSymbol( nlmHandle, crem );

      if (!add || !rem)
        plrpResourceTag = 0;
      else
      {
        char *symname = "AllocateResourceTag";
        unsigned long (*allocrt)(unsigned long,unsigned char *,unsigned long);
        allocrt = (unsigned long (*)(unsigned long,unsigned char *,
              unsigned long))ImportSymbol(nlmHandle, symname );
        if (allocrt)
        {
          unsigned char *rtname = (unsigned char *)("Polling Procedure");
          plrpResourceTag = (*allocrt)(nlmHandle, rtname, 'RPLP' ); 
          UnimportSymbol( nlmHandle, symname );
        }  
      }
      if (add)
        UnimportSymbol( nlmHandle, cadd );
      if (rem)
        UnimportSymbol( nlmHandle, crem );
    }
  }
  if (plrpResourceTag != 0xfffffffful && plrpResourceTag != 0)
    return plrpResourceTag; 
  return 0;
}

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

const char *__nwCliSetNLMBaseName(const char *argv0)
{
  static int initialized = -1;
  static char basename[32];

  if (initialized < 0)
  {
    int pos = GetThreadGroupID();
    if (pos != -1 && pos != 0)
    {
      char buff[128+1];
      if (GetNLMNameFromNLMID( GetNLMID(), basename, buff ) == 0)
      {
        pos = 0;
        while (basename[pos] && basename[pos]!='.')
        {
          if (basename[pos] >= 'a' && basename[pos] <= 'z')
            basename[pos] -= ('a'-'A');
          pos++;
        }
        basename[pos] = '\0';
        if (pos)
          initialized = 1;
      }
    }
    if (initialized < 0)
    {
      if (argv0)
      {
        pos = strlen(argv0);
        while (pos > 0)
        {
          if (argv0[pos-1]=='/' || argv0[pos-1]=='\\' || argv0[pos-1]==':')
          {
            argv0+=pos;
            break;
          }
          pos--;
        }
        pos = 0;
        while (pos < (sizeof(basename)-1))
        {
          basename[pos] = *argv0++;
          if (!basename[pos] || basename[pos]!='.')
            break;
          if (basename[pos] >= 'a' && basename[pos] <= 'z')
            basename[pos] -= ('a'-'A');
          pos++;
        }
        basename[pos] = '\0';
        if (pos)
          initialized = 1;
      }
    }
    if (initialized < 0)
    {
      initialized = 0; /* recursion protection */
      argv0 = utilGetAppName();
      pos = 0;
      while (pos < (sizeof(basename)-1))
      {
        basename[pos] = *argv0++;
        if (basename[pos] >= 'a' && basename[pos] <= 'z')
          basename[pos] -= ('a'-'A');
        pos++;
      }  
      initialized = 1;
    }
  }
  if (initialized > 0)
    return (const char *)&basename[0];
  return "";
}

const char *nwCliGetNLMBaseName(void)
{
  return __nwCliSetNLMBaseName(NULL);
}

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

int nwCliInitClient( int argc, char **argv)
{
  register char *p,*q;
  char basename[32];
  unsigned int len;

  ConsolePrintf("\r");     /* why can't *someone* fix console? */
  ThreadSwitchWithDelay();

  if (argv)
    __nwCliSetNLMBaseName(argv[0]);
  if (strcmpi(nwCliGetNLMBaseName(),utilGetAppName())!=0)
  {
    ConsolePrintf("%s: Hey dude!, fix the makefile! %s.NLM != %s.NLM\n",
                  nwCliGetNLMBaseName(), utilGetAppName(), 
                  nwCliGetNLMBaseName() );
    return -1;                  
  }

  #if 0
  {
    char x[64];
    if (GetSetableParameterValue( 0, (unsigned char *)"SMP NetWare Kernel Mode", (void *)&x[0] ) != 0)
      strcpy(x,"*failed*");                             
    ConsolePrintf("SMP NetWare Kernel Mode = \"%s\"\n", x );
  }
  #endif

  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? */

  __DOWN_handler_control( +1 /* initialize */ );
  nwCliGetNLMBaseName(); /* self-initializing */
  nwCliGetPollingProcedureResourceTag(); /* self-initializing */

  if (argv && argc)
  {
    char inipath[65];
    basename[0] = '\0';
    if (argv[0])
    {
      q = (char *)0;
      p = argv[0];
      len = 0;

      while (*p)
      {
        if (*p == ':' || *p == '/' || *p == '\\')
          q = p;
        p++;
      }
      p = argv[0];
      if (q != ((char *)0))
      {
        char *apppath;
        len = q-p;
        if (*q == ':')
          len++;
        apppath = (char *)malloc( len + 1 );
        if (apppath != ((char *)0))
        {
          memcpy( (void *)apppath, p, len );
          apppath[len] = '\0';
          chdir( apppath );
          free((void *)apppath);
        }
        p = q+1;
      }
      len = 0;
      while (*p && *p!='.' && len < sizeof(basename)-1)
        basename[len++] = (const char)(*p++);
      basename[len] = '\0';
    }

    if (basename[0] == '\0')
    {
      strncpy( basename, nwCliGetNLMBaseName(), sizeof(basename));
      basename[sizeof(basename)-1] = '\0';
    }
    strcat(strcpy(inipath,basename),".ini");
    
    __nwCliLoadSettings(inipath);
  }

  nwCliInitializeThread( 0 /* 0 is main */);
  nwCliInitPollingProcedures();
  nwCliUtilSuppressorControl(+1); /* initialize */
  return 0;
}

int nwCliExitClient( void )
{
  __DOWN_handler_control( -1 /* deinitialize */ );
  nwCliUninitPollingProcedures(); /* this must be clib-call free */
  nwCliUtilSuppressorControl(-1); /* deinitialize */
  return 0;
}  

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

#if 0 /* now in nwcpprun.cpp */
static void __doNothing(void) {} /* for resetting polling loop counters */
void __nwCliResetProfiling(void)
{
  static unsigned long plrpResourceTag = 0xfffffffful;
  static int (*_RemovePollingProcedure)( void (*proc)(void) ); 
  static int (*_AddPollingProcedureRTag)( void (*proc)(void), int resTag );

  if (plrpResourceTag == 0xfffffffful)
  {                      /* resetting the polling procedure counters */
    #if 0
    if (NWSMPIsLoaded()) /* is a senseless undertaking when under SMP */
    {
      _RemovePollingProcedure = ((int (*)(void (*)(void)))0);
      _AddPollingProcedureRTag = ((int (*)(void (*)(void), int))0);
      plrpResourceTag = 0;
    }
    else
    #endif
    {
      unsigned char *rtname = (unsigned char *)("Polling Procedure");
      unsigned int nlmHandle = GetNLMHandle();

      _AddPollingProcedureRTag = (int (*)(void (*)(void),int))
                     ImportSymbol( nlmHandle, "AddPollingProcedureRTag" );
      _RemovePollingProcedure = (int (*)(void (*)(void)))
                     ImportSymbol( nlmHandle, "RemovePollingProcedure" );

      if (_AddPollingProcedureRTag && _RemovePollingProcedure)
        plrpResourceTag = AllocateResourceTag( nlmHandle, rtname, 'RPLP' ); 
      else
        plrpResourceTag = 0;

      if ( plrpResourceTag == 0 )
      {
        if (_AddPollingProcedureRTag)
        {
          _AddPollingProcedureRTag = (int (*)(void (*)(void),int))0;
          UnimportSymbol( nlmHandle, "AddPollingProcedureRTag" );
        }
        if (_RemovePollingProcedure)
        {
          _RemovePollingProcedure = (int (*)(void (*)(void)))0;
          UnimportSymbol( nlmHandle, "RemovePollingProcedure" );
        }
      }
    }
  }  
  if (plrpResourceTag && _RemovePollingProcedure 
                      && _AddPollingProcedureRTag )
  {
    static unsigned long lastResetTime = 0xfffffffful;
    unsigned long currTime = GetCurrentTicks();
    register int needreset = 0;
    #define SECS2TICKS(__t) ((182*(__t))/10)
    if (lastResetTime == 0xfffffffful && currTime > SECS2TICKS(600)) /* begin 20s */
      lastResetTime = ((currTime+SECS2TICKS(20))-SECS2TICKS(600)); /* after first call */
    else if ((currTime < lastResetTime) || ((currTime-lastResetTime) > SECS2TICKS(600)))
      needreset = 1;
    else if ((currTime-lastResetTime) > SECS2TICKS(20)) 
      needreset = (GetProcessorUtilization() > 90);
    if (needreset)
    {
      lastResetTime = currTime;
      if ((*_AddPollingProcedureRTag)( __doNothing, plrpResourceTag) == 0)
        (*_RemovePollingProcedure)( __doNothing );
    }
  }                          
  return;
}
#endif

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

int nwCliIsNetworkAvailable(int dummy)
{
  dummy = dummy;
  return (FindNLMHandle("TCPIP.NLM") != 0);
}  

unsigned int nwCliGetNumberOfProcessors(void)
{
  return GetNumberOfRegisteredProcessors();  
}

