/*
    $Header: main:net/WPL/logproc.c,v 1.8 93/06/02 22:52:29 akelm Exp Locker: akelm $

    Log process routines for WPL (or general usage)

    Copyright (C) 1992 Russell McOrmond
    Copyright (C) 1993 Alan Kelm

    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; 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 General Public License for more details.

    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.

 $Log:  logproc.c,v $
 * Revision 1.8  93/06/02  22:52:29  akelm
 * modified Putline to use escape character specified as first character in
 * command.  If no such character is specified,  the default is NO escape
 * character.
 * 
 * Revision 1.7  93/05/13  19:31:15  Alan_Kelm
 * Modified PutLine to only operate on RAW: windows (CON: wouldn't work).
 * OpenLog will not open a log if a log of the same name already exists.
 * 
 * Revision 1.6  93/04/12  14:35:59  Alan_Kelm
 * Added PutLine command and fixes for it.
 * 
 * Revision 1.5  93/04/08  14:54:06  Alan_Kelm
 * Added PutLine command for line-addressable window logging.
 * 
 * Revision 1.4  93/03/16  18:41:56  Alan_Kelm
 * Revision 1.3  93/03/10  21:59:33  Alan_Kelm
 * Replaced list command by Show and Exist commands.  Added support for
 * Asynchronous (non-returnable) messages.
 * 
 * Revision 1.2  93/03/04  19:26:32  Alan_Kelm
 * Added "list" command.
 * 

*/

char *LogProcVersion = "$VER: LogProc version 1.3 ("__DATE__") Russell McOrmond,"
  " Alan Kelm.\r\n GNU Public License\r\n";

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <dos/dos.h>
#include <dos/dosextens.h>  /* to define struct Process */
#include <rexx/storage.h>
#include <exec/memory.h>
#include <exec/types.h>
#include "listmacros.h"
#include "logproc.h"

int stricmp(char *s, char *t); /* case insensitive string compare */
int strnicmp(char *s, char *t, int n);
    /* case insensitive string compare up to position n */

/* Prototypes from system .h files: */
         /* exec_protos.h */
void AddTail( struct List *list, struct Node *node );
void Remove( struct Node *node );
struct Node *RemHead( struct List *list );
struct Node *FindName( struct List *list, char *name );
struct Message *GetMsg( struct MsgPort *port );
void ReplyMsg( struct Message *message );
struct Message *WaitPort( struct MsgPort *port );
struct MsgPort *FindPort( char *name );
struct Task *FindTask( char *name );
void Forbid( void );
void Permit( void );
struct Library *OpenLibrary( char *libName, unsigned long version );
void CloseLibrary( struct Library *library );
APTR AllocMem( unsigned long byteSize, unsigned long requirements );
void FreeMem( APTR memoryBlock, unsigned long byteSize );
      /* from dos_protos.h */
BPTR Open( char *name, long accessMode );
LONG Read( BPTR file, APTR buffer, long length );
LONG Write( BPTR file, APTR buffer, long length );
LONG Seek( BPTR file, long position, long offset );
void Flush( BPTR fh );
LONG Close( BPTR file );
LONG DeleteFile( char *name );
void FPutC( BPTR fh, unsigned long ch );
LONG FPuts( BPTR fh, char *str );
          /* from alib_protos.h */
void NewList( struct List *list );
void DeletePort( struct MsgPort *io );
struct MsgPort *CreatePort( char *name, long pri );
          /* from rexxsyslib_protos.h */
BOOL IsRexxMsg( struct RexxMsg *msgptr );
char *CreateArgstring( char *string, unsigned long length );
void DeleteRexxMsg( struct RexxMsg *packet );
void ClearRexxMsg( struct RexxMsg *msgptr, unsigned long count );

/* LOGPROC
 *
 * Limitations:
 *
 *   Log entries are truncated to 219 characters (per line).
 *   Log session and handle names are limited to 20 chars.
 *
 * Bugs:
 *
 *   When PutLog flushes a buffer to a file it doesn't return a non-zero
 *   error code if the file cannot be opened.
 *
 */

struct Library *RexxSysBase=NULL;

