
/*
**
**  $VER: Ping.c 1.6 (20.8.96)
**  Ping 1.0
**
**  (C) Copyright 1995-96 by Roland 'Gizzy' Mainz
**
*/

/* We did not want any names before V36 */
#define INTUI_V36_NAMES_ONLY 1

/* amiga includes */
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/execbase.h>
#include <dos/dos.h>
#include <workbench/startup.h>
#include <workbench/icon.h>
#include <envoy/nipc.h>
#include <envoy/envoy.h>
#include <envoy/errors.h>

/* amiga prototypes */
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/intuition_protos.h>
#include <clib/icon_protos.h>
#include <clib/nipc_protos.h>
#include <clib/envoy_protos.h>
#include <clib/alib_protos.h>

/* amiga pragmas */
#include <pragmas/exec_pragmas.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/intuition_pragmas.h>
#include <pragmas/icon_pragmas.h>
#include <pragmas/nipc_pragmas.h>
#include <pragmas/envoy_pragmas.h>
#include <pragmas/alib_pragmas.h>

/* ansi includes */
#include <string.h>

/* version string */
#include "Ping_rev.h"

/* misc defines */
#ifndef NAME
#define NAME "Ping"
#endif /* !NAME */

struct PingStat
{
    ULONG ps_min;
    ULONG ps_max;
    ULONG ps_num;
    ULONG ps_sum;
    ULONG ps_numrequested;
};

/* prototypes */
#ifdef _DCC
                long                  wbmain( struct WBStartup * );
                void                  chkabort( void );
#endif /* _DCC */

                long                  main( long, STRPTR * );
        static  void                  DefaultSettings( void );
        static  void                  FreeInitProjectResult( void );
        static  void                  ReadENVPrefs( void );

        static  void                  ClearRDA( void );
        static  void                  ScanRDA( void );
        static  void                  ScanToolTypes( STRPTR * );

        static  void                  RunTool( void );

        static  BOOL                  OpenLibStuff( void );
        static  void                  CloseLibStuff( void );

        static  BOOL                  CreateBasicResources( void );
        static  void                  DeleteBasicResources( void );

                void                  AttemptOpenLibrary( struct Library **, STRPTR, STRPTR, ULONG );

                STRPTR                StringSave( STRPTR );
                STRPTR                AllocStringBuff( ULONG );
                STRPTR                UpdateString( STRPTR *, STRPTR );
                void                  FreeString( STRPTR );

                APTR                  AllocVecPooled( APTR, ULONG );
                void                  FreeVecPooled( APTR, APTR );

                void                  DoPing( BPTR, struct Entity *, struct PingStat * );
                void                  PrintPingStat( BPTR, struct PingStat * );



/* version_string */
STRPTR versionstring = VERSTAG;

long main_retval,
     main_retval2;

/* shared libraries */
extern struct ExecBase *SysBase;
extern struct Library  *DOSBase;


struct Library *IntuitionBase,
               *IconBase,
               *NIPCBase;

/* mempool for strings */
APTR StringPool;

/* templates for ReadArgs */
#define STARTUP_TEMPLATE "HOSTNAME,"        \
                         "REQUESTHOST/S,"   \
                         "ENTITY/K,"        \
                         "TIMEOUT/K/N,"     \
                         "NUM/K/N,"         \
                         "RECONNECT/K/N,"   \
                         "DELAY/K/N,"       \
                         "WINDOW=TO/K"

#define ENV_TEMPLATE STARTUP_TEMPLATE /* no /A specified, startup and env template can be the same */

static struct RDArgs  *startuprda;

static
struct
{
    STRPTR  hostname;
    long   *requesthost;
    STRPTR  entity;
    long   *timeout;
    long   *num;
    long   *reconnect;
    long   *delay;
    STRPTR  window;
} result;

static
struct
{
    STRPTR hostname;
    ULONG  requesthost;
    STRPTR entity;
    ULONG  timeout;
    ULONG  num;
    ULONG  reconnect;
    ULONG  delay;
    STRPTR window;
} project;


