//-----------------------------------------------------------------------------
// Copyright (c) 1994,1995 Southeastern Universities Research Association,
//                         Continuous Electron Beam Accelerator Facility
//
// This software was developed under a United States Government license
// described in the NOTICE file included as part of this distribution.
//
//-----------------------------------------------------------------------------
//
// Description:
//       cdevSystem class (implementation)
//
// Author:  Jie Chen, Chip Watson & Walt Akers
//
// Revision History:
//   cdevSystem.cc,v
// Revision 1.20  1998/02/13  14:23:06  chen
// add fall through service behaviour
//
// Revision 1.19  1998/02/13  14:03:40  chen
// check timer queue in poll
//
// Revision 1.18  1998/02/10  18:05:47  chen
// add add/removeTimer to the system
//
// Revision 1.17  1997/12/22  18:23:06  akers
// Ongoing development of 1.6.2
//
// Revision 1.16  1997/12/12  16:39:42  chen
// add fix for VMS
//
// Revision 1.15  1997/08/27  18:23:32  chen
// Change error reporting to site specific scheme
//
// Revision 1.14  1997/08/07  13:36:33  akers
// Converted CDEV_MAJOR_VERSION and CDEV_MINOR_VERSION to strings to support adding minor revision numbers
//
// Revision 1.13  1997/03/03  17:35:45  chen
// add buffering to channel access connection
//
// Revision 1.11  1997/01/29  17:58:42  akers
// Patch for notifyService
//
// Revision 1.10  1997/01/29  17:39:35  akers
// Removed assertion from cdevSystem
//
// Revision 1.9  1996/03/22  17:57:06  chen
// add cdevFdChangedCallback
//
// Revision 1.8  1995/10/26  14:33:32  akers
// Removal of Warnings
//
// Revision 1.7  1995/10/03  20:07:33  chen
// use cdevGlobalTagTable class
//
// Revision 1.6  1995/08/17  19:41:05  danjin
// added missing %s in printf command
//
// Revision 1.5  1995/07/19  20:32:43  akers
// Added command cdevGlobalTagTable.initialize() to the constructor
//
// Revision 1.4  1995/07/14  11:51:39  akers
// Modifications to support changes in cdevData class.
//
// Revision 1.3  1995/07/05  18:45:32  chen
// allow access to devices etc...
//
// Revision 1.2  1995/06/30  16:06:25  chen
// remove all unnecessary files and use a genric list and hash
//
// Revision 1.1.1.1  1995/06/16  17:14:08  epics
// initial import of cdev
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <assert.h>
#include <string.h>

#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#endif

#include <cdevDevice.h>
#include <cdevRequestObject.h>
#include <cdevErrReqObject.h>
#include <cdevDirectory.h>
#include <cdevGroup.h>
#include <cdevExecGroup.h>
#include <cdevSvcFinder.h>
#include <cdevErrSvc.h>
#include <cdevUserFdService.h>
#include <cdevErrCode.h>
#include <cdevData.h>
#include <cdevCallback.h>
#include <cdevClock.h>
#include <cdevGlobalTagTable.h>
#include <cdevConfigFinder.h>

#include "cdevService.h"
#include "cdevSystem.h"

const char * cdevSystem::CDEV_MAJOR_VERSION ="1";
const char * cdevSystem::CDEV_MINOR_VERSION ="7.2";

// *****************************************************************************
// * These inline functions are defined here for use in this module.
// * max and min are also defined in WINDEF.H in WIN Platform
// *****************************************************************************
#ifndef max
inline int max (int a, int b) { return a>b?a:b; }
#endif

#ifndef min
inline int min (int a, int b) { return a<b?a:b; }
#endif

// *****************************************************************************
// * Hash table size for devices
// *****************************************************************************
const int DEVICE_TABLE_SIZE=997;

// *****************************************************************************
// * Initialize a default system
// *****************************************************************************
cdevSystem&defaultSys=cdevSystem::defaultSystem();

// *****************************************************************************
// * Default callback function
// *****************************************************************************
static void defaultCallback(int, void*, cdevRequestObject&, cdevData&)
{
#ifdef _CDEV_DEBUG
  printf("default callback called\n");
#endif
}

#ifdef _WIN32
// *****************************************************************************
// * Window Socket Initialize and cleanup part inside dllMain code
// *****************************************************************************
BOOL WINAPI DllMain (HINSTANCE hi, DWORD reason, LPVOID ptr)
{
  WORD wVersion;
  WSADATA wsaData;

  wVersion = MAKEWORD (2, 0); /* WinSock 2.0 */
  switch (reason) {
  case DLL_PROCESS_ATTACH:
    if (WSAStartup (wVersion, &wsaData) != 0) 
		{
	    WSACleanup ();
	    return FALSE;
    }
    break;
  case DLL_PROCESS_DETACH:
    if (WSACleanup () != 0)
      return FALSE;
    break;
  };
  return TRUE;
}
#endif