/*** MAIN ***/
int main(int argc, char *argv[])
{
  void logRexxMsg(struct RexxMsg *aMsg, struct LogProc *log);
  void CloseLogHandle(struct LogProc *log, struct logHandle *handle);
  void MsgDispose(struct RexxMsg *aMsg);
  struct LogProc *log=NULL;
  struct Process *me;
  struct Message *aMsg;
  struct MsgPort *thisPort;
  char *portname;
  int n;
#ifdef LOGDEBUG
  BPTR oldfile,newfile;

  newfile=Open("raw:580/0/150/100/Debug",MODE_NEWFILE);
  if (newfile) oldfile=SelectOutput(newfile);

  PutStr("DEBUG Log Ready\n");
#endif

  /* check to see if a port was specified on the command line */
  if(argc > 2  ||
     (argc == 2 &&
       ( (n=toupper((int)argv[1][0])) < 'A' || n > 'Z' ||
         !stricmp(argv[1],"help") )) ) {
         puts("Usage:  Logproc [portname]\n");
         return(1);
  }
  if(argc == 2) portname = argv[1];
  else portname = "LOGPROC";    /* default Message Port Name */

  /* check to see if logproc is already running */
  Forbid();
  thisPort=FindPort(portname);
  Permit();
  if(thisPort) return 1;  /* Logproc is already running */

  /* create message port and open libraries */
  if((RexxSysBase = OpenLibrary("rexxsyslib.library", 0L)) &&
     (log=AllocMem(sizeof(struct LogProc),MEMF_CLEAR)) &&
     (log->PublicPort=CreatePort(portname, 0)))
  {

    me=(struct Process *)FindTask(NULL);
    me->pr_WindowPtr=(APTR)-1;   /* kill requestors */


#ifdef LOGDEBUG
    PutStr("Log Port Opened...");

    SPrintF(log->scratch,"Log:%lx(%ld)\n",log,sizeof(struct LogProc));
    PutStr(log->scratch);

    SPrintF(log->scratch,"LogHandleF(%ld) LogHandle(%ld)\n",sizeof(struct logHandleF)
                   ,sizeof(struct logHandle));
    PutStr(log->scratch);

    SPrintF(log->scratch,"LogSession(%ld)\n",sizeof(struct logSession));
    PutStr(log->scratch);
#endif

    NewList(&log->logHandles);  /* no lh_Type not specified */
    NewList(&log->logSessions);

#ifdef LOGDEBUG
    PutStr("Ready\n");
#endif

    log->aborting=FALSE;
    while(!log->aborting) {
      WaitPort(log->PublicPort);
      while(!log->aborting  &&  (aMsg=GetMsg(log->PublicPort))) {
        if(IsRexxMsg((struct RexxMsg *)aMsg)) {

          /* Message is a REXX message... process it */
          logRexxMsg((struct RexxMsg *)aMsg,log);
          /* both rexx return codes have been set... now reply */
          MsgDispose((struct RexxMsg *)aMsg);   /* reply or free message */

        } else {

#ifdef LOGDEBUG
          PutStr("Wasn't a REXX message? Bad...Bad..!!\n");
#endif
          ReplyMsg(aMsg); /* Let's hope this is legal */
        }
      }
    }

#ifdef LOGDEBUG
    PutStr("Aborting....\n");
#endif
    /* an abort command has been received.
     * discard all incoming messages,  and clean up.
     */
    Forbid();
    while(aMsg=GetMsg(log->PublicPort)) {
      ((struct RexxMsg *)aMsg)->rm_Result1=100;
      ((struct RexxMsg *)aMsg)->rm_Result2=0;
      MsgDispose((struct RexxMsg *)aMsg);   /* reply or free message */
    }
    DeletePort(log->PublicPort);
    Permit();

    CloseLogHandle(log,NULL); /* clear out all log handles
                               * and sessions referring to them
                               */
    /* free any remaining sessions (these have no handles) */
    {
      struct logSession *temps;
      FOREACH_LIST_SAFE(&log->logSessions,temps)
        Remove((struct Node *)temps);
        FreeMem(temps,sizeof(struct logSession));
      END_LIST_SAFE;
    }
  }
  /* final cleanup */
  if(log) FreeMem(log,sizeof(struct LogProc));
  if(RexxSysBase) CloseLibrary(RexxSysBase);


#ifdef LOGDEBUG
  if (newfile) {
     newfile=SelectOutput(oldfile);
     Close(newfile);
  }
#endif
  return 0;
}