#ifdef _DCC
/* disable CTRL_C break support (DICE CTRL_C abort function) */
void chkabort( void )
{
}
#endif /* _DCC */


/****** Ping/Ping ************************************************************
*
*   NAME
*       Ping -- Envoy ping utility
*
*   FORMAT
*       Ping [<hostname>] [REQUESTHOST] [ENTITY] [TIMEOUT <value>]
*       [NUM <num pings>] [RECONNECT <reconnect count>] [DELAY <ticks>] 
*       [WINDOW|TO <file>]
*
*   TEMPLATE
*       HOSTNAME/K,REQUESTHOST/S,ENTITY/K,TIMEOUT/K/N,NUM/K/N,RECONNECT/K/N,
*       DELAY/K/N,WINDOW=TO/K
*
*   TOOLTYPES
*       HOSTNAME
*       REQUESTHOST
*       ENTITY
*       TIMEOUT
*       NUM
*       RECONNECT
*       DELAY
*       WINDOW
*       TO
*
*   PATH
*       Ping:
*
*   PURPOSE
*       "Ping" a specified entity on the given host.
*
*   DESCRIPTION
*       HOSTNAME - sets the host name of the entity to ping
*
*       REQUESTHOST - prompts with a envoy requester to select the host
*
*       ENTITY - sets the entity name to ping for (defaults to
*           "Accounts Server")
*
*       TIMEOUT - sets the maximum time to wait for a single entity,
*           in microseconds (defaults to 60000) (1 minute).
*
*       NUM - sets the number of pings to do (defaults to 1).
*
*       RECONNECT - reconnect after n pings (n given number)
*           (defaults to 0, which means no reconnect).
*
*       DELAY - delay between pings, in microseconds 
*           (defaults to 0, which means no delay).
*
*       WINDOW
*       TO - sets the output file name (defaults to stdout if invoked
*           from shell or "CON:////Ping/auto/wait/close/inactive" if invoked
*           from Workbench)
*
*   EXAMPLES
*       ; Ping the host "Meridian"
*       10.0.Ram Disk:>ping Meridian num=10
*       elapsedtime 0
*       elapsedtime 0
*       elapsedtime 0
*       elapsedtime 0
*       elapsedtime 0
*       elapsedtime 0
*       elapsedtime 0
*       elapsedtime 0
*       elapsedtime 0
*       elapsedtime 0
*       minimum 0 / nom. 0 / maximum 0
*       success 10 / requested 10
*
*   BUGS
*       See "History" section below
*
*   HISTORY
*       V1.5
*           First public release
*
*       V1.6
*           - Shell output redirection now works (using stdout instead
*             CONSOLE: for output).
*           - Now returns an error if closing of outputfile (WINDOW/TO option)
*             fails.
*           - Ping now supports V37 (2.0).
*           - If the given entity cannot be found, Ping now returns the
*             error as secondary result instead of prompting the user
*             with a requester and returning ERROR_OBJECT_NOT_FOUND
*             (also saves some code).
*           - HOSTNAME option is now a simple option, no keyword required
*             any more.
*           - envoy.library is now opened on demand (e.g. if the REQUESTHOST
*             switch was set).
*           - A missing HOSTNAME arg now pings the local host instead of
*             exiting without any error.
*           - RECONNECT and DELAY args added.
*           - Default name of output file (console) (if invoked from WB)
*             fixed (window title "Ping" was missing).
*           - TIMEOUT value has been changed from seconds to microseconds.
*             Default value is now 60000 (1 minute).
*
*   SEE ALSO
*       NetInfo
*
*****************************************************************************
*
*/


#ifdef _DCC
/* DICE workbench entry */
long wbmain( struct WBStartup *wbstartup )
{
    /* Call main like SAS-C */
    return( main( 0L, (STRPTR *)wbstartup ) );
}
#endif /* _DCC */