// *****************************************************************************
// * cdevSystem::cdevSystem :
// *	This is the constructor for the cdevSystem class.   It initializes the
// *	internal variables and prepares the cdevSystem object for operation.
// *****************************************************************************
cdevSystem::cdevSystem(char *systemName, char *prefix)
: cdevSystemBase(), serviceList_(), 
  deviceList_(DEVICE_TABLE_SIZE, cdevStrHashFunc), 
  groupList_(), activeGroupList_(), activeGrps_(0), refCount_(1), 
  fdCbkList_(), fdCbkArgList_(), timerQueue_ (), defaultSvc_ (1)
{
#ifdef _TRACE_OBJECTS
  printf("         Create cdevSystem Class\n");
#endif
  systemName_=new char[::strlen(systemName)+1];
  ::strcpy(systemName_, systemName);
	
  if(prefix)
    {
      prefix_=new char[::strlen(prefix)+1];
      ::strcpy(prefix_, prefix);
    }
  else
    prefix_=0;

  // *********************************************************************
  // * Initialize the cdevGlobalTagTable
  // *********************************************************************
  cdevGlobalTagTable::tagTable();

  // *********************************************************************
  // * Add this system to system list
  // *********************************************************************
#ifdef _CDEV_DEBUG
  printf("Add system %s to system list\n", systemName_);
#endif
  cdevSystem::systemList_().add((void *) this);

  // *********************************************************************
  // * Create ns_ first, delete it last
  // *********************************************************************
  ns_=new cdevDirectory(*this);

  // *********************************************************************
  // * Reset error request object
  // *********************************************************************
  errObj_=0;

  // *********************************************************************
  // * Create service finder object
  // *********************************************************************
  svcFinder_=new cdevSvcFinder(*this);

  // *********************************************************************
  // * Create error service handler
  // *********************************************************************
  errSvcObj_=new cdevErrSvc(*this);

  // *********************************************************************
  // * Touch transaction object, waste memory but I have to touch this
  // *********************************************************************
  defXobj_=new cdevTranObj();
  defCallbackObj_=new cdevCallback(defaultCallback, 0);
  egroup_ = new cdevExecGroup (errSvcObj_, 2, *this);

  // *********************************************************************
  // * Check site specific configuration such as error reporting mechanism
  // *********************************************************************  
  configFinder_ = new cdevConfigFinder (*this);
  configFinder_->loadConfig ();
}


// *****************************************************************************
// * cdevSystem::~cdevSystem :
// *	This is the destructor for the cdevSystem object.  It will delete any
// * 	data that was allocated to the class.
// *****************************************************************************
cdevSystem::~cdevSystem(void)
{
#ifdef _TRACE_OBJECTS
  printf("         Delete cdevSystem Class\n");
#endif
  
  if (systemName_ == 0)
    return;

  // *********************************************************************
  // * Remove this system from the list
  // *********************************************************************
  cdevSystem::remove(this);

  // *********************************************************************
  // * Free all memory associated with this syetem
  // *********************************************************************
#if !defined (_WIN32)
  // On Win32, a dynamic loaded dll (services and requestobjects) will be
  // detached before code reach here when programs quit.
  freeMemory();
#endif
	
  delete[]systemName_;
  if(prefix_)
    delete[]prefix_;
	
  activeGrps_=0;

  // *********************************************************************
  // * ns_ will be removed from device list
  // * errObj_ will be deleted through device list
  // * but I have to remove service finder
  // *********************************************************************
  delete svcFinder_;

  // *********************************************************************
  // * remove errSvc Object. 
  // * Lock removal since errSvcObj_ is not registered
  // *********************************************************************
  errSvcObj_->unregOn_=0;
  delete errSvcObj_;

  // *********************************************************************
  // * remove transaction object and more
  // *********************************************************************
  delete defXobj_;
  delete defCallbackObj_;

  // *********************************************************************
  // * remove configuration finder
  // *********************************************************************  
  delete configFinder_;
}


// *****************************************************************************
// * cdevSystem::attachRef :
// *	This method allows the caller to obtain a reference to a specific
// *	cdevSystem object.
// *****************************************************************************
cdevSystem & 
cdevSystem::attachRef(char *name, char *prefix)
{
  return *cdevSystem::attachPtr(name, prefix);
}


// *****************************************************************************
// * cdevSystem::attachPtr :
// *	This method allows the caller to obtain a pointer to a specific
// *	cdevSystem object.
// *****************************************************************************
cdevSystem * 
cdevSystem::attachPtr(char *name, char *prefix)
{
  cdevSystem *newSystem=0;
	
  cdevSlistIterator sit(cdevSystem::systemList_());
  int found=0;
	
  for(sit.init(); !sit; ++sit)
    {
      newSystem=(cdevSystem *) sit();
      if(::strcmp(name, newSystem->name()) == 0)
	{
	  if(prefix && newSystem->prefix() && ::strcmp(prefix, newSystem->prefix()) == 0)
	    found=1;
	  else if(!prefix && !newSystem->prefix())
	    found=1;
	  else
	    found=0;
	}
      if(found)
	break;
    }
  if(found)
    {
      newSystem->refCount_++;
      return newSystem;
    }
  else
    return(new cdevSystem(name, prefix));
}


// *****************************************************************************
// * cdevSystem::getDevice :
// *	This method allows the caller to obtain a pointer to a specified
// *	cdevDevice object.
// *****************************************************************************
cdevDevice * 
cdevSystem::getDevice(char *name)
{
  cdevDevice *dev=0;
  dev=cdevDevice::attachPtr(name, *this);
  return dev;
}


