/***************************************************************************
*                                                                          *
* SktSocketManager.m                                                       *
* Copyright 1992 by Nik A Gervae                                           *
*                                                                          *
* One of a set of three Objective-C classes (SktSocketManager, SktSocket,  *
* and SktSocketUser) which implement a convenient interface to Berkeley    *
* stream sockets under NeXTSTEP(r).  See the accompanying class            *
* specifications (files with a .rtf or .spec suffix) for further           *
* information.                                                             *
*                                                                          *
* NeXTSTEP is a registered trademark of NeXT Computer, Inc.                *
*                                                                          *
****************************************************************************
*                                                                          *
* LICENSE                                                                  *
*                                                                          *
* This program is free software; you can redistribute it and/or modify     *
* it under the terms of the GNU General Public License as published by     *
* the Free Software Foundation.                                            *
*                                                                          *
* The program and this makefile are distributed in the hope that it will   *
* be useful, but are provided "AS IS" AND WITHOUT ANY WARRANTY; without    *
* any express or implied warranty of MERCHANTABILITY or FITNESS FOR A      *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* Any use or distribution of the program and documentation must include    *
* appropriate copyrights to acknowledge Nik A. Gervae and the Free         *
* Software Foundation, Inc.                                                *
* You should have received a copy of the GNU General Public License        *
* along with this program; if not, write to the Free Software              *
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.                *
*                                                                          *
****************************************************************************
*                                                                          *
* VERSION HISTORY                                                          *
*                                                                          *
* Version numbers are simply dates in the form YYYYMMDD.  These represent  *
* the date that version was finished.  Only significantly changed versions *
* are reported here, or those versions requiring explanation of changes.   *
* There may be many interim stages between dated versions.                 *
*                                                                          *
* DateVersion Primary Author  Notes                                        *
* ----------- --------------- -------------------------------------------- *
* 19920327    Nik A Gervae    First released version                       *
* 19920723    Nik A Gervae    Actually released                            *
*                                                                          *
***************************************************************************/

#import <errno.h>
#import <stdio.h>

#import <sys/param.h>
#import <sys/types.h>
#import <sys/socket.h>
#import <sys/time.h>
#import <netinet/in.h>
#import <netdb.h>

#import <objc/List.h>

#import "SktSocketManager.h"

extern int errno;


/***************************************************************************
*                                                                          *
* These are the constant strings used.  Feel free to translate them into   *
* your favorite language.  Do be sure to keep all the % directives in      *
* place, or change the code that accesses these strings.                   *
*                                                                          *
***************************************************************************/
#define STR_Error             "ERROR (%s): "

#define STR_MallocFatalError   STR_Error "zone malloc failed.\n"

#define STR_BadCapacity       STR_Error "specified capacity (%d) invalid.\n"
#define STR_NoUserClass       STR_Error "no user class specified for " \
                               "initializing.\n"
#define STR_NotUserClass      STR_Error "%s is not a subclass of " \
                               "SktSocketUser.\n"
#define STR_CantGetSocket     STR_Error "unable to get service socket.\n"
#define STR_CantSetOptions    STR_Error "unable to set sender\'s options.\n"
#define STR_CantBindSocket    STR_Error "unable to bind socket.\n"
#define STR_CantGetSocketName STR_Error "unable to get socket name.\n"
#define STR_CantCreateList    STR_Error "unable to create openSockets " \
                               "list.\n"


#define STR_NoHostInfo        "(%s) Cannot get host information.\n"
#define STR_NoFds             "(%s) No more fds. Closing new connection.\n"
#define STR_NoNewConnects     "No new connections can be accepted  at this " \
                               "time. Sorry.\n"
#define STR_ClosingSocket     "(%s) closing SktSocket with socketFd %d\n"

