/*
 * $Header: /home/gene/library/website/docsrc/sca/src/RCS/scan.c,v 395.1 2008/04/20 17:25:48 gene Exp $
 *
 *  Copyright (c) 2006 Gene Michael Stover.  All rights reserved.
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation; either version 2 of the
 *  License, or (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 * 
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 *  USA
 */

#include "this.h"

/*
 * Maximum number of octets for the list.
 */
#define MAX_OCTETS_IN_LIST (1 << 16 /* 64 kilobyte */)

/*
 */
#define S_lst_len (MAX_OCTETS_IN_LIST / sizeof (struct in_addr))
static struct in_addr S_lst[S_lst_len];
static size_t S_count = 0;
static size_t S_i = 0;

/*
 * Port where we are looking for live hosts.
 */
static u_short S_port = 0;

/*
 * Timeout (in seconds) to wait for input on the socket.
 * Set with the "-t" command line option.
 */
static int S_timeout = 10;

/*
 * Pathname of file from which we read the message to send.
 */
static char *S_pathname;

/*
 * The message to send.  Maximum size is 8,000 octets because
 * that's pretty much the maximum payload of a UDP packet.
 */
static char S_message[8000];
static int S_message_len;

/*
 */
static char *S_progname = "";

/*
 */
enum { ACTION_RUN, ACTION_HELP };
static int S_action = ACTION_RUN;

/*
 */
static int
S_Init ()
{
  int rc = 0;
#if IS_WINDERS
  WORD w;
  static WSADATA wsadata;
#endif

  srand (time (NULL) + getpid ());
#if IS_WINDERS
  w = MAKEWORD (2, 0);
  if (WSAStartup (w, &wsadata) == 0) {
    /* good */
  } else {
    fprintf (stderr, "\n%s:%d: WSAStartup failed", __FILE__, __LINE__);
    rc = 56;
  }
#endif
  return rc;
}

/*
 */
static int
S_CommandLine (int argc, char *argv[])
{
  int rc = 0;
#if IS_UNIX
  int c;
#endif
#if IS_WINDERS
  int optind = 1;
#endif
  
  S_progname = argv[0];
#if IS_UNIX
  while (rc == 0 && (c = getopt (argc, argv, "t:")) != -1) {
    switch (c) {
    case 't':
      if (sscanf (optarg, "%d", &S_timeout) == 1) {
        /* good */
      } else {
        fprintf (stderr, "\n%s:%d: warning:", __FILE__, __LINE__);
        fprintf (stderr, " Can't parse \"%s\" into an integeral", optarg);
        fprintf (stderr, " timeout.  Using 10 seconds for the timeout.");
        S_timeout = 10;
      }
      break;
    default:
      fprintf (stderr, "\n%s:%d: warning:", __FILE__, __LINE__);
      fprintf (stderr, " Ignoring unexpected command line");
      fprintf (stderr, " option \"%c\".", c);
    }
  }
#endif
#if IS_WINDERS
  /* Winders doesn't have getopt. */
  while (rc == 0 && optind < argc && argv[optind][0] == '-') {
    if (strcmp (argv[optind], "-t") == 0) {
      ++optind;
      if (optind < argc) {
	if (sscanf (argv[optind], "%d", &S_timeout) == 1) {
	  /* good */
	} else {
	  fprintf (stderr, "\n%s:%d: warning:", __FILE__, __LINE__);
	  fprintf (stderr, " Can't parse \"%s\" into an integeral",
		   argv[optind]);
	  fprintf (stderr, " timeout.  Using 10 seconds for the timeout.");
	  S_timeout = 10;
	}
      } else {
	fprintf (stderr, "\n%s:%d: warning:", __FILE__, __LINE__);
	fprintf (stderr, " Must have integral number of seconds following");
	fprintf (stderr, " the \"-t\" argument.  Assuming the default,");
	fprintf (stderr, " 10 seconds.");
	S_timeout = 10;
      }
    } else {
      fprintf (stderr, "\n%s:%d: Ignoring unexpected switch, \"%s\".",
	       __FILE__, __LINE__, argv[optind]);
    }
    ++optind;
  }
#endif

  if (optind < argc) {
    if (sscanf (argv[optind], "%hu", &S_port) == 1) {
      ++optind;
      if (optind < argc) {
	S_pathname = argv[optind];
	++optind;
	if (optind < argc) {
	  fprintf (stderr, "\n%s:%d:", __FILE__, __LINE__);
	  fprintf (stderr, " Ignoring extra command line arguments.");
	}
      } else {
	fprintf (stderr, "\n%s:%d:",  __FILE__, __LINE__);
	fprintf (stderr, " Missing name of message file on the");
	fprintf (stderr, " command line.");
	S_action = ACTION_HELP;
      }
    } else {
      fprintf (stderr, "\n%s:%d:",  __FILE__, __LINE__);
      fprintf (stderr, " Cannot parse \"%s\" into a port", argv[optind]);
      fprintf (stderr, " number.");
      S_action = ACTION_HELP;
    }
  } else {
    fprintf (stderr, "\n%s:%d:",  __FILE__, __LINE__);
    fprintf (stderr, " Missing port number & name of message file");
    fprintf (stderr, " on the command line.");
    S_action = ACTION_HELP;
  }
  return rc;
}

