/*
     WatchOpenDevice - a tool for AmigaOS to monitor OpenDevice() calls
     Copyright (C) 1999 Matthias Andree

     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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "WatchOpenDevice.h"

int mymain(void);
LONG __main(void)
{
  LONG RC=20;
  /* this is done manually since we have no startup modules */
  SysBase = *((struct ExecBase **)4L);
  if (DOSBase = (struct DosLibrary *)OpenLibrary(DOSNAME, 37L)) {
    if (IntuitionBase = (struct IntuitionBase *)OpenLibrary(INTUITIONNAME, 37L)) {
      RC=(LONG)mymain();
      CloseLibrary((struct Library *)IntuitionBase);
    }
    CloseLibrary((struct Library *)DOSBase);
  }
  return RC;
}

void __dummy(void);

const char verstag[] = VERSTAG;

/* Prototypes for assembler part */
extern void __asm __stdargs InitPatch(void);
extern LONG __asm __stdargs RemovePatch(void);
extern void __asm __stdargs RemovePatchForce(void);

/* global variables */
static struct MsgPort *MainPort = NULL;
static struct Process *MyProc = NULL;

struct MyMessage {
  struct Message msg;
  UBYTE *devName;
  ULONG unit;
  struct IORequest *ioRequest;
  ULONG flags;
  struct Task *task;
  UBYTE SerFlags;
};

/* this is very important */
static struct SignalSemaphore ss;

/* This is the new OpenDevice() call. It logs the data and falls through to the former
   OpenDevice() */

/* WARNING! THIS FUNCTION   * M U S T *   TAKE REGISTER ARGUMENTS IN SPECIFIED ORDER,
   IT IS CALLED FROM ASSEMBLER PARTS. IF YOU WANT IT TO TAKE STACK ARGUMENTS,
   ADD APPROPRIATE ASSEMBLER GLUE CODE TO THE WATCHOPENDEVICE_ASM.S FILE! */

__asm __saveds
extern void
OpenDevDebug(
  register __a0 UBYTE *devName,
  register __d0 unsigned long unit,
  register __a1 struct IORequest *ioRequest,
  register __d1 unsigned long flags )
{
  void *m;
  static struct MyMessage msg; /* static to prevent excess extra stack usage
				  this is also the main reason why we use the
				  semaphore */

  /* lock for exclusive access, to prevent output of concurrent OpenDevice() calls
     messing up each other */
  ObtainSemaphore(&ss);

  /* okay, we have obtained the key to pass here, this piece of code is
     for exclusive use. Get done with it and free as soon as possible,
     in order not to block OpenDevice calls for too long. We use a Message to send
     to the main logger Task */
  msg.devName = devName;
  msg.unit = unit;
  msg.ioRequest = ioRequest;
  msg.flags = flags;
  msg.msg.mn_ReplyPort = NULL;
  msg.task = FindTask(NULL);
  msg.SerFlags = ((struct IOExtSer *)ioRequest)->io_SerFlags;
  msg.msg.mn_Length = sizeof(struct MyMessage);

  if(m = AllocVec(sizeof(struct MyMessage), MEMF_PUBLIC)) {
    CopyMem(&msg, m, sizeof(struct MyMessage));
    PutMsg(MainPort, m);
  } /* no further error handling in OpenDevice() call. If we did not get
       enough memory for our message: bad luck, no notification */

  /* done, release the semaphore (unlock) */
  ReleaseSemaphore(&ss);
}