#define STR_SelectEBADF       "(%s) bad fd in select().\n"
#define STR_SelectEINTR       "(%s) signal during select().\n"
#define STR_SelectEINVAL      "(%s) invalid timeout in select().\n"
#define STR_SelectErrUnknown  "(%s) unknown select() error #%d.\n"
#define STR_NewSocketDied     STR_Error "coudn\'t create/init new socket.\n"
#define STR_NewUserDied       STR_Error "coudn\'t create/init new user.\n"
#define STR_CantCreateUserSorry "Can\'t create needed resources to accept " \
                                 "a new connection.  Sorry.\n"

#define STR_Unknown           "(unknown)"



@implementation SktSocketManager

/***************************************************************************
*                                                                          *
* -init                                                                    *
*                                                                          *
***************************************************************************/
- init
{
  return [self notImplemented:_cmd];
}

/***************************************************************************
*                                                                          *
* -initPort:logfile:fdCapacity:userClass:options:                          *
*                                                                          *
* THIS IS THE DESIGNATED INITIALIZER FOR THIS CLASS.                       *
*                                                                          *
* This method does a hell of a lot.  It has to establish all ivars, check  *
* the amount of available file descriptors for sockets, check the supplied *
* user class, get a socket for service connections, bind it, verify is,    *
* and listen for connections. See below for the nitty-gritty.              *
*                                                                          *
* ERROR CONDITIONS: The following conditions cause a return of nil:        *
*                                                                          *
* - The specified capacity is invalid.                                     *
* - The specified user class is nil or is not a subclass of SktSocketUser. *
* - The socket can't be created or bound, or verified.                     *
* - setSocketOptions returns nil.                                          *
* - The List object of open SktSockets can't be allocated/initted.         *
*                                                                          *
***************************************************************************/
- initPort:(int)portNum logfile:(FILE *)file fdCapacity:(int)cap
    userClass:aClass
{
  int  slen = sizeof(struct sockaddr_in);  // for use in inet functions
  char host_name[MAXHOSTNAMELEN+1];        // for getting the hostname
  struct sockaddr_in  server;              // for binding the socket
  struct sockaddr_in  hostaddr_in;         // for getting the inet address
  struct hostent     *host;                // for getting the inet address

  [super init];
  zone = [self zone];

 /*
  * Set up the logfile right away, and initialize ivars to be void
  * until we can fill them in later.  Also check cap against the
  * number of available fds.
  */
  logfile     = file;
  userClass   = nil;
  openSockets = nil;
  hostaddress = hostname = NULL;
  doesLog     = YES;

 /*
  * Default select() behavior in -update is to poll (0 timeout),
  * not block, and not to cover any signals.
  */
  selectTimeout.tv_sec  = 0;
  selectTimeout.tv_usec = 0;
  timeoutIndefinite     = NO;
  selectSignalMask      = 0;

  if (0 > cap || getdtablesize() < cap) {
    [self log:STR_BadCapacity, [self name], cap];
    return [self free];
  }
  numAvailFds = cap;

 /*
  * If aClass is not a subclass of SktSocketUser, don't init.
  */
  if (!aClass) {
    [self log:STR_NoUserClass, [self name]];
    return [self free];
  }

  if (![aClass isKindOf:objc_getMetaClass([[SktSocketUser class] name])]) {
    [self log:STR_NotUserClass, [self name], [aClass name]];
    return [self free];
  }

  userClass = [aClass class];  // Send class message just in case.

 /*
  * A new socket, please.  And don't init if we can't get one.
  * If the sender wanted to set some options, try to do so, but
  * don't init if the provided function returns NO.
  */
  serviceSocketFd = socket(AF_INET, SOCK_STREAM, 0);
  if (0 > serviceSocketFd) {
    [self log:STR_CantGetSocket, [self name]];
    return [self free];
  }

  if (![self setSocketOptions:serviceSocketFd]) {
    [self log:STR_CantSetOptions, [self name]];
    return [self free];
  }

 /*
  * bind() the socket.  This part is messy, 'cause we have to get
  * all sorts of random garbage info to get what we really need.
  */
  gethostname(host_name, sizeof(host_name));
  if (NULL == (host = gethostbyname(host_name))) {
    server.sin_family = AF_INET;
    hostaddress = NULL;
  }
  else {
    server.sin_family = host->h_addrtype;
    memcpy((char *)&hostaddr_in.sin_addr.s_addr, (char *)host->h_addr_list[0], 
          host->h_length);

   /*
    * Not fatal if this fails. (May need to change this.)
    */
    hostaddress = zoneStrdup([self zone],
                             (char *)inet_ntoa(hostaddr_in.sin_addr));

    if (!hostaddress) {
      [self log:STR_MallocFatalError, [self name]];
    }
  }

 /*
  * Not fatal if this fails.  (May need to change this.)
  */
  hostname = zoneStrdup([self zone], host_name);
  if (!hostname) {
    [self log:STR_MallocFatalError, [self name]];
  }

  server.sin_addr.s_addr = htonl(INADDR_ANY);
  server.sin_port        = htonl(portNum);
  if (bind(serviceSocketFd, (struct sockaddr *)&server, slen)) {
    [self log:STR_CantBindSocket, [self name]];
    return [self free];
  }

 /*
  * Verify socket and listen() for connection requests.
  */
  if (0 > getsockname(serviceSocketFd, (struct sockaddr *)&server, &slen)) {
    [self log:STR_CantGetSocketName, [self name]];
    return [self free];
  }
  listen(serviceSocketFd, 5);

 /*
  * Establish more instance variables.
  */
  servicePort = portNum;
  maxSocketFd = serviceSocketFd;

  openSockets = [[List allocFromZone:[self zone]] initCount:5];

  if (!openSockets) {
    [self log:STR_CantCreateList, [self name]];
    return [self free];
  }

  return self;

} /*initPort:logfile:fdCapacity:userClass:*/