/*
 */
static int
S_Help ()
{
  printf ("\nUsage: %s [-t SECONDS] PORT FILENAME", S_progname);
  printf ("\nPORT is the UDP port number");
  printf ("\nFILENAME identifies a file which contains the message to");
  printf ("\n    send to each host to see if it's alive.");
  printf ("\n");
  return 0;
}

/*
 */
static int
S_LoadMessage ()
{
  int rc = 0;
  static char mode[] = "rb";
  FILE *fp;

  fp = fopen (S_pathname, mode);
  if (fp != NULL) {
    S_message_len = fread (S_message, 1, sizeof S_message, fp);
    if (S_message_len > 0) {
      /* good */
      fprintf (stderr, "\n%s:%d:", __FILE__, __LINE__);
      fprintf (stderr, " Message contains %d octets", S_message_len);
    } else {
      DEBUG_PrintLastError (__FILE__, __LINE__, "S_LoadMessage");
      rc = 121;
    }
    fclose (fp);
  } else {
    DEBUG_PrintLastError (__FILE__, __LINE__, "S_LoadMessage");
    rc = 55;
  }
  return rc;
}

/*
 */
static SOCKET
S_CloseSocket (SOCKET s)
{
  if (s != INVALID_SOCKET) {
#if IS_UNIX
    close (s);
#endif
#if IS_WINDERS
    closesocket (s);
#endif
  }
  return INVALID_SOCKET;
}

/*
 */
static SOCKET
S_MakeSocket ()
{
  SOCKET s;

  fprintf (stderr, "\n%s:%d: S_MakeSocket", __FILE__, __LINE__);
  s = socket (PF_INET, SOCK_DGRAM, 0);
  if (s != INVALID_SOCKET) {
    /* good */
  } else {
    DEBUG_PrintLastError (__FILE__, __LINE__, "S_MakeSocket");
  }
  return s;
}

/*
 */
static void
S_Trim (char x[])
{
  while (strlen (x) > 0 && isspace (x[strlen(x) - 1])) {
    x[strlen(x) - 1] = '\0';
  }
}

/*
 */
static int
S_ReadAddy (struct in_addr *addy)
{
  int rc = 0;
  char line[100];

  if (fgets (line, sizeof line, stdin) == line) {
    S_Trim (line);
    bzero (addy, sizeof *addy);
    /* sin->sin_family = AF_INET; */
    /* sin->sin_port = htons (S_port); */
    /* sin->sin_addr.s_addr = inet_addr (line); */
    addy->s_addr = inet_addr (line);
  } else {
    /*
     * Probably normal end of file, though I suppose some other
     * problems are possible.
     */
    rc = 3;
  }
  return rc;
}