long main( long ac, STRPTR *av )
{
    LONG               numArgs,
                       x;
    struct WBStartup  *wbstartup;
    struct WBArg      *wbarg;
    struct DiskObject *tooldobj,
                      *projectdobj;
    BPTR               oldToolLock,
                       oldProjectLock;

    main_retval2 = 0L;
    main_retval = RETURN_OK;

    if( CreateBasicResources() )
    {
      DefaultSettings();

      /* check if something goes wrong (ENV error etc.) */
      if( main_retval == RETURN_OK )
      {
/* Workbench */
        if( ac == 0L )
        {
          wbstartup = (struct WBStartup *)av;

          numArgs = wbstartup -> sm_NumArgs;
          wbarg   = wbstartup -> sm_ArgList;

          if( *(wbarg[ 0 ] . wa_Name) )
          {
            if( wbarg[ 0 ] . wa_Lock )
            {
              oldToolLock = CurrentDir( (wbarg[ 0 ] . wa_Lock) );
            }

            if( tooldobj = GetDiskObjectNew( (wbarg[ 0 ] . wa_Name) ) )
            {
              /* two possible cases when started from workbench ... */
              if( numArgs < 2L )
              {
                /* ... first case, only our tool icon is given, create one project here */

                ScanToolTypes( (STRPTR *)(tooldobj -> do_ToolTypes) );

                /* check if something goes wrong (parsing error, etc.) */
                if( main_retval == RETURN_OK )
                {
                  RunTool();
                }

                FreeInitProjectResult();
              }
              else
              {
                /* ... second case, a couple of project icons are given, multiple projects will start from here */
                for( x = 1L ; x < numArgs ; x++ )
                {
                  if( wbarg[ x ] . wa_Lock )
                  {
                    oldProjectLock = CurrentDir( (wbarg[ x ] . wa_Lock) );
                  }

                  if( *(wbarg[ x ] . wa_Name) )
                  {
                    if( projectdobj = GetDiskObject( (wbarg[ x ] . wa_Name) ) )
                    {
                      ScanToolTypes( (STRPTR *)(tooldobj -> do_ToolTypes) );
                      ScanToolTypes( (STRPTR *)(projectdobj -> do_ToolTypes) );

                      /* check if something goes wrong (parsing error, etc.) */
                      if( main_retval == RETURN_OK )
                      {
                        RunTool();
                      }

                      FreeInitProjectResult();
                      DefaultSettings();

                      FreeDiskObject( projectdobj );
                    }
                  }

                  if( wbarg[ x ] . wa_Lock )
                  {
                    CurrentDir( oldProjectLock );
                  }
                }
              }

              FreeDiskObject( tooldobj );
            }

            if( wbarg[ 0 ] . wa_Lock )
            {
              CurrentDir( oldToolLock );
            }
          }
        }
        else
        {
/* CLI/Shell */
          if( startuprda = ReadArgs( STARTUP_TEMPLATE, (LONG *)(&result), NULL ) )
          {
            /* did we get a CTRL_C signal ? */
            if( !CheckSignal( SIGBREAKF_CTRL_C ) )
            {
              ScanRDA();

              /* check if something goes wrong (parsing error, etc.) */
              if( main_retval == RETURN_OK )
              {
                RunTool();
              }

              FreeInitProjectResult();
            }
            else
            {
              main_retval2 = ERROR_BREAK;
              main_retval  = RETURN_WARN;
            }

            FreeArgs( startuprda );
          }
          else
          {
            main_retval2 = IoErr();
            main_retval  = RETURN_FAIL;
          }
        }
      }

      DeleteBasicResources();
    }

    PrintFault( main_retval2, NAME );

    SetIoErr( main_retval2 );

    return( main_retval );
}


static
void DefaultSettings( void )
{
    ClearRDA();

    if( (((struct Process *)(SysBase -> ThisTask)) -> pr_CLI) == NULL )
    {
      result . window = "CON:////" NAME "/auto/wait/close/inactive";
    }

    result . entity = "Accounts Server";

    memset( (void *)(&project), 0, sizeof( project ) );

    project . timeout   = 60000UL;
    project . num       = 1UL;

    ReadENVPrefs();
}