/***************************************************************************
*                                                                          *
* -setSocketOptions                                                        *
*                                                                          *
* This method sets no options, as the SktSocketManager needs none set.  It *
* may be overriden in subclasses to set other socket/file descriptor       *
* options via setsockopt/fcntl.                                            *
*                                                                          *
* If this method returns nil, then any initialization is aborted and the   *
* init... method will return nil.                                          *
*                                                                          *
***************************************************************************/
- setSocketOptions:(int)fd
{
  return self;
}

/***************************************************************************
*                                                                          *
* -free                                                                    *
*                                                                          *
* Does the obvious.  Also kills all connections.                           *
*                                                                          *
***************************************************************************/
- free
{
  if (hostaddress) free(hostaddress);
  if (hostname) free(hostname);
  [self closeAllSockets];
  if (openSockets) [openSockets free];
  shutdown(serviceSocketFd, 2);
  return [super free];
}

/***************************************************************************
*                                                                          *
* -setDoesLog:                                                             *
*                                                                          *
* Allows you to change whether the user logs non-error messages (which may *
* nonetheless be quite important). The default is YES.                     *
*                                                                          *
***************************************************************************/
- setDoesLog:(BOOL)flag
{
  doesLog = flag;
  return self;
}

/***************************************************************************
*                                                                          *
* -doesLog                                                                 *
*                                                                          *
* YES if non-error messages are logged (the default), NO if not.  Simple.  *
*                                                                          *
***************************************************************************/
- (BOOL)doesLog
{
  return doesLog;
}

/***************************************************************************
*                                                                          *
* -setTimeoutSeconds:microseconds:                                         *
*                                                                          *
* Sets the timeout for checking i/o.  The SktSocketManager will not wait   *
* longer than this before beginning to process an -update message.  If the *
* timeout is (0,0) then -update simply polls for immediately available i/o *
* operations and performs them.                                            *
*                                                                          *
* This method clears and deactivates timeoutIndefinite.                    *
*                                                                          *
***************************************************************************/
- setTimeoutSeconds:(long int)secs microseconds:(long int)usecs
{
  if (0 > secs || 0 > usecs) return nil;

  timeoutIndefinite     = NO;
  selectTimeout.tv_sec  = secs;
  selectTimeout.tv_usec = usecs;

  return self;
}