/*
 */
static void
S_RandomizeLst ()
{
  size_t i, r;
  struct in_addr tmp;

  for (i = 1; i < S_count; ++i) {
    r = rand () % (S_count - i) + i;
    memcpy (&tmp, &S_lst[i], sizeof S_lst[i]);
    memcpy (&S_lst[i], &S_lst[r], sizeof S_lst[i]);
    memcpy (&S_lst[r], &tmp, sizeof S_lst[i]);
  }
}

/*
 */
static int
S_LoadLst ()
{
  struct in_addr addy;

  S_count = 0;
  while (S_count < S_lst_len && S_ReadAddy (&addy) == 0) {
    memcpy (&S_lst[S_count++], &addy, sizeof S_lst[S_count]);
  }
  fprintf (stderr, "\n%s:%d: S_LoadLst:", __FILE__, __LINE__);
  fprintf (stderr, " S_count is %d.", S_count);
  fprintf (stderr, " Last is %s.", inet_ntoa (S_lst[S_count - 1]));
  return S_count > 0 ? 0 : -123;
}

/*
 */
static int
S_CmpInAddr (const void *va, const void *vb)
{
  int cmp = 0;
  struct in_addr *ina, *inb;

  ina = (struct in_addr *) va;
  inb = (struct in_addr *) vb;
  if (ina->s_addr < inb->s_addr) cmp = -1;
  else if (ina->s_addr == inb->s_addr) cmp = 0;
  else cmp = 1;
  return cmp;
}

/*
 */
static void
S_RemoveAddy (struct in_addr *addy)
{
  struct in_addr *elt;

  elt = lfind (addy, S_lst, &S_count, sizeof S_lst[0], &S_CmpInAddr);
  if (elt != NULL) {
    /*
     * Remove the element from the list by copying the last element
     * on top of it, then decrementing the count.
     */
    if (S_count >= 2 && elt < &S_lst[S_count - 1]) {
      /*
       * List has at least 2 elements, & the one to remove
       * is not the last.
       */
      memcpy (elt, &S_lst[--S_count], sizeof *elt);
    } else if (S_count >= 1) {
      /*
       * List has just one element, or the one to remove is
       * the last one.
       */
      --S_count;
    } else {
      /* List is already empty. */
    }
    S_i = 0; /* reset the write location */
  } else {
    /* fprintf (stderr, ".  Not in list???"); */
  }
}

/*
 */
static SOCKET
S_DoRecv (SOCKET s)
{
  int i;
  char buffer[8000];
  struct sockaddr_in sin;
  struct sockaddr *sa;
  int sin_len;

  bzero (&sin, sizeof sin);
  sin_len = sizeof sin;
  sa = (struct sockaddr *) &sin;
  i = recvfrom (s, buffer, sizeof buffer, 0, sa, &sin_len);
  if (i > 0) {
    printf ("%s\n", inet_ntoa (sin.sin_addr));
    S_RemoveAddy (&sin.sin_addr);
  } else {
    DEBUG_PrintLastError (__FILE__, __LINE__, "S_DoRecv");
    /* s = S_CloseSocket (s); */
  }
  return s;
}

/*
 */
static SOCKET
S_DoSend (SOCKET s)
{
  int i;
  struct sockaddr_in sin;
  struct sockaddr *sa;

  if (S_count > 0) {
    if (S_i >= S_count) S_i = 0;
    fprintf (stderr, "\n%s:%d: Send %s", __FILE__, __LINE__, 
	     inet_ntoa (S_lst[S_i]));
    bzero (&sin, sizeof sin);
    sin.sin_family = AF_INET;
    sin.sin_addr = S_lst[S_i];
    sin.sin_port = htons (S_port);
    sa = (struct sockaddr *) &sin;
    i = sendto (s, S_message, S_message_len, 0, sa, sizeof sin);
    if (i > 0) {
      /* good */
    } else {
      DEBUG_PrintLastError (__FILE__, __LINE__, "S_DoSend");
      fprintf (stderr, "%s:%d: Removing %s", __FILE__, __LINE__,
	       inet_ntoa (S_lst[S_i]));
      S_RemoveAddy (&S_lst[S_i]);
      s = S_CloseSocket (s);
    }
    ++S_i;
  } else {
    /*
     * The list is empty, so don't send anyting at all.
     */
    fprintf (stderr, "\n%s:%d: S_DoSend:", __FILE__, __LINE__);
    fprintf (stderr, " list is empty");
  }
  return s;
}