/*** MsgDispose ***/
void MsgDispose(struct RexxMsg *aMsg)
{
  /* reply to a message or release it if it is the non-returnable kind */
  if(aMsg->rm_Action & RXFF_NONRET) {
    /* a non-returnable message ... free it */
    ClearRexxMsg(aMsg, (aMsg->rm_Action & RXARGMASK) + 1); /* free all ARGstrings */
    DeleteRexxMsg(aMsg);
  }
  else ReplyMsg((struct Message *)aMsg);
}


/*** copyParam ***/
char *copyParam(char *args, char *buffer, long len)
{
  /* get next 'word' from args into buffer,  converted to lowercase
   * return start of following word
   */

  while(*args==' ') args++; /* skip any leading blanks */
  if (len) len--;   /* always leave room for null termination */
  while(len && *args && (*args!=' ')) {
    *buffer++=tolower((int)*args);
    args++;
    len--;
  }
  *buffer='\0';
  while(*args==' ') args++; /* skip any trailing blanks */
  return(args);
}

/*** OPEN LOG HANDLE ***/
struct logHandle *OpenLogHandle(struct LogProc *log, char *name,
  char *file, char *flags)
{
  /* Open a log handle for either a file or a window */
  struct logHandleW *myHandleW;
  struct logHandleF *myHandleF;
  char *s;
  long size=0;

  s=flags;
  while (*s) {
    switch (tolower((int)*s)) {
      case 'f':
        size=1;
        break;

      case 'w':
        size=0;
        break;
    }
    s++;
  }

  if(size) {
    /* want log file */
    size=sizeof(struct logHandleF)+strlen(file);
    if(myHandleF=AllocMem(size,MEMF_CLEAR)) {
      (myHandleF->lh.length)=size;
      strncpy(myHandleF->lh.name,name,LPNAMELEN);
      myHandleF->lh.lplh.ln_Name=myHandleF->lh.name;
      strcpy(myHandleF->filename,file);
      /* setup lists of free and used logs */
      NewList(&myHandleF->logs);
      NewList(&myHandleF->freelogs);
      for (size=0; size<NUMLOGS; size++) {
        AddTail(&myHandleF->freelogs,
                (struct Node *)&myHandleF->logentries[size]);
      }      

#ifdef LOGDEBUG
      SPrintF(log->scratch,"OpenHand: %lx,%s\n",myHandleF,myHandleF->lh.name);
      PutStr(log->scratch);
#endif

      AddTail(&log->logHandles, (struct Node *)myHandleF);
      return((struct logHandle *)myHandleF);
    }
  } else if(myHandleW=AllocMem(sizeof(struct logHandleW),MEMF_CLEAR)) {
    /* open window specified */
    if((myHandleW->myFile)=Open(file,MODE_NEWFILE)) {
      myHandleW->lh.length=0;   /* flag to say this is a window handle */
      strncpy(myHandleW->lh.name,name,LPNAMELEN);
      myHandleW->lh.lplh.ln_Name=myHandleW->lh.name;
#ifndef SCROLL_ONLY
      if(!strnicmp(file, "raw:", 4)) myHandleW->winType |= ADDRESSABLE;
#endif

#ifdef LOGDEBUG
      SPrintF(log->scratch,"OpenHand: %lx,%s\n",myHandleW,myHandleW->lh.name);
      PutStr(log->scratch);
#endif

      AddTail(&log->logHandles,  (struct Node *)myHandleW);
      return((struct logHandle *)myHandleW);
    }
    else FreeMem(myHandleW,sizeof(struct logHandleW));
  }
  /* we failed to setup log handle */
  return(NULL);
}


/*** FLUSHLOGF ***/
void FlushLogF(struct logHandleF *handle)
{
  BPTR logfile;
  struct LogEntry *entry;

  logfile=Open(handle->filename,MODE_READWRITE);
  if(logfile) Seek(logfile,0,OFFSET_END);

  FOREACH_LIST_SAFE(&handle->logs,entry)
    if(logfile) {
      FPuts(logfile,entry->buf);
      FPutC(logfile,'\n');
    }
    Remove((struct Node *)entry);
    AddTail(&handle->freelogs, (struct Node *)entry);
  END_LIST_SAFE;

  if(logfile) Close(logfile);
}