/***************************************************************************
*                                                                          *
* -getTimeoutSeconds:microseconds:                                         *
*                                                                          *
* Gets the timeout for checking i/o.  The SktSocketManager will not wait   *
* longer than this before beginning to process an -update message.  If the *
* timeout is (0,0) then -update simply polls for immediately available i/o *
* operations and performs them.                                            *
*                                                                          *
* This method returns NO if the timeout does not apply; that is, if        *
* timeoutIndefinite is in effect.                                          *
*                                                                          *
***************************************************************************/
- (BOOL)getTimeoutSeconds:(long int *)secs microseconds:(long int *)usecs
{
  *secs  = selectTimeout.tv_sec;
  *usecs = selectTimeout.tv_usec;

  return !timeoutIndefinite;
}

/***************************************************************************
*                                                                          *
* -setTimeoutIndefinite                                                    *
*                                                                          *
* Causes the SktSocketManager to block indefinitely in -update until there *
* is an i/o operation to perform, or a signal occurs.                      *
*                                                                          *
***************************************************************************/
- setTimeoutIndefinite
{
  timeoutIndefinite = YES;
  return self;
}

/***************************************************************************
*                                                                          *
* -isTimeoutIndefinite                                                     *
*                                                                          *
* Returns whether the SktSocketManager blocks indefinitely in -update      *
* until there is an i/o operation to perform, or a signal occurs.          *
*                                                                          *
***************************************************************************/
- (BOOL)isTimeoutIndefinite
{
  return timeoutIndefinite;
}

/***************************************************************************
*                                                                          *
* -setSignalMask:                                                          *
*                                                                          *
* Sets the signal mask used during a select(), and returns the old one.    *
* Note the difference from the standard sigsetmask() system call!          *
*                                                                          *
***************************************************************************/
- (int)setSignalMask:(int)sigmask;
{
  int oldmask = selectSignalMask;
  selectSignalMask = sigmask;
  return oldmask;
}

/***************************************************************************
*                                                                          *
* -signalMask                                                              *
*                                                                          *
* Returns the signal mask used during a select().  This is not a cover for *
* the sigmask() system call!                                               *
*                                                                          *
***************************************************************************/
- (int)signalMask;
{
  return selectSignalMask;
}

/***************************************************************************
*                                                                          *
* -setFdCapacity:                                                          *
*                                                                          *
* Sets the maximum amount of connections that will be allowed at one time. *
* Various error checking is done.  This method can be used to reserve some *
* of the process's file descriptors for other use (log/temp files).        *
*                                                                          *
***************************************************************************/
- setFdCapacity:(int)cap
{
  int assigned;

  assigned = [openSockets count];

  if (getdtablesize() < cap || assigned > cap) {
    return nil;
  }
  else if (0 > cap) {
    numAvailFds = 0;
    return nil;
  }
  else {
    numAvailFds = cap - assigned;
  }
  return self;
}

/***************************************************************************
*                                                                          *
* -adjustFdCapacity:                                                       *
*                                                                          *
* Adjusts the maximum amount of connections that will be allowed at one    *
* time.  Various error checking is done.  This method can be used to       *
*  reserve some of the process's file descriptors for other use (log/temp  *
* files).                                                                  *
*                                                                          *
***************************************************************************/
- (int)adjustFdCapacity:(int)byAmount
{
  int newAvailFds;
  int assigned;

  newAvailFds = numAvailFds + byAmount;
  assigned = [openSockets count];

  if (getdtablesize() < newAvailFds + assigned) {

    return -1;

  } else if (0 > newAvailFds) {

    numAvailFds = 0;
    return -1;

  }
  else {
    numAvailFds = newAvailFds;
  }
  return numAvailFds + assigned;
}