// *****************************************************************************
// * cdevSystem::prefix :
// *	This method returns the prefix string which has been associated with
// *	this cdevSystem object.
// *****************************************************************************
char * 
cdevSystem::prefix(void) const
{
  return prefix_;
}


// *****************************************************************************
// * cdevSystem::prefix :
// *	This method allows the caller to specify the prefix string which will
// *	be associated with this cdevSystem object.
// *****************************************************************************
void 
cdevSystem::prefix(char *pre)
{
  if(prefix_)
    {
      delete[]prefix_;
      prefix_=0;
    }
  prefix_=new char[::strlen(pre)+1];
  ::strcpy(prefix_, pre);
}


// *****************************************************************************
// * cdevSystem::freeMemory :
// *	This method will delete all devices and services that are associated
// *	with this cdevSystem object.
// *****************************************************************************
void
cdevSystem::freeMemory(void)
{
  // *********************************************************************
  // * Delete all devices
  // *********************************************************************
  cdevStrHashIterator sit1(deviceList_);
  cdevDevice *tdev=0;
  for(sit1.init(); !sit1; ++sit1)
    {
      // *************************************************************
      // * Turn off unregister flags off so device destructor will not 
      // * remove itself again
      // *************************************************************
      tdev=(cdevDevice *) sit1();
      tdev->unregOn_=0;
#ifdef _CDEV_DEBUG
      printf("Free device %s\n", tdev->name());
#endif
      delete tdev;
    }

  // *********************************************************************
  // * Delete all services
  // *********************************************************************
  cdevSlistIterator sit(serviceList_);
  cdevService *tsvc=0;
  for(sit.init(); !sit; ++sit)
    {
      // *************************************************************
      // * Turn off unregister flags off so service destructor will 
      // * not remove itself again
      // *************************************************************
      tsvc=(cdevService *) sit();
      tsvc->unregOn_=0;
#ifdef _CDEV_DEBUG
      printf("Free service %s\n", tsvc->name());
#endif
      delete tsvc;
    }
  // *********************************************************************
  // * Don't delete groups, groups are dynamic variables
  // * Don't worry about active group
  // *********************************************************************
}


// *****************************************************************************
// * cdevSystem::defaultSystem :
// *	This method returns a static cdevSystem object that is used as the 
// *	default cdevSystem object.
// *****************************************************************************
cdevSystem & cdevSystem::defaultSystem(void)
{
  // *********************************************************************
  // * Make sure default system constructed before being used
  // *********************************************************************
  static cdevSystem sys("cdevDefaultSystem");
  return sys;
}

// *****************************************************************************
// * cdevSystem::finalize
// *	This method clean up system memory and everything
// *	Any calls after this will have unexpected behaviour
// *****************************************************************************
void
cdevSystem::finalize (void)
{
  // *********************************************************************
  // * Remove this system from the list
  // *********************************************************************
  cdevSystem::remove(this);

  // *********************************************************************
  // * Free all memory associated with this syetem
  // *********************************************************************
  freeMemory();
	
  delete[]systemName_;
  systemName_ = 0;

  if(prefix_)
    delete[]prefix_;
	
  activeGrps_=0;

  // *********************************************************************
  // * ns_ will be removed from device list
  // * errObj_ will be deleted through device list
  // * but I have to remove service finder
  // *********************************************************************
  delete svcFinder_;

  // *********************************************************************
  // * remove errSvc Object. 
  // * Lock removal since errSvcObj_ is not registered
  // *********************************************************************
  errSvcObj_->unregOn_=0;
  delete errSvcObj_;

  // *********************************************************************
  // * remove transaction object and more
  // *********************************************************************
  delete defXobj_;
  delete defCallbackObj_;

  // *********************************************************************
  // * remove configuration finder
  // *********************************************************************  
  delete configFinder_;
}

// *****************************************************************************
// * cdevSystem::nameServer :
// *	This method returns a pointer to a cdevDirectory object that is used
// *	by the cdevSystem to perform directory operations.
// *****************************************************************************
cdevDirectory & 
cdevSystem::nameServer(void)
{
  return *ns_;
}


// *****************************************************************************
// * cdevSystem::errorRequestObject :
// *	This method returns an error request object that will be used whenever
// *	a caller creates a cdevRequestObject using an invalid device/message
// *	combination.
// *****************************************************************************
cdevRequestObject * 
cdevSystem::errorRequestObject(void)
{
  if(errObj_)
    return errObj_;
  else
    return new cdevErrReqObject(*this);
}


// *****************************************************************************
// * cdevSystem::errorRequestObject :
// *	This method allows the caller to specify the error request object that 
// *	will be used to process invalid device/message combinations.
// *****************************************************************************
void 
cdevSystem::errorRequestObject(cdevRequestObject *obj)
{
  // obj = 0;
  errObj_=obj;
}


// *****************************************************************************
// * cdevSystem::systemList_ :
// *	This method allows the caller to retrieve a list of cdevSystem objects
// *	that have been instanciated.
// *****************************************************************************
cdevSlist &  
cdevSystem::systemList_(void)
{
  // *********************************************************************
  // * Make sure static list constructed before being used
  // *********************************************************************
  static cdevSlist list;
  return list;
}