/*** REMOVELOGHANDLE ***/
void RemoveLogHandle(struct logHandle *handle)
{
    /* This routine removes a log handle from the list of handles,
     * closes it (flushing if necessary),  and frees it.
     * It does *not* remove session logs pointing to the handle.
     * This routine SHOULD ONLY BE CALLED FROM CloseLogHandle(), which
     * does remove session nodes pointing to the handle.
     */

    Remove((struct Node *)handle);

#ifdef LOGDEBUG
    PutStr("Closing:");
    PutStr(handle->name);
    PutStr("\n");
#endif

    if (handle->length) {   /* file handle */
      FlushLogF((struct logHandleF *)handle);

#ifdef LOGDEBUG
      SPrintF(log->scratch,"Remove-HandF: %lx\n",handle);
      PutStr(log->scratch);
#endif

      FreeMem(handle,handle->length);
    } else {    /* window handle */
      Close(((struct logHandleW *)handle)->myFile);

#ifdef LOGDEBUG
      SPrintF(log->scratch,"Remove-Hand: %lx\n",handle);
      PutStr(log->scratch);
#endif

      FreeMem(handle,sizeof(struct logHandleW));
    }
}

/*** CLOSELOGHANGLE ***/
void CloseLogHandle(struct LogProc *log, struct logHandle *handle)
 /* 
  * The specified log 'handle' is removed from any session containing it.
  * Empty sessions are removed,  and the 'handle' is closed.
  * If 'handle' is  NULL  *all* sessions and handles are closed and  removed.
  */
{
  struct lpSesNode *tempn;
  struct logSession *temps;

  /* first remove any session references to this handle */
  FOREACH_LIST_SAFE(&log->logSessions,temps)
    FOREACH_LIST_SAFE(&temps->logSesNodes,tempn)
      if(!handle || ((tempn->handle)==handle)) {
        /* remove this lpSesNode */

#ifdef LOGDEBUG
        SPrintF(log->scratch,"Remove-Node: %lx, %s\n",tempn,tempn->lpsn.ln_Name);
        PutStr(log->scratch);
#endif

        Remove((struct Node *)tempn);
        FreeMem(tempn,sizeof(struct lpSesNode));
      }
    END_LIST_SAFE;
    /* If the session list is empty, nuke it */
    if((temps->logSesNodes.lh_Head->ln_Succ)==NULL) {
#ifdef LOGDEBUG
      SPrintF(log->scratch,"Remove-Ses: %lx, %s\n",temps,temps->name);
      PutStr(log->scratch);
#endif
      Remove((struct Node *)temps);
      FreeMem(temps,sizeof(struct logSession));
    }
  END_LIST_SAFE;

  /* now close the handle itself */
  if (handle) RemoveLogHandle(handle);
  else {
    /* remove all log handles */
    FOREACH_LIST_SAFE(&log->logHandles,handle)
      RemoveLogHandle(handle);
    END_LIST_SAFE;
  }
}

#ifndef SCROLL_ONLY
/*** MSGLEN ***/
int msgLen(char *text, char echar)
{
  /* return number of printable characters in message text */
  char *m=text;
  int n=0, skipone=0;
  
  while(*m) {
    if(echar  &&  (*m == echar)  &&  !skipone) {
      if(*++m == echar) skipone=1;
      else switch(*m) {
        case 'P':
        case 'B':
        case 'F':
        case 'I':
        case 'U':
        case 'R': ++m; break;
        case 'f':
        case 'c':
        case 'b': if(*++m) ++m; break;
      }
    }
    else {
      skipone=0;
      ++m; ++n;
    }
  }
  return n;
}

/*** PUTNUM ***/
void putNum(BPTR win, int num)
{
  /* output unsigned decimal number to window */
  char buf[20];
  int i;

  i=0;
  do {
    buf[i++] = num % 10 + '0';
  } while( num /= 10 );
  while(--i >= 0) FPutC(win, (unsigned long)buf[i]);
}