/***************************************************************************
*                                                                          *
* -fdCapacity                                                              *
*                                                                          *
* Returns the maximum amount of connections that will be allowed at one    *
* time.                                                                    *
*                                                                          *
***************************************************************************/
- (int)fdCapacity
{
  return numAvailFds + [openSockets count];
}

/***************************************************************************
*                                                                          *
* -numAvailFds                                                             *
*                                                                          *
* Returns the amount of file desctiptors free for further connections.     *
*                                                                          *
***************************************************************************/
- (int)numAvailFds
{
  return numAvailFds;
}

/***************************************************************************
*                                                                          *
* -logfile                                                                 *
*                                                                          *
* Return the FILE pointer for the file all log messages are written to.    *
*                                                                          *
***************************************************************************/
- (FILE *)logfile
{
  return logfile;
}

/***************************************************************************
*                                                                          *
* -hostaddress                                                             *
*                                                                          *
* Returns the address of the host the SktSocketManager's process is        *
* on, in dot notation.                                                     *
*                                                                          *
***************************************************************************/
- (const char *)hostaddress
{
  if (hostaddress) return (const char *)hostaddress;
  else return STR_Unknown;
}

/***************************************************************************
*                                                                          *
* -hostname                                                                *
*                                                                          *
* Returns the name of the host the SktSocketManager's process is on.       *
*                                                                          *
***************************************************************************/
- (const char *)hostname
{
  if (hostname) return (const char *)hostname;
  else return STR_Unknown;
}

/***************************************************************************
*                                                                          *
* -servicePort                                                             *
*                                                                          *
* The Internet port number used to connect to the SktSocketManager.        *
*                                                                          *
***************************************************************************/
- (int)servicePort
{
  return servicePort;
} 

/***************************************************************************
*                                                                          *
* -getInetAddresses                                                        *
*                                                                          *
* Return a NULL-terminated array of character strings representing all of  *
* the Internet addresses of the host machine. The sender may free the      *
* array returned when it is no longer needed (just free the top-level      *
* pointer).                                                                *
*                                                                          *
* The array is allocated in the default malloc zone.                       *
*                                                                          *
* ERROR CONDITION: If the allocation of the array fails, a message is      *
* logged, and NULL is returned.                                            *
*                                                                          *
***************************************************************************/
- (char **)getInetAddresses
{
  struct hostent     *host;          // the record for the host
  struct sockaddr_in  thisHostaddr;  // 
  int                 count, this;
  int                 totalSize;
  char              **addresses; // Used to return inet addresses
  char               *loc;

 /*
  * Get the host's name and count the number of addresses it has.
  */
  if (NULL == (host = gethostbyname(hostname))) {
    [self log:STR_NoHostInfo, [self name]];
    return NULL;
  }
  for (count = 0; host->h_addr_list[count] != NULL; count++)
    ;

 /*
  * Count up the total amount we need to malloc to get all the
  * strings and their pointers.
  */
  totalSize = (1+count) * sizeof(char *);

  for (this = 0; this < count && NULL != host->h_addr_list[this]; this++) {
    memcpy((char *)&(thisHostaddr.sin_addr.s_addr),
           (char *)host->h_addr_list[this],
           host->h_length);

    totalSize += 1 + strlen(inet_ntoa(thisHostaddr.sin_addr));
  }
  addresses = (char **)NXZoneMalloc(NXDefaultMallocZone(), totalSize);
  if (!addresses) {
    [self log:STR_MallocFatalError, [self name]];
    return NULL;
  }

 /*
  * Now copy each address into the proper offset in the array, and
  * set a pointer to it at the beginning.
  */
  loc = (char *)(addresses + count + 1);
  for (this = 0; this < count && NULL != host->h_addr_list[this]; this++) {

    char     *dummy;
    long int  size;

    memcpy((char *)&(thisHostaddr.sin_addr.s_addr),
           (char *)host->h_addr_list[this],
           host->h_length);
    dummy = (char *)inet_ntoa(thisHostaddr.sin_addr);
    size = 1+strlen(dummy);

    *(addresses + this) = loc;
    memcpy(loc, dummy, size);
    loc += size;
  }

 /*
  * Remember, it's a NULL-terminated array of arrays.
  */
  addresses[this] = NULL;

  return addresses;

} /*getInetAddresses*/