// *****************************************************************************
// * cdevSystem::name :
// *	This method allows the caller to obtain the name of this cdevSystem
// *	object.
// *****************************************************************************
char * 
cdevSystem::name(void) const
{
  return systemName_;
}


// *****************************************************************************
// * cdevSystem::remove :
// *	This method allows the caller to remove a specific cdevSystem object
// *	from the internal system list.
// *****************************************************************************
int 
cdevSystem::remove(cdevSystem *system)
{
#ifdef _CDEV_DEBUG
  printf("remove system %s from the list\n", system->name());
#endif
  cdevSystem::systemList_().remove((void *) system);
  return CDEV_SUCCESS;
}


// *****************************************************************************
// * cdevSystem::registerService :
// *	This method is used to register a service with the cdevSystem object.
// *****************************************************************************
int 
cdevSystem::registerService(cdevService *service)
{
  if(!serviceList_.includes((void *) service))
    {
#ifdef _CDEV_DEBUG
      printf("register new service %s\n", service->name());
#endif
      serviceList_.add((void *) service);
      return CDEV_SUCCESS;
    }
  else
    {
#ifdef _CDEV_DBUG
      printf("this service %s is already here\n", service->name());
#endif
      return CDEV_ERROR;
    }
}


// *****************************************************************************
// * cdevSystem::removeService :
// *	This method is used to remove a service that has been registered with
// *	with this cdevSystem object.
// *****************************************************************************
int 
cdevSystem::removeService(cdevService *service)
{
#ifdef _CDEV_DEBUG
  printf("remove service %s from the list\n", service->name());
#endif
  serviceList_.remove((void *) service);
  return CDEV_SUCCESS;
}


// *****************************************************************************
// * cdevSystem::serviceCreated :
// *	This method allows the caller to determine if a service has been 
// *	created and installed in the service list for this cdevSystem object.
// *****************************************************************************
int 
cdevSystem::serviceCreated(char *serviceName)
{
  cdevSlistIterator sit(serviceList_);
  int found=0;
  cdevService *tsvc=0;
	
  for(sit.init(); !sit; ++sit)
    {
      tsvc=(cdevService *) sit();
      if(::strcmp(tsvc->name(), serviceName) == 0)
	{
#ifdef _CDEV_DEBUG
	  printf("This service %s has been created\n", tsvc->name());
#endif
	  found=1;
	  break;
	}
    }
  return found;
}


// *****************************************************************************
// * cdevSystem::service :
// *	This method allows the caller to get a pointer to a cdevService object 
// *	by providing the name of the service.
// *****************************************************************************
cdevService * 
cdevSystem::service(char *serviceName)
{
  cdevSlistIterator sit(serviceList_);
  int found=0;
  cdevService *obj=0;
	
  for(sit.init(); !sit; ++sit)
    {
      obj=(cdevService *) sit();
      if(::strcmp(obj->name(), serviceName) == 0)
	{
	  found=1;
	  break;
	}
    }
  if(found)
    return obj;
  else
    return 0;
}


// *****************************************************************************
// * cdevSystem::loadService :
// *	This method causes the cdevSystem object to load the specified service.
// *****************************************************************************
cdevService * 
cdevSystem::loadService(char *serviceName)
{
  cdevService *svc=0;
  int status=svcFinder_->getService(serviceName, svc);
  if(status == 0)
    return svc;
  else
    return 0;
}


// *****************************************************************************
// * cdevSystem::suspendService :
// *	This method causes the cdevSystem object to suspend processing of the
// *	specified service's file descriptors.
// *****************************************************************************
int 
cdevSystem::suspendService(cdevService * /* service */)
{
  return CDEV_SUCCESS;
}


// *****************************************************************************
// * cdevSystem::resumeService :
// *	This method causes the cdevSystem object to resume processing 
// *	events on the specified service's file descriptors.
// *****************************************************************************
int
cdevSystem::resumeService(cdevService * /* service */)
{
  return CDEV_SUCCESS;
}


// *****************************************************************************
// * cdevSystem::registerDevice :
// *	This method is used to register the specified cdevDevice with the
// *	the cdevSystem object ( if the device is not already registered).
// *****************************************************************************
int 
cdevSystem::registerDevice(cdevDevice *dev)
{
  if(!deviceList_.find((char *) dev->name(), (void *) dev))
    {
#ifdef _CDEV_DEBUG
      printf("add new device %s\n", dev->name());
#endif
      deviceList_.add((char *) dev->name(), (void *) dev);
      return CDEV_SUCCESS;
    }
  else
    {
#ifdef _CDEV_DEBUG
      printf("this device %s is already here: Error++++\n", dev->name());
#endif
      return CDEV_ERROR;
    }
}


// *****************************************************************************
// * cdevSystem::removeDevice :
// *	This method will remove the named device from the device list that is
// *	maintained by the cdevSystem object.
// *****************************************************************************
int 
cdevSystem::removeDevice(cdevDevice *dev)
{
#ifdef _CDEV_DEBUG
  printf("remove device %s\n", dev->name());
#endif
  deviceList_.remove((char *) dev->name(), (void *) dev);
  return CDEV_SUCCESS;
}