/*** PUTLINE ***/
void putLine(BPTR win, char *msg, char echar) {
  /* output a message to a window,  providing position and format control
   * The first word of msg should be a position/attribute specifier.
   * The remainder of msg is the string to output.
   * <echar> is the escape character to use ('\0' for none),  unless the first
   *   character of the position word is not a digit,  in which case it
   *   replaces the specified value of echar.
   * Note that the msg string can be altered by putline().
   */
  int atoi(const char *cp);
  char buf[5], *s, *m, *next;
  int rows, cols;   /* window dimensions */
  int row, col, len, txtlen, pos;
  int right, left, center, keep; /* flags */
  int n, skipone;

  /* send Window Status Request to find window bounds */
  {
    unsigned char aWSR[5]="00 q";
    char report[20];

    aWSR[0] = CSI;
    Write(win, aWSR, 4);
    if(!(n = Read(win, report, 19))) return;
    report[n] = '\0';
    /* report should look like: "<CSI>1;1;rows;cols r" */
    if(strncmp(report+1, "1;1;", 4)) return;
    rows = atoi(report+5);
    if(!(s=strchr(report+5,';'))) return;
    cols = atoi(s+1);
    /* printf("Number of rows: %d cols %d\n", rows, cols); */
  }

  if(!(m=strchr(msg, ' '))) return; /* no message */
  *m++ = '\0';  /* mark off the position string and start of message */
  if(!isdigit((int)*msg)) echar = *msg++;

  /* check to see if there are more putline sequences in this message */
  for(next=NULL, s=m ; echar && (s=strchr(s, (int)echar)) ;) {
    if(isdigit((int)*++s)) {    /* found next putline sequence */
      next = s;
      *--s = '\0';  /* seal off preceding argument */
      break;
    }
  }

  /* handle the current putline sequence */
  s=msg;
  row=0; col=0; len=0;
  if(isdigit((int)*s)) {
    row=atoi(s);
    while(isdigit((int)*s)) ++s;
    if(*s && isdigit((int)*++s)) {
      col=atoi(s);
      while(isdigit((int)*s)) ++s;
      if(*s == '+' && isdigit((int)s[1])) {
        col += atoi(++s);
        while(isdigit((int)*s)) ++s;
      }
      if(*s && isdigit((int)*++s)) len=atoi(s);
    }
  }
  right=left=center=keep=0;

  if(strchr(msg, 'R')) right=1;
  if(strchr(msg, 'L')) left=1;
  if(strchr(msg, 'C')) center=1;
  if(strchr(msg, 'K')) keep=1;


  txtlen = len ? len : msgLen(m, echar);
  if(!col) {    /* need to decide on starting column */
    if(center) col = (cols-txtlen)/2 + 1;
    else if(left) col = 1;
    else if(right) col = cols-txtlen;
  }
  if(col <= 0) col = 1;

  if(col <= cols  && row >= 1  && row <= rows) {    /* valid position */
    /* make cursor invisible */
    FPutC(win, CSI); FPuts(win, "0 p");
    if(!len && !keep) { /* clear line */
      FPutC(win, CSI); putNum(win, row); FPuts(win, ";1H");
      FPutC(win, CSI); FPutC(win, 'K');
    }
    /* position cursor */
    FPutC(win, CSI); putNum(win, row); FPutC(win,';');
    putNum(win, col); FPutC(win,'H');
    FPutC(win, CSI); FPuts(win,"0m");   /* normal text attributes */
    
    /* now output text to the window */
    pos=col;
    skipone=0;
    while(*m) {
      /* handle <echar>-escape sequences */
      if(echar  &&  (*m == echar)  &&  !skipone) {
        /* if it's an invalid escape sequence, we discard the <echar> */
        s= buf;
        *s='\0';
        if(*++m == echar) skipone=1;
        else switch(*m) {
          case 'P': *s='0'; break;
          case 'B': *s='1'; break;
          case 'F': *s='2'; break;
          case 'I': *s='3'; break;
          case 'U': *s='4'; break;
          case 'R': *s='7'; break;
          case 'f': *s++='3'; *s=*++m; break;
          case 'c': *s++='4'; *s=*++m; break;
          case 'b': *s++='>'; *s=*++m; break;
        }
        if(*s) {  /* found an attribute escape sequence */
          *++s ='\0';
          FPutC(win, CSI);
          FPuts(win, buf);
          FPutC(win, 'm');
          ++m;
        }
      }
      else {
        skipone=0; /* for <echar><echar> sequences a single <echar> prints */
        if(pos < cols  && txtlen>0) {
          FPutC(win, (unsigned int)*m);
          ++pos;  --txtlen;
        }
        ++m;
      }
    }
    while(txtlen > 0 && pos < cols) { FPutC(win, ' '); ++pos; --txtlen; }
    
    Flush(win);
  }

  /* recursively handle any further putline sequences */
  if(next) putLine(win, next, echar);
}
#endif  /* #ifndef SCROLL_ONLY */