/***************************************************************************
*                                                                          *
* -update                                                                  *
*                                                                          *
* In brief (see below for details):                                        *
*  - Do asynchronous input via select(). This is the messy part.           *
*  - Drop any sockets with exceptions pending.                             *
*  - Have individual sockets flush output.                                 *
*  - Have them queue input.                                                *
*  - Check for new connections. (Always do this last.)                     *
*                                                                          *
* ERROR CONDITION: If a new SktSocket/User pair need to be created, and    *
* either can't be, this method logs a message and returns immediately.     *
* This is not a fatal error.                                               *
*                                                                          *
***************************************************************************/
- update
{
  int             i;                // general loop variable
  id              currentSocket;    // use in loops
  int             currentSocketFd;  // use in loops

  int             oldmask;          // used to protect select() from signals
  int             numpending;       // result of select()
  fd_set          readfds;          // for select()
  fd_set          exceptfds;        // for select()
  struct timeval *timeout;          // for select()

 /*
  * Prepare for select().  Set the timeout, the fd sets, and
  * mask any signals requested.
  */
  if (timeoutIndefinite) timeout = NULL;
  else timeout = &selectTimeout;

  FD_ZERO(&readfds);
  FD_ZERO(&exceptfds);

  FD_SET(serviceSocketFd, &readfds);
  for (i = [openSockets count]-1; 0 <= i; i--) {
    currentSocketFd = [[openSockets objectAt:i] socketFd];
    FD_SET(currentSocketFd, &readfds);
    FD_SET(currentSocketFd, &exceptfds);
  }

  oldmask = sigsetmask(selectSignalMask);

  numpending = select(maxSocketFd+1,
                      &readfds,
                      (fd_set *)0,  // SktSockets handle write()s
                      &exceptfds,
                      timeout);
  sigsetmask(oldmask);

 /*
  * Error occured: report and don't do anything this time
  * (odds are it was a signal).
  */
  if (0 > numpending) {
    if (doesLog) switch (errno) {
      case EBADF:
        [self log:STR_SelectEBADF, [self name]];
        break;
      case EINTR:
        [self log:STR_SelectEINTR, [self name]];
        break;
      case EINVAL:
        [self log:STR_SelectEINVAL, [self name]];
        break;
      default:
        [self log:STR_SelectErrUnknown, [self name], errno];
        break;
    }
    return self;
  }

 /*
  * Drop sockets with exception conditions (assume corrupt connection).
  * It is the socket user's responsibility to notice that its SktSocket
  * is gone, and to clean up appropriately.

  * Have to go backwards 'cause List slides contents forward on removal.
  */
  for (i = [openSockets count]-1; 0 <= i; i--) {

    currentSocket = [openSockets objectAt:i];
    currentSocketFd = (int)[currentSocket socketFd];

    if (FD_ISSET(currentSocketFd, &exceptfds)) {
      FD_CLR(currentSocketFd, &readfds);
      [self closeSocket:currentSocket];
    }

  }

 /*
  * Have all sockets flush output. We don't need writefds for this.
  */
  [openSockets makeObjectsPerform:@selector(flushOutput)];

 /*
  * Have each socket read one block of input. Use the info from
  * select() in this messy loop to avoid the hassle and overhead
  * of nonblocking read()'s where they might be unnecessary.
  * If readInput returns nil, the socket connection is closed/dead
  * (EOF), and the SktSocket should be closed.  It is the socket
  * user's responsibility to notice that its SktSocket is gone,
  * and to clean up appropriately.
  */
  for (i = [openSockets count]-1; 0 <= i; i--) {

    currentSocket   = [openSockets objectAt:i];
    currentSocketFd = [currentSocket socketFd];

    if (FD_ISSET(currentSocketFd, &readfds)) {      
      if (![currentSocket readInput]) {
        [self closeSocket:currentSocket];
      }
    }
  }

 /* 
  * Accept new connections if possible.
  */
  if (FD_ISSET(serviceSocketFd, &readfds)) {

    id  newSocket;
    int newSocketFd;
    id  newUser;

   /*
    * Create a new SktSocket and a user.  Log messages if they
    * can't be created, and return.
    */
    newSocket = [[SktSocket allocFromZone:zone] initOnFd:serviceSocketFd
                 withManager:self];
    if (!newSocket) {
      [self log:STR_NewSocketDied, [self name]];
      return self;
    }

    newSocketFd = [newSocket socketFd];

    newUser = [[userClass allocFromZone:zone] initWithSocket:newSocket];
    if (!newUser) {
      [self log:STR_NewUserDied, [self name]];
      [newSocket queueOutputString:STR_CantCreateUserSorry];
      [newSocket flushOutput];
      [newSocket free];
      return self;
    }

   /*
    * Check that we have room for it and update bookkeeping info.
    */
    if ([openSockets count] < numAvailFds) {
      if (newSocketFd > maxSocketFd) maxSocketFd = newSocketFd;
      [openSockets addObject:newSocket];
    }
    else {
      [self log:STR_NoFds, [self name]];
      [newSocket queueOutputString:STR_NoNewConnects];
      [newSocket flushOutput];

      [newSocket free];
      [newUser free];
    } 

  } /*if (FD_ISSET())*/

  return self;

} /*update*/