static
void ReadENVPrefs( void )
{
    struct RDArgs envvarrda =
    {
      NULL,
      256L,
      0L,
      0L,
      NULL,
      0L,
      NULL,
      RDAF_NOPROMPT
    };

    TEXT varbuff[ 258 ];

    envvarrda . RDA_Source . CS_Buffer = varbuff;

    if( GetVar( NAME, varbuff, 256L, 0UL ) != (-1L) )
    {
      strcat( varbuff, "\n" );

      if( ReadArgs( ENV_TEMPLATE, (LONG *)(&result), (&envvarrda) ) )
      {
        ScanRDA();

        FreeArgs( (&envvarrda) );
      }
      else
      {
        main_retval2 = IoErr();
        main_retval  = RETURN_FAIL;
      }
    }
}


static
void ClearRDA( void )
{
    memset( (void *)(&result), 0, sizeof( result ) );
}


static
void ScanRDA( void )
{
    if( result . hostname )
    {
      UpdateString( (&(project . hostname)), (result . hostname) );
    }

    if( result . requesthost )
    {
      project . requesthost = TRUE;
    }

    if( result . entity )
    {
      UpdateString( (&(project . entity)), (result . entity) );
    }

    if( result . timeout )
    {
      project . timeout = (ULONG)(*(result . timeout));
    }

    if( result . num )
    {
      project . num = (ULONG)(*(result . num));
    }

    if( result . reconnect )
    {
      project . reconnect = (ULONG)(*(result . reconnect));
    }

    if( result . delay )
    {
      project . delay = (ULONG)(*(result . delay));
    }

    if( result . window )
    {
      UpdateString( (&(project . window)), (result . window) );
    }

    ClearRDA();
}


static
void ScanToolTypes( STRPTR *tt )
{
    STRPTR s;

    if( s = FindToolType( tt, "HOSTNAME" ) )
      result . hostname = s;

    if( s = FindToolType( tt, "REQUESTHOST" ) )
      result . requesthost = (long *)s;

    if( s = FindToolType( tt, "ENTITY" ) )
      result . entity = s;

    if( s = FindToolType( tt, "TIMEOUT" ) )
      (void)StrToLong( s, (LONG *)(&(project . timeout)) );

    if( s = FindToolType( tt, "NUM" ) )
      (void)StrToLong( s, (LONG *)(&(project . num)) );

    if( s = FindToolType( tt, "RECONNECT" ) )
      (void)StrToLong( s, (LONG *)(&(project . reconnect)) );

    if( s = FindToolType( tt, "DELAY" ) )
      (void)StrToLong( s, (LONG *)(&(project . delay)) );

    if( s = FindToolType( tt, "WINDOW" ) )
      result . window = s;

    if( s = FindToolType( tt, "TO" ) )
      result . window = s;

    ScanRDA();
}


static
void FreeInitProjectResult( void )
{
    FreeString( (project . hostname) );
    FreeString( (project . entity) );
}


static
BOOL CreateBasicResources( void )
{
    if( OpenLibStuff() )
    {
      if( StringPool = LibCreatePool( (MEMF_PUBLIC | MEMF_CLEAR), 256UL, 256UL ) )
      {
        return( TRUE );

#ifdef COMMENTED_OUT
        LibDeletePool( StringPool );
#endif /* COMMENTED_OUT */
      }

      CloseLibStuff();
    }

    return( FALSE );
}


static
void DeleteBasicResources( void )
{
    LibDeletePool( StringPool );
    CloseLibStuff();
}


static
BOOL OpenLibStuff( void )
{
    IconBase = NIPCBase = NULL;

    if( IntuitionBase = OpenLibrary( "intuition.library", 39UL ) )
    {
      AttemptOpenLibrary( (&IconBase),  NAME, ICONNAME,        37UL );
      AttemptOpenLibrary( (&NIPCBase),  NAME, "nipc.library",  40UL );

      if( IconBase && NIPCBase )
      {
        return( TRUE );
      }
      
      CloseLibStuff();
    }

    return( FALSE );
}


static
void CloseLibStuff( void )
{
    CloseLibrary( NIPCBase );
    CloseLibrary( IconBase );
    CloseLibrary( IntuitionBase );
}