// *****************************************************************************
// * cdevSystem::deviceCreated :
// *	This method returns a boolean value that indicates if the cdevDevice
// *	specified by device name has been created already.
// *****************************************************************************
int 
cdevSystem::deviceCreated(char *deviceName)
{
  cdevSlist &list=deviceList_.bucketRef(deviceName);
  cdevSlistIterator sit(list);
  cdevDevice *tdev=0;
  int found=0;
	
  for(sit.init(); !sit; ++sit)
    {
      tdev=(cdevDevice *) sit();
      if(::strcmp(tdev->name(), deviceName) == 0)
	{
#ifdef _CDEV_DEBUG
	  printf("this device %s is already created\n", tdev->name());
#endif
	  found=1;
	  break;
	}
    }
  return found;
}


// *****************************************************************************
// * cdevSystem::device :
// *	This method allows the caller to obtain a cdevDevice object using the 
// *	name of the device.
// *****************************************************************************
cdevDevice * 
cdevSystem::device(char *deviceName)
{
  cdevSlist &list=deviceList_.bucketRef(deviceName);
  cdevSlistIterator sit(list);
  int found=0;
  cdevDevice *obj=0;
	
  for(sit.init(); !sit; ++sit)
    {
      obj=(cdevDevice *) sit();
      if(::strcmp(obj->name(), deviceName) == 0)
	{
	  found=1;
	  break;
	}
    }
  if(found)
    return obj;
  else
    return 0;
}


// *****************************************************************************
// * cdevSystem::registerGroup :
// *	This method allows the caller to register a new group with the 
// *	cdevSystem object.
// *****************************************************************************
int 
cdevSystem::registerGroup(cdevGroup *grp)
{
  if(groupList_.count() < MAX_NUM_GROUPS && !groupList_.includes((void *) grp))
    {
#ifdef _CDEV_DEBUG
      printf("Add new group\n");
#endif
      groupList_.add((void *) grp);
      return CDEV_SUCCESS;
    }
  else
    {
#ifdef _CDEV_DEBUG
      printf("Exceeds max num of group limit: Error++++++\n");
#endif
      return CDEV_ERROR;
    }
}


// *****************************************************************************
// * cdevSystem::removeGroup : 
// * This method allows the caller to remove a cdevgroup object from the 
// *	list that is maintained within the cdevSystem object.
// *****************************************************************************
int 
cdevSystem::removeGroup(cdevGroup *grp)
{
#ifdef _CDEV_DEBUG
  printf("Remove a group\n");
#endif
  groupList_.remove((void *) grp);
  activeGroupList_.remove((void *) grp);
  return CDEV_SUCCESS;
}


// *****************************************************************************
// * cdevSystem::registerActiveGroup :
// *****************************************************************************
int 
cdevSystem::registerActiveGroup(cdevGroup *grp)
{
  if(groupList_.count() < MAX_NUM_GROUPS && !groupList_.includes((void *) grp))
    {
      groupList_.add((void *) grp);
    }
  if(!activeGroupList_.includes((void *) grp))
    {
#ifdef _CDEV_DEBUG
      printf("Add an active group\n");
#endif
      activeGroupList_.add((void *) grp);
      activeGrps_=1;
      return CDEV_SUCCESS;
    }
  return CDEV_ERROR;
}


// *****************************************************************************
// * cdevSystem::removeActiveGroup
// *****************************************************************************
int 
cdevSystem::removeActiveGroup(cdevGroup *grp)
{
#ifdef _CDEV_DEBUG
  printf("remove an active group\n");
#endif
  int status=activeGroupList_.remove((void *) grp);
  if(activeGroupList_.count() == 0)
    activeGrps_=0;
  return CDEV_SUCCESS;
}


// *****************************************************************************
// * cdevSystem::activeGroups :
// *****************************************************************************
int 
cdevSystem::activeGroups(cdevGroup **groups, int &numGroups)
{
  numGroups=activeGroupList_.count();
  if(numGroups > 0)
    {
      cdevSlistIterator sit(activeGroupList_);
      int i=0;
      for(sit.init(); !sit; ++sit)
	{
	  groups[i++]=(cdevGroup *) sit();
	}
    }
  return CDEV_SUCCESS;
}


// *****************************************************************************
// * cdevSystem::activeGroups
// *****************************************************************************
int
cdevSystem::activeGroups(void) const
{
  return activeGrps_;
}


// *****************************************************************************
// * cdevSystem::attachReadFd :
// *	This method is called to add a newly opened file descriptor to the 
// *	cdevFdSet that is used to maintain the list of file descriptors.  If
// *	the file descriptor has been newly opened, a used specified callback
// *	will be executed to notify the user of the new file descriptor.
// *****************************************************************************
int 
cdevSystem::attachReadFd(int fd)
{
  int status=cdevSync::attachReadFd(fd);
	
  if(status == CDEV_SUCCESS)
    {
      cdevSlistIterator ite(fdCbkList_);
      cdevSlistIterator itearg(fdCbkArgList_);
      cdevFdChangedCallback cbkfunc=0;
      void *userarg=0;

      // *************************************************************
      // * processing two lists at the same pace
      // *************************************************************
      itearg.init();
      while(!itearg)
	{
	  for(ite.init(); !ite; ++ite)
	    {
	      cbkfunc=(cdevFdChangedCallback) ite();
	      userarg=itearg();
	      // *********************************************
	      // * call registered callback
	      // *********************************************
				
	      (*cbkfunc) (fd, 1, userarg);
	      ++itearg;
	    }
	}
    }
  return status;
}