/*
 */
static int
S_Loop ()
{
  int rc = 0;
  SOCKET s = INVALID_SOCKET;
  fd_set rfds, wfds;
  struct timeval stv;
  int i;

  while (rc == 0 && S_count > 0) {
    if (s == INVALID_SOCKET) {
      s = S_MakeSocket (); S_i = 0; S_RandomizeLst ();
    }
    if (s != INVALID_SOCKET) {
      FD_ZERO (&rfds); FD_SET (s, &rfds);
      FD_ZERO (&wfds); FD_SET (s, &wfds);
      stv.tv_sec = S_timeout; stv.tv_usec = 0;
      assert (FD_ISSET (s, &rfds));
      assert (FD_ISSET (s, &wfds));
      i = select (s + 1,
		  /* always want to read */
		  &rfds,
		  /* only want to write if we have not written
		   * everything.  Otherwise, we busy-write */
		  S_i < S_count ? &wfds : NULL,
		  NULL, /* never exceptions */
		  &stv);
      if (i > 0) {
	if (FD_ISSET (s, &rfds)) {
	  s = S_DoRecv (s);
	}
	if (FD_ISSET (s, &wfds)) {
	  s = S_DoSend (s);
	}
      } else {
	/* Timeout */
	S_count = 0;
      }
    } else {
      fprintf (stderr, "\n%s:%d: S_MakeSocket failed", __FILE__, __LINE__);
      rc = 3;
    }
  }
  if (s != INVALID_SOCKET) {
    s = S_CloseSocket (s);
  }
  return rc;
}

/*
 */
static int
S_Run ()
{
  int rc = 0;

  if (S_LoadMessage () == 0) {
    while (S_LoadLst () == 0) {
      if (S_Loop () == 0) {
	/* good */
      } else {
	fprintf (stderr, "\n%s:%d: S_Loop failed", __FILE__, __LINE__);
	rc = 134;
      }
    }
  } else {
    fprintf (stderr, "\n%s:%d: S_LoadMessage failed", __FILE__, __LINE__);
    rc = 154;
  }
  return rc;
}

/*
 */
static int
S_Dispatch ()
{
  int rc = 0;

  switch (S_action) {
  case ACTION_RUN:
    rc = S_Run ();
    break;
  case ACTION_HELP:
    rc = S_Help ();
    break;
  default:
    fprintf (stderr, "\n%s:%d:", __FILE__, __LINE__);
    fprintf (stderr, " S_action is %d.", S_action);
    fprintf (stderr, "  This should never happen!");
    rc = 102;
  }
  return rc;
}

int
main (int argc, char *argv[])
{
  int rc = 0;

  fprintf (stderr, "\nMAX_OCTETS_IN_LIST is %d.", MAX_OCTETS_IN_LIST);
  fprintf (stderr, "\nS_lst_len is %d.", S_lst_len);
  if (S_Init () == 0) {
    if (S_CommandLine (argc, argv) == 0) {
      if (S_Dispatch () == 0) {
	/* good */
      } else {
	fprintf (stderr, "\n%s:%d: S_Dispatchfailed", __FILE__, __LINE__);
	rc = 234;
      }
    } else {
      fprintf (stderr, "\n%s:%d: S_CommandLine failed", __FILE__, __LINE__);
      rc = 234;
    }
  } else {
    fprintf (stderr, "\n%s:%d: S_Init failed", __FILE__, __LINE__);
    rc = 526;
  }
  return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}

/* --- end of file --- */