void AttemptOpenLibrary( struct Library **library, STRPTR title, STRPTR libname, ULONG libversion )
{
    struct EasyStruct LibNotFoundES,
                      LibWrongVersionES;

    LibNotFoundES . es_StructSize   = sizeof( struct EasyStruct );
    LibNotFoundES . es_Flags        = 0UL;
    LibNotFoundES . es_Title        = title;
    LibNotFoundES . es_TextFormat   = "%s\nnot found.";
    LibNotFoundES . es_GadgetFormat = "Retry|Cancel";

    LibWrongVersionES . es_StructSize   = sizeof( struct EasyStruct );
    LibWrongVersionES . es_Flags        = 0UL;
    LibWrongVersionES . es_Title        = title;
    LibWrongVersionES . es_TextFormat   = "Requires at least\n%s version %lu";
    LibWrongVersionES . es_GadgetFormat = "Cancel";

    if( (*library) == NULL )
    {
      for( ;; )
      {
        /* attemp to open shared library */
        (*library) = OpenLibrary( libname, 0UL );

        if( (*library) )
        {
          /* check if we got at least "libversion" version of shared library */
          if( ((*library) -> lib_Version) < libversion )
          {
            (void)EasyRequest( NULL, (&LibWrongVersionES), 0UL, libname, libversion );

            CloseLibrary( (*library) );
            (*library) = NULL;
          }

          break;
        }

        /* prompt the user */
        if( EasyRequest( NULL, (&LibNotFoundES), 0UL, libname ) == 0L )
        {
          /* user canceled */
          break;
        }
      }
    }
}


STRPTR StringSave( STRPTR s )
{
    STRPTR saved;

    if( s )
    {
      if( saved = (STRPTR)AllocVecPooled( StringPool, (strlen( s ) + 2UL) ) )
      {
        strcpy( saved, s );

        return( saved );
      }
    }

    return( NULL );
}


void FreeString( STRPTR s )
{
    FreeVecPooled( StringPool, s );
}


STRPTR UpdateString( STRPTR *storage, STRPTR newstring )
{
    STRPTR s;

    if( storage )
    {
      FreeString( (*storage) );

      s = (*storage) = StringSave( newstring );
    }
    else
    {
      s = NULL;
    }

    return( s );
}


APTR AllocVecPooled( APTR poolheader, ULONG memsize )
{
    ULONG *memory;

    memsize += sizeof( ULONG );

    if( memory = (ULONG *)LibAllocPooled( poolheader, memsize ) )
    {
      *memory = memsize;

      memory++;
    }

    return( (APTR)memory );
}


void FreeVecPooled( APTR poolheader, APTR mem )
{
    ULONG *memory;

    if( mem )
    {
      memory = (ULONG *)mem;

      memory--;

      LibFreePooled( poolheader, memory, (*memory) );
    }
}