// *****************************************************************************
// * cdevSystem::detachReadFd :
// *	This method is called to remove a file descriptor from the list of
// *	file descriptors that the cdevSystem object will pend for events. If
// *	the fd was previously registered with the cdevSystem object, then a
// *	user specified callback will be executed to notify the user that
// *	the file descriptor is no longer in use.
// *****************************************************************************
int 
cdevSystem::detachReadFd(int fd)
{
  int status=cdevSync::detachReadFd(fd);
	
  if(status == CDEV_SUCCESS)
    {
      cdevSlistIterator ite(fdCbkList_);
      cdevSlistIterator itearg(fdCbkArgList_);
      cdevFdChangedCallback cbkfunc=0;
      void *userarg=0;

      // *************************************************************
      // * Processing two lists at the same pace
      // *************************************************************
      itearg.init();
      while(!itearg)
	{
	  for(ite.init(); !ite; ++ite)
	    {
	      cbkfunc=(cdevFdChangedCallback) ite();
	      userarg=itearg();
	      // *********************************************
	      // * Call registered callback
	      // *********************************************
	      (*cbkfunc) (fd, 0, userarg);
	      ++itearg;
	    }
	}
    }
  return status;
}

// *****************************************************************************
// * cdevSystem::addFdChangedCallback :
// *	This method allows the caller to specify a callback function that
// *	will be executed each time a file descriptor is added or removed from
// *	the list of file descriptors that the cdevSystem object will poll for
// *	events.
// *****************************************************************************
int
cdevSystem::addFdChangedCallback(cdevFdChangedCallback cbk, void *arg)
{
  fdCbkList_.add((void *) cbk);
  fdCbkArgList_.add(arg);
  return CDEV_SUCCESS;
}

// *****************************************************************************
// * cdevSystem::addUserFdCallback :
// *	This method allows the caller to specify a callback function that
// *	will be executed each time there are something to be read at this
// *    file descriptor. The callback function should return 0 on success
// *    read, and -1 on an IO faliure
// *****************************************************************************
int
cdevSystem::addUserFdCallback (int fd, cdevUserFdCallback cbk, void* arg,
			       cdevUserFdCbkId& id)
{
  // first check whether this fd is already in the monitored list
  int efd[256];
  int ec = 256;
  int es = CDEV_SUCCESS;

  if ((es = getFd (efd, ec)) != CDEV_SUCCESS)
    return es;

  for (int i = 0; i < ec; i++) {
    if (efd[i] == fd)
      return CDEV_INVALIDARG;
  }

  // check whether this fd is a bad fd
  if (checkFd (fd) < 0)
    return CDEV_IOFAILED;

  // now create a unique userFdService
  char tempsvcn[32];
  strcpy (tempsvcn, cdevUserFdService::prefixName ());
#ifdef _WIN32
  _mktemp (tempsvcn);
#else
  // get a unique name
  mkstemp (tempsvcn);
#endif
#ifdef _CDEV_DEBUG
  printf ("Create a cdevUserFdService: %s \n", tempsvcn);
#endif

  cdevUserFdService* fdsvc = new cdevUserFdService (fd, cbk, arg, tempsvcn,
						    *this);
  id = (cdevUserFdCbkId)fdsvc;
  return CDEV_SUCCESS;
}

int
cdevSystem::removeUserFdCallback (cdevUserFdCbkId id)
{
  cdevUserFdService* fdsvc = (cdevUserFdService *)id;
  
  if (serviceCreated (fdsvc->name ()) ) {
    removeService (fdsvc);
    delete fdsvc;
    return CDEV_SUCCESS;
  }
  return CDEV_ERROR;
}


// *****************************************************************************
// * cdevSystem::flush :
// *	This method calls the flush method of each of the services that are
// *	maintained in the internal service list.  The service's flush mechanism
// *	should force all of their buffers to be written to the respective
// *	underlying control systems.
// *****************************************************************************
int
cdevSystem::flush(void)
{
  cdevSlistIterator sit(serviceList_);
  cdevService *tsvc=0;
	
  for(sit.init(); !sit; ++sit)
    {
      tsvc=(cdevService *) sit();
      tsvc->flush();
    }
  return CDEV_SUCCESS;
}