static int construct(void)
{
  struct MsgPort *p;
  MyProc = (struct Process *)FindTask(NULL);

  if(!(MainPort = CreateMsgPort())) return 1;

  /* check if we are already running in another Task */
  Forbid();
  p = FindPort(PORTNAME);
  if(!p) {
    MainPort -> mp_Node.ln_Name = PORTNAME;
    MainPort -> mp_Node.ln_Pri = 0;
    AddPort(MainPort);
    InitSemaphore(&ss);
    Permit();
    return 0;
  } else {
    struct EasyStruct ER = {
      sizeof(struct EasyStruct),
      0,
      "WatchOpenDevice Request",
      "WatchOpenDevice is already installed.",
      "Remove|Abort"
    };
    Permit();
    /* this is safe since we check again if it's not gone in the meanwhile */

    Printf(VERS " (" DATE ") could not be installed, already running.\n\n");

    if(EasyRequest(MyProc->pr_WindowPtr, &ER, NULL) == 1)
    {
      Forbid(); /* make atomic */
      if(p = FindPort(PORTNAME)) {
	Signal(p -> mp_SigTask, SIGBREAKF_CTRL_C);
      }
      Permit();

      /* actually, somebody else may have quit the other incarnation
	 without us knowing. But since that is what we want anyways,
	 we don't bother */
    }
    return 1;
  }
}

static void KillMsgPort(struct MsgPort *mp)
/* flushes all pending messages and deletes the port
   only call this when the port is private! i. e. after RemPort */
{
  void *m; /* enough, we don't process things, only pass'em on to FreeVec */

  if(mp) {
    while(m = GetMsg(mp)) FreeVec(m);
    DeleteMsgPort(mp);
  }
  Permit();
}

static void destruct(void)
{
  if(MainPort) {
    RemPort(MainPort);     /* make port private */
    KillMsgPort(MainPort); /* delete it */
  }
}

int mymain(void)
{
  long sigrcvd;
  long waitmsg;
  int running = 1;

  Printf(VERS " (" DATE "), Copyright (C) 1999 Matthias Andree\n");
  Printf("It comes with ABSOLUTELY NO WARRANTY. This is free\n");
  Printf("software, and you are welcome to redistribute it under certain conditions.\n");
  Printf("For details, check the file `COPYING' in the distribution archive.\n");

  if(construct()) return 20;
  InitPatch();

  Printf(VERS " (" DATE ") installed.\n\n");

  Printf("Device                      Unt IOReqst  Flags    SF Task\n"
	 "--------------------------- --- -------- -------- -- --------------------------\n");

  waitmsg = 1 << MainPort->mp_SigBit;

  while(running) {
    sigrcvd = Wait(SIGBREAKF_CTRL_C | waitmsg);

    if(sigrcvd & waitmsg) {
      struct MyMessage *m;

      while(m = (struct MyMessage *)GetMsg(MainPort)) {
	Printf("%-27.27s %3ld %08lx %08lx %02lx %-25.25s\n",
	  m->devName, m->unit, m->ioRequest, m->flags,
	  m->SerFlags, m->task->tc_Node.ln_Name);
	ReplyMsg((struct Message *)m);
	FreeVec(m);
      } /* while m */
    } /* if message */

    if(sigrcvd & SIGBREAKF_CTRL_C) {
      Forbid();
      /* force remove if SetMan installed */

      if(FindPort("SetMan")) {
	RemovePatchForce();
	running = 0;
	Permit();
      } else { /* if FindPort */
	Permit();
	/* try to remove if not */

	if(RemovePatch()) {
	  /* failed, pop up requester */
	  struct EasyStruct ER = {
	    sizeof(struct EasyStruct),
	    0,
	    "WatchOpenDeviceRequest",
	    "Overpatched. Force remove?\n(Attention! May crash!)",
	    "Remove|Stay"
	  };

	  if(EasyRequest(MyProc->pr_WindowPtr, &ER, NULL) == 1)
	  {
	    RemovePatchForce();
	    running = 0;
	  }
	} else running = 0; /* if removepatch */
      } /* if FindPort */
    } /* if break */
  } /* while */

  /* make sure no-one is still stuck in our function, we don't need to release it */
  ObtainSemaphore(&ss);
  /* ok, the function was quit */
  destruct();
  Printf(VERS " (" DATE ") removed.\n\n");

  return 0;
}