static
void RunTool( void )
{
    STRPTR          hostname;
    TEXT            hostnamebuff[ 512 ];

    struct PingStat ps = { (~0UL), 0UL, 0UL, 0UL, 0UL };

    hostname = project . hostname;

    if( (project . requesthost) )
    {
      struct Library *EnvoyBase = NULL;

      AttemptOpenLibrary( (&EnvoyBase), NAME, "envoy.library", 37UL );

      if( EnvoyBase )
      {
        /* request hostname */
        if( HostRequest( HREQ_Buffer,   hostnamebuff,
                         HREQ_BuffSize, sizeof( hostnamebuff ),
                         HREQ_Title,    NAME,
                         TAG_DONE ) )
        {
          hostname = (STRPTR)hostnamebuff;
        }
        else
        {
          /* user abort */
          main_retval2 = ERROR_BREAK;
          main_retval  = RETURN_WARN;
        }

        CloseLibrary( EnvoyBase );
      }
      else
      {
        /* can't open library */
        main_retval2 = ERROR_NO_FREE_STORE;
        main_retval  = RETURN_FAIL;
      }
    }

    /* all ok ? */
    if( (project . entity) && (main_retval == RETURN_OK) )
    {
      BPTR fh;

      if( project . window )
      {
        fh = Open( (project . window), MODE_READWRITE );
      }
      else
      {
        fh = Output();
      }

      if( fh )
      {
        struct Entity *sourceentity;

        if( sourceentity = CreateEntityA( NULL ) )
        {
          struct Entity *pingentity;
          ULONG          findentityerror;

          /* Try to find the entity on the other side to ping. */
          if( pingentity = FindEntity( hostname, (project . entity), sourceentity, (&findentityerror) ) )
          {
            ULONG x,         /* loop counter */
                  reconnect; /* reconnect counter */

            reconnect = project . reconnect;
            project . delay /= (1000UL / TICKS_PER_SECOND); /* bump delay from ms to internal ticks */

            for( x = 0UL ; x < (project . num) ; x++ )
            {
              /* for each loop, do a delay if requested (exept for this first loop cycle) */
              if( x && (project . delay) )
              {
                Delay( (project . delay) );
              }

              DoPing( fh, pingentity, (&ps) );

              /* did we got a CTRL_C signal ? */
              if( CheckSignal( SIGBREAKF_CTRL_C ) )
              {
                main_retval2 = ERROR_BREAK;
                main_retval  = RETURN_WARN;

                break;
              }

              /* reconnect requested ? */
              if( project . reconnect )
              {
                reconnect--;

                /* check reconnect counter */
                if( reconnect == 0UL )
                {
                  LoseEntity( pingentity );

                  /* Attempt to find the entity on the other side to ping again */
                  if( pingentity = FindEntity( hostname, (project . entity), sourceentity, (&findentityerror) ) )
                  {
                    /* reset the reconnect counter */
                    reconnect = project . reconnect;
                  }
                  else
                  {
                    /* can't find entity */
                    main_retval2 = (long)findentityerror;
                    main_retval  = RETURN_ERROR;

                    break;
                  }
                }
              }
            }

            PrintPingStat( fh, (&ps) );

            if( pingentity )
            {
              LoseEntity( pingentity );
            }
          }
          else
          {
            /* can't find entity */
            main_retval2 = (long)findentityerror;
            main_retval  = RETURN_FAIL;
          }

          DeleteEntity( sourceentity );
        }
        else
        {
          /* can't create source entity */
          main_retval2 = ERROR_NO_FREE_STORE;
          main_retval  = RETURN_FAIL;
        }

        if( fh != Output() )
        {
          if( !Close( fh ) )
          {
            /* Close error */
            main_retval2 = IoErr();
            main_retval  = RETURN_ERROR;
          }
        }
      }
      else
      {
        /* no output file */
        main_retval2 = IoErr();
        main_retval  = RETURN_FAIL;
      }
    }
}


void DoPing( BPTR fh, struct Entity *pingentity, struct PingStat *ps )
{
    ULONG  elapsedtime;
    STRPTR timeout;

    (ps -> ps_numrequested)++;

    elapsedtime = PingEntity( pingentity, (project . timeout) );

    if( elapsedtime == (ULONG)(-1L) )
    {
      timeout = "(timeout)";
    }
    else
    {
      timeout = "";

      /* statistics will only be done over the succsessfull pings */

      /* bump time (from microseconds to seconds) */
      elapsedtime /= 1000UL;

      /* ping statistic */
      (ps -> ps_num)++;

      ps -> ps_sum += elapsedtime;

      /* min bounds */
      if( elapsedtime < (ps -> ps_min) )
      {
        ps -> ps_min = elapsedtime;
      }

      /* max bounds */
      if( elapsedtime > (ps -> ps_max) )
      {
        ps -> ps_max = elapsedtime;
      }
    }

    (void)FPrintf( fh, "elapsedtime %lu %s\n", elapsedtime, timeout );
}


void PrintPingStat( BPTR fh, struct PingStat *ps )
{
    (void)FPrintf( fh, "minimum %lu / nom. %lu / maximum %lu\nsuccess %lu / requested %lu\n",
                   (ULONG)(ps -> ps_min),
                   (ULONG)((ps -> ps_sum) / (ps -> ps_num)),
                   (ULONG)(ps -> ps_max),
                   (ULONG)(ps -> ps_num),
                   (ULONG)(ps -> ps_numrequested) );
}