// *****************************************************************************
// * cdevSystem::setupMask :
// *	This method will setup the file descriptor mask for the service
// *	provided file descriptors.
// *****************************************************************************
void 
cdevSystem::setupMask ( void )
{
  static cdevFdSet newSet;
  fd_set *         newSetData = (fd_set *)newSet;
  fd_set *         oldSetData = (fd_set *)rdMask_;
  int    *         fd;
  int              numFds;
		
  cdevSlistIterator sit(serviceList_);
  cdevService *tsvc=0;

  // *********************************************************************
  // * Clean all fd masks, except the read mask... it will be explictly
  // * updated.
  // *********************************************************************
  wrMask_.reset();
  exMask_.reset();
	
  // *********************************************************************
  // * Reset the newSet data.
  // *********************************************************************
  newSet.reset();
		
  // *********************************************************************
  // * Load all of the service file descriptors into the newSet.
  // *********************************************************************
  for(sit.init(); !sit; ++sit)
    {
      fd     = NULL;
      numFds = 0;
      tsvc   = (cdevService *) sit();

      tsvc->getFd(fd, numFds);
      while(numFds>0) 
	{ 
	  numFds--;
	  newSet.set_bit(fd[numFds]);
	}
    }
	
  // *********************************************************************
  // * If the two groups of fd_set objects are different, figure out how 
  // * many bits have to be compared to find all changes, then walk 
  // * through the list and set the appropriate flags in the rdMask_ 
  // * object.
  // *********************************************************************
  int max_set = max(newSet.max_set(), rdMask_.max_set());
  if(max_set > 0 && memcmp(newSetData, oldSetData, sizeof(fd_set))!=0)
    {
      long set_flag = 0;
      while(max_set > 0)
	{
	  set_flag     = FD_ISSET(max_set, newSetData);
	  if(set_flag != FD_ISSET(max_set, oldSetData))
	    {
	      if(set_flag) attachReadFd(max_set);
	      else         detachReadFd(max_set);
	    }
	  max_set--;
	}
    }
}
	

// *****************************************************************************
// * cdevSystem::poll :
// *	This method will poll each of the underlying services, giving the
// *	opportunity to process any read or write events that they may have.
// *****************************************************************************
int 
cdevSystem::poll(void)
{
  cdevSlistIterator sit(serviceList_);
  cdevService *tsvc=0;
	
  for(sit.init(); !sit; ++sit)
    {
      tsvc=(cdevService *) sit();
      tsvc->poll();
    }

  // check timer queue here
  cdevTimeValue timerSkew (0, 1000*10);  // solaris timer skew
  if (!timerQueue_.isEmpty ())
    timerQueue_.expire (cdevTimeValue::currentTime () + timerSkew);

  return CDEV_SUCCESS;
}


// *****************************************************************************
// * cdevSystem::pend :
// *	This method causes the cdevSystem object to wait for an event to occur 
// *	on one of the underlying file descriptors.
// *****************************************************************************
int 
cdevSystem::pend(int)
{
  // *********************************************************************
  // * Flush all I/O out
  // *********************************************************************
  flush();
	
  // *********************************************************************
  // * Setup the file descriptor mask.
  // *********************************************************************
  setupMask ();
			
  // *********************************************************************
  // * Check IO ready
  // *********************************************************************
  return handleEvents((cdevTimeValue *)0);
}


// *****************************************************************************
// * cdevSystem::pend :
// *	This method causes the cdevSystem object to wait a specified number
// *	of seconds for an event to occur on one of the underlying file
// *	descriptors.
// *****************************************************************************
int 
cdevSystem::pend(double seconds, int)
{
  if(seconds > 0.0)
    {
      // *************************************************************
      // * Flush all io out
      // *************************************************************
      flush();
		
      // *********************************************************************
      // * Setup the file descriptor mask.
      // *********************************************************************
      setupMask ();

      // *************************************************************
      // * Check IO ready
      // *************************************************************
      cdevTimeValue how_long(seconds);
      return handleEvents(&how_long);
    }
  else return poll();
}


// *****************************************************************************
// * cdevSystem::getRequestObject :
// *	This method will obtain the request object associated with the 
// *	specified device/message combination.
// *****************************************************************************
int 
cdevSystem::getRequestObject(char *deviceName, char *msg, cdevRequestObject* &req)
{
  req=cdevRequestObject::attachPtr(deviceName, msg, *this);
  if(req)
    return CDEV_SUCCESS;
  else
    return CDEV_ERROR;
}


// *****************************************************************************
// * cdevSystem::addTimer
// *****************************************************************************
int
cdevSystem::addTimer (cdevTimerHandler* handler,
		      const void* arg,
		      double delay, double interval)
{
  cdevTimeValue t0 (delay);
  cdevTimeValue td = t0 + cdevTimeValue::currentTime ();

  if (interval > 0.000001) {
    cdevTimeValue ti (interval);
    return timerQueue_.scheduleTimer (handler, arg, td, ti);
  }
  else
    return timerQueue_.scheduleTimer (handler, arg, td);
}


// *****************************************************************************
// * cdevSystem::removeTimer
// *****************************************************************************
int
cdevSystem::removeTimer (cdevTimerHandler* handler)
{
  if (timerQueue_.cancel (handler) != 0)
    return CDEV_ERROR;
  return CDEV_SUCCESS;
}

// *****************************************************************************
// * cdevSystem::removeTimer
// *****************************************************************************
int
cdevSystem::removeTimer (int id)
{
  if (timerQueue_.cancel (id) != 0)
    return CDEV_ERROR;
  return CDEV_SUCCESS;
}

// *****************************************************************************
// * cdevSystem::enableDefaultSvc
// *****************************************************************************
void
cdevSystem::enableDefaultSvc (void)
{
  defaultSvc_ = 1;
}

void
cdevSystem::disableDefaultSvc (void)
{
  defaultSvc_ = 0;
}