/***************************************************************************
*                                                                          *
* -closeSocket:                                                            *
*                                                                          *
* Remove a socket from the list of currently open sockets, have the        *
* SktSocket object clean itself up and die, and update bookeeping info.    *
* Do NOT kill the socket user here!  It has to find out its socket is gone *
* and take appropriate action.                                             *
*                                                                          *
***************************************************************************/
- closeSocket:socketObj
{
  [self log:STR_ClosingSocket, [self name], [socketObj socketFd]];

  [openSockets removeObject:socketObj];
  if ([socketObj socketFd] == maxSocketFd) {
    maxSocketFd--;
  }

  [socketObj free];

  return self;

}

/***************************************************************************
*                                                                          *
* closeAllSockets                                                          *
*                                                                          *
* Have all SktSocket objects clean up and die.                             *
*                                                                          *
***************************************************************************/
- closeAllSockets
{
  int i;

  for (i = [openSockets count] - 1; 0 <= i; i--) {
    [self closeSocket:[openSockets objectAt:i]];
  }
  return self;

}

/***************************************************************************
*                                                                          *
* -announceString:                                                         *
*                                                                          *
* Dump the given string on each socket.                                    *
*                                                                          *
***************************************************************************/
- announceString:(const char *)announcement
{
  int numfds;
  int i;

  numfds = [openSockets count];
  for (i = 0; i < numfds; i++) {
    [[openSockets objectAt:i] queueOutputString:announcement];
  }

  return self;
}

/***************************************************************************
*                                                                          *
* -log:                                                                    *
*                                                                          *
* Print a message to the logfile.  You can use this exactly like printf,   *
* 'cause that's what it uses.                                              *
*                                                                          *
***************************************************************************/
- log:(const char *)format, ...
{
  va_list  param_list;

  va_start(param_list, format);
  vfprintf(logfile, format, param_list);
  fflush(logfile);
  va_end(param_list);
  return self;

}

@end /*implementation SktSocketManager*/