/*** LOGREXXMSG ***/
void logRexxMsg(struct RexxMsg *aMsg, struct LogProc *log)
{
  int ListNodes(struct RexxMsg *aMsg, struct LogProc *log);
  int show_names(struct RexxMsg *aMsg, struct List *list);
  char *temp;

  /*
   * Perform commands given in rexx message and set both Rexx result fields.
   */

#ifdef LOGDEBUG
  SPrintF(log->scratch,"%lx,%lx, REXX:%s\n",aMsg->rm_TaskBlock,aMsg->rm_LibBase,
            ARG0(aMsg));
  PutStr(log->scratch);
#endif

  /* extract the command */
  temp=copyParam((char *)ARG0(aMsg),log->scratch,SCRATCHLEN);
  aMsg->rm_Result2=0;   /* secondary rexx result field zero (for now) */

  /* do specified command,  and set the primary REXX result field */
  if(!stricmp(log->scratch,"abort")) {              /*** ABORT ***/
    log->aborting=TRUE;
    aMsg->rm_Result1=0; /* abort succeeded */
  } else if(!stricmp(log->scratch,"putlog")) {      /*** PUTLOG ***/
    struct logSession *temps;
    struct lpSesNode  *tempn;

    temp=copyParam(temp,log->session,LPNAMELEN); /* get group name */

    if(temps=(struct logSession *)FindName(&log->logSessions,log->session)) {
      FOREACH_LIST_UNSAFE(&temps->logSesNodes,tempn)
        if (tempn->handle->length) {    /* file handle */
          struct LogEntry *entry;
          struct logHandleF *handle;

          handle=(struct logHandleF *)(tempn->handle);
          if ((entry=(struct LogEntry *)RemHead(&handle->freelogs))==NULL) {
            /* loghandle buffer is full... flush it */
            FlushLogF(handle);
            entry=(struct LogEntry *)RemHead(&handle->freelogs);
          }
          /* we have a log entry to write to */
          if (entry) {
            strncpy(entry->buf,temp,MAXLOGENTRY-1);
            entry->buf[MAXLOGENTRY-1]='\0';
            AddTail(&handle->logs, (struct Node *)entry);
          }
        } else {    /* window handle */
          FPuts(((struct logHandleW *)(tempn->handle))->myFile,temp);
          FPutC(((struct logHandleW *)(tempn->handle))->myFile,'\n');
        }
      END_LIST_UNSAFE;
      aMsg->rm_Result1=0;
    } else
      aMsg->rm_Result1=1;   /* log group non-existent */
#ifndef SCROLL_ONLY
  } else if(!stricmp(log->scratch,"putline")) {      /*** PUTLINE ***/
    struct logSession *temps;
    struct lpSesNode  *tempn;
    struct logHandleW *handle;

    temp=copyParam(temp,log->session,LPNAMELEN); /* group name */

    aMsg->rm_Result1=0;
    if(temps=(struct logSession *)FindName(&log->logSessions,log->session)) {
      FOREACH_LIST_UNSAFE(&temps->logSesNodes,tempn)
        if(tempn->handle->length == 0) {    /* a window */
            handle=(struct logHandleW *)(tempn->handle);
            if(handle->winType & ADDRESSABLE) { /* addressable window */
              strncpy(log->scratch, temp, SCRATCHLEN);
              log->scratch[SCRATCHLEN-1] = '\0';
              putLine(handle->myFile, log->scratch, (char)'\0');
            }
            else aMsg->rm_Result1=1;  /* window not addressable */
        }
        else aMsg->rm_Result1=1;    /* not a window */
      END_LIST_UNSAFE;
    } else aMsg->rm_Result1=1;
#endif
  } else if(!stricmp(log->scratch,"openlog")) {     /*** OPENLOG ***/
    struct logHandle *temph;

    temp=copyParam(temp,log->session,LPNAMELEN);    /* log name */
    temp=copyParam(temp,log->scratch,SCRATCHLEN);   /* flags */

    if(!FindName(&log->logHandles,log->session)) {
      /* no log by this name exists yet,  so lets make one */
      temph=OpenLogHandle(log,log->session,temp,log->scratch);
      aMsg->rm_Result1=(temph) ? 0 : 1;
    }
    else aMsg->rm_Result1= 2; /* log by this name already exists */
  } else if(!stricmp(log->scratch,"closelog")) {    /*** CLOSELOG ***/
    struct logHandle *temph;

    aMsg->rm_Result1=0;
#ifdef LOGDEBUG
    PutStr("ClosLog:");
#endif
    while(strlen(temp)) {
      temp=copyParam(temp,log->session,LPNAMELEN); /* log name */
#ifdef LOGDEBUG
      PutStr(log->session);
      PutStr("\n");
#endif
      if(temph=(struct logHandle *)FindName(&log->logHandles,log->session)) {
        CloseLogHandle(log,temph);
      } else 
        aMsg->rm_Result1=1;  /* no log by that name exists */
    }
  } else if(!stricmp(log->scratch,"flushlog")) {    /*** FLUSHLOG ***/
    struct logSession *temps;
    struct lpSesNode *tempn;

    aMsg->rm_Result1=0;
#ifdef LOGDEBUG
    PutStr("FlushLog:");
#endif
    while(strlen(temp)) {
      temp=copyParam(temp,log->session,LPNAMELEN); /* log name */
#ifdef LOGDEBUG
      PutStr(log->session);
      PutStr("\n");
#endif
      if(temps=(struct logSession *)FindName(&log->logSessions,log->session)) {
        FOREACH_LIST_UNSAFE(&temps->logSesNodes,tempn)
          if(tempn->handle->length) /* handle is a file handle */
            FlushLogF((struct logHandleF *)tempn->handle);
        END_LIST_UNSAFE;
      } else 
        aMsg->rm_Result1=1;
    }
  } else if(!stricmp(log->scratch,"addloggroup") ||
            !stricmp(log->scratch,"addlogses")) {   /*** ADDLOGGROUP ***/
    struct logHandle *temph;
    struct logSession *newtemps;
    struct logSession *temps;
    struct lpSesNode  *tempn;

    temp=copyParam(temp,log->session,LPNAMELEN);  /* group name */
    if((temps=(struct logSession *)FindName(&log->logSessions,log->session))
       || (newtemps=AllocMem(sizeof(struct logSession),MEMF_CLEAR))) {
      if (!temps) { /* create a new session */
        strncpy(newtemps->name,log->session,LPNAMELEN);
        newtemps->lpls.ln_Name=newtemps->name;
        NewList(&newtemps->logSesNodes);
        AddTail(&log->logSessions, (struct Node *)newtemps);
        temps=newtemps;
      }
      /* now add log handles to group */
      aMsg->rm_Result1=0;   /* we'll return success unless we can't find sth */
      while(strlen(temp)) {
        temp=copyParam(temp,log->scratch,LPNAMELEN);  /* log name */
        if(FindName(&temps->logSesNodes, log->scratch))
          continue; /* don't duplicate a handle already in log group */
        if(temph=(struct logHandle *)FindName(&log->logHandles,log->scratch)) {
          if(tempn=AllocMem(sizeof(struct lpSesNode),MEMF_CLEAR)) {
            tempn->lpsn.ln_Name=temph->name;
            tempn->handle=temph;
            AddTail(&temps->logSesNodes, (struct Node *)tempn);
          }
        } else
          aMsg->rm_Result1=2; /* one or more could not be found */
      }
    }
    else aMsg->rm_Result1=1; /* couldn't allocate new session */
  } else if(!stricmp(log->scratch,"remloggroup") ||
            !stricmp(log->scratch,"remlogses")) {   /*** REMLOGGROUP ***/
    struct logSession *temps;
    struct lpSesNode  *tempn;

    /* remove some handles from the specified session */
    temp=copyParam(temp,log->session,LPNAMELEN);  /* group name */
    if(temps=(struct logSession *)FindName(&log->logSessions,log->session)) {
      /* we've found the log group */
      aMsg->rm_Result1=0;
      while(strlen(temp)) {
        temp=copyParam(temp,log->scratch,LPNAMELEN);  /* log name */
        if(tempn=(struct lpSesNode *)FindName(&temps->logSesNodes,log->scratch)) {
          Remove((struct Node *)tempn);
          FreeMem(tempn,sizeof(struct lpSesNode));
        }
      }
      if(temps->logSesNodes.lh_TailPred == (struct Node *)&temps->logSesNodes) {
        /* log group is empty now... remove it */
        Remove((struct Node *)temps);
        FreeMem(temps,sizeof(struct logSession));
      }
    } else aMsg->rm_Result1=1; /* couldn't find log group */
  } else if(!stricmp(log->scratch,"exists")) {   /*** EXISTS ***/
    struct List *list;

    if(tolower((int)temp[0]) == 'g') list = &log->logSessions;
    else if(tolower((int)temp[0]) == 'l') list = &log->logHandles;
    else {
      aMsg->rm_Result1 = 0;
      return;
    }
    temp=copyParam(temp,log->scratch,LPNAMELEN);
    temp=copyParam(temp,log->session,LPNAMELEN);    /* get name */
    aMsg->rm_Result1 = FindName(list, log->session) ? 1 : 0 ;
  } else if(!stricmp(log->scratch,"show")) {   /*** SHOW ***/
    switch(tolower((int)temp[0])) {
    case 'g':   /* show log groups */
      aMsg->rm_Result1 = show_names(aMsg, &log->logSessions);
      break;
    case 'l':   /* show logs */
      temp=copyParam(temp,log->scratch,LPNAMELEN);
      if(!*temp)    /* show all log names */
        aMsg->rm_Result1 = show_names(aMsg, &log->logHandles);
      else {  /* we want to list only logs in a particular log group */
        struct logSession *temps;

        temp=copyParam(temp,log->session,LPNAMELEN);
        if((temps=(struct logSession *)FindName(&log->logSessions,log->session)))
          aMsg->rm_Result1 = show_names(aMsg, &temps->logSesNodes);
        else aMsg->rm_Result1 = 1;
      }
      break;
    default:
      aMsg->rm_Result1 = 1;
    }
  } else {
    /* unknown command */
    aMsg->rm_Result1=99;
  }
}