int
cdevSystem::defaultSvc (void) const
{
  return defaultSvc_;
}

// *****************************************************************************
// * cdevSystem::notifyService :
// *****************************************************************************
void 
cdevSystem::notifyService(int handle)
{
  cdevService *service=getService(handle);
	
  if(service && service->pend(handle) < 0)
    {
      // *************************************************************
      // * Remove this file descriptor
      // *************************************************************
      detachReadFd(handle);
    }
}


// *****************************************************************************
// * cdevSystem::getService :
// *	This method allows the caller to obtain a pointer to the cdevService
// *	that is associated with the specified file handle.
// *****************************************************************************
cdevService * 
cdevSystem::getService(int handle)
{
  cdevSlistIterator sit(serviceList_);
  cdevService *tsvc=0;
	
  for(sit.init(); !sit; ++sit)
    {
      tsvc=(cdevService *) sit();
      int *fd=0;
      int numFds=0;
      tsvc->getFd(fd, numFds);
      if(numFds != 0)
	{
	  for(int j=0; j < numFds; j++)
	    if(handle == fd[j])
	      {
		return tsvc;
	      }
	  numFds=0;
	}
    }
  return(cdevService *) 0;
}


// *****************************************************************************
// * cdevSystem::getFd :
// *	This method will return the list of file handles that are currently
// *	being managed by this cdevSystem object.  If the buffer provided is
// *	too small (as specified in numFD), CDEV_ERROR will be returned.  
// *	Otherwise, the fd array will be populated with the list of file
// *	descriptors, the number of items will be placed in the numFd parameter,
// *	and CDEV_SUCCESS will be returned.
// *****************************************************************************
int 
cdevSystem::getFd(int fd[], int &numFD)
{
  cdevSlistIterator sit(serviceList_);
  cdevService *tsvc=0;
	
  int k=0;
  int oldNumFD=numFD;
  for(sit.init(); !sit; ++sit)
    {
      tsvc=(cdevService *) sit();
      int *ifd=0;
      int inumFds=0;
      tsvc->getFd(ifd, inumFds);
      if(inumFds != 0)
	{
	  for(int j=0; j < inumFds; j++)
	    {
	      if(k >= oldNumFD)
		return CDEV_INVALIDARG;
	      fd[k++]=ifd[j];
	    }
	  inumFds=0;
	}
    }
  numFD=k;
  return CDEV_SUCCESS;
}

// *****************************************************************************
// * cdevSystem::handleEvents
// *****************************************************************************
int 
cdevSystem::handleEvents (cdevTimeValue *tv)
{
  cdevFdSet rmaskret;
  cdevFdSet wmaskret;
  cdevFdSet emaskret;
  int       nfound = 0, status = 1;
  cdevClock timer;
  cdevTimeValue timerSkew (0, 1000*10);  // solaris timer skew

  if (!tv) {
    cdevTimeValue* timeout = calculateTimeOut (tv);

    nfound = waitFor (rmaskret, wmaskret, emaskret, timeout);
    // really handle lower level pend here
    dispatch (nfound, rmaskret, wmaskret, emaskret);

    if (nfound > 0)
      status = 0;

    if (!timerQueue_.isEmpty ())
      timerQueue_.expire (cdevTimeValue::currentTime () + timerSkew);
  }
  else {
    timer.schedule (0, *tv);
    while (!timer.expired()){
      // maximum time out value
      cdevTimeValue newTv = timer.scheduledTime() - cdevTimeValue::currentTime();
      // caculate timeout value from timer queue
      cdevTimeValue* timeout = calculateTimeOut (&newTv);

      nfound = waitFor (rmaskret, wmaskret, emaskret, timeout);
      // really handle lower level pend here
      dispatch (nfound, rmaskret, wmaskret, emaskret);

      if (nfound > 0)
	status = 0;

      // handle timer callbacks
      if (!timerQueue_.isEmpty ())
	timerQueue_.expire (cdevTimeValue::currentTime () + timerSkew);
    }
  }
  // status == 0 stands for there is at least one IO event
  if (status == 0)
    return CDEV_SUCCESS;
  return CDEV_TIMEOUT;
}

// *****************************************************************************
// * cdevSystem::calculate timeout
// *****************************************************************************
cdevTimeValue *
cdevSystem::calculateTimeOut (cdevTimeValue* maxTimeOut)
{
  static cdevTimeValue timeout;

  if (timerQueue_.isEmpty ()) {
    if (maxTimeOut) {
      timeout = *maxTimeOut;
      return &timeout;
    }
    return 0;
  }
  else {
    cdevTimeValue currTime = cdevTimeValue::currentTime ();
    
    if (timerQueue_.earliestTime () > currTime) {
      // The earliest time on the timer queue is still in the future.
      // Therefore use smaller of (1) caller's wait timer or (2) the delta time
      // between now and earliest time on the timer queue
      timeout = timerQueue_.earliestTime () - currTime;
      if (maxTimeOut == 0 || *maxTimeOut > timeout)
	return &timeout;
      else
	return maxTimeOut;
    }
    else {
      // The earliest time is now in the past. We have to call all
      // timers
      timeout = cdevTimeValue::zero;
      return &timeout;
    }
  }
}

  