int show_names(struct RexxMsg *aMsg, struct List *list)
{
  /* Traverse a list and build a Rexx Argstring which contains all the
   * node names (.ln_Name) separated by single blanks.
   * Then place this Argstring in the return field of the given
   * Rexx message (provided the mesage has the RESULT flag set)
   *
   * Returns 0 for success
   *         1 if RexxMsg RESULT flag not set or cannot allocate Argstring
   */
  struct Node *node;
  int slen, pass2;
  char *s, *t, *src;

  if(!(aMsg->rm_Action & RXFF_RESULT)) return 1;  /* options results not set */

  /* First scan through the list to find the length of string to allocate.
   * then go through and collect all the names.
   */
  for(slen = 0, pass2=0 ; pass2 <= 1 ; ++pass2) {
    if(pass2  && !slen) slen = 1; /* for trailing null */
    if(pass2  && !(t=s=AllocMem(slen,MEMF_CLEAR))) return 1;
    FOREACH_LIST_UNSAFE(list, node)
      if(!pass2) slen += strlen(node->ln_Name) + 1;
      else {
        /* add this node's name to the string s */
        if(t != s) *t++ = ' ';  /* separate names */
        for(src= node->ln_Name ; *src ;) *t++ = *src++;
      }
    END_LIST_UNSAFE;
  }
  *t = '\0';
  /* Autodocs forbid modifying an Argstring... presumably because of the
   * hash field... so we built string separately and copy it over.
   */
  aMsg->rm_Result2 = (long)CreateArgstring(s, slen);
  /* CreateArgstring() seems to allocate slen+1 characters and pad with an
   * ASCII 128 character.  If this is true,  I should only ask to allocate
   * slen-1 characters.  Since the ARexx docs don't say,  I allocate slen
   * (which is strlen(s)+1) to be safe.
   */
  FreeMem(s, slen);
  if(aMsg->rm_Result2) return 0;
  return 1;
}

