#define DEBUG
/*
 *  KILL version 1.0
 *  by George Musser Jr.
 *  11 January 1986
 *
 *  Removes a task and as much of its hinterland as possible.  We can
 *  close windows and unload process code, but screens and other,
 *  unidentified structures elude us.  We can remove all but 40 or so
 *  bytes of an empty CLI.
 *
 *  Syntax: KILL <task name>
 *          KILL <task pointer>
 *
 *  I certainly don't claim that this program is the proper way to deal with
 *  the Amiga system.  Hopefully, DOS 1.3 will come with a legitimate way to
 *  kill runaway and undesirable processes.
 *
 *  Things to try in the next version:
 *    - improve the killing of Workbench-spawned processes (somehow get
 *      at the stdin window via process->pr_ConsoleTask);
 *    - try to kill a non-process;
 *    - experiment with trackdisk.device (killing the device stops
 *      the drive from clicking; perhaps a SleepAmiga utility could
 *      kill trackdisk, await a keypress, and Mount the drive);
 *    - how does the system associate the CON devices with windows?
 *      maybe we can use this to kill devices and Workbench processes
 *      more throrughly.
 *
 *  Geo's net addresses:
 *    CIS 76566,3714
 *    Plink OHS152
 *    ST801115@BROWNVM.BITNET
 */

/***** Includes ***********************************************************/

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/memory.h>
#include <exec/tasks.h>
#include <exec/ports.h>
#include <exec/execbase.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <intuition/intuitionbase.h>
#include <intuition/intuition.h>
#include <workbench/startup.h>
#include <stdio.h>

/***** Defines ************************************************************/

#define LIMIT1_OF_PATIENCE  1  /* Time to wait for death by CLOSEWINDOW */
#define LIMIT2_OF_PATIENCE  1  /* Time to wait for death by CTRL-C */
#define MAX_WINDOWS        25  /* Maximum number of windows we can close */

/***** Global variables ***************************************************/

struct IntuitionBase *IntuitionBase;
extern struct DosLibrary *DOSBase;

/***** main() *************************************************************/

main(argc,argv)
char **argv;
int argc;
{

   /***** Functions *******************************************************/

   void exit();
   int stch_i();
   void Forbid();
   struct Task *FindTask(char *);
   void Permit();
   void fprintf();
#ifndef DEBUG
   struct IntuitionBase *OpenLibrary(char *,long);
   ULONG LockIBase(long);
   void UnlockIBase(long);
#endif !DEBUG
   APTR AllocMem(long,long);
#ifndef DEBUG
   void PutMsg();
   void Delay(long);
#endif !DEBUG
   void CloseLibrary();
   void FreeMem();
#ifndef DEBUG
   void Signal(struct Task *,long);
#endif !DEBUG
   void UnLock(BPTR);
   void UnLoadSeg(BPTR);
   void Close(BPTR);
   struct MsgPort *FindPort(char *);
   void ReplyMsg();
   void RemTask(struct Task *);
   void ClearMenuStrip(struct Window *);
   void CloseWindow(struct Window *);

   /***** Local variables *************************************************/

#ifndef DEBUG
   ULONG ilock;           /* Lock on IntuitionBase, so rug stays under us */
#endif !DEBUG

   struct Task *task;       /* This will point to the task we must remove */

   struct RootNode *rootnode;   /* Central AmigaDOS structure */
   APTR *TaskTable;             /* List of processes */

#ifndef DEBUG
   struct Screen *screen;                   /* Points to a screen */
   struct Window *window;                   /* ...and to a window */
#endif !DEBUG
   int nWindow;                             /* Number of task's windows */
   struct Window *WindowList[MAX_WINDOWS];  /* Windows opened by task */

   unsigned long i;               /* Loop counter */
#ifndef DEBUG
   struct IntuiMessage *message;  /* Our forged IntuiMessage */
#endif !DEBUG

   struct Process *process;           /* Oops, task is part of a process */
   struct CommandLineInterface *cli;  /* What's more, it's a CLI process */
   BPTR *SegArray;                    /* A process has a list of segments */
   unsigned long nSeg;                /* Number of process segments */

   struct WBStartup *startup;  /* Forged message to Workbench */

   struct DOSLibrary *dos;  /* So that we can close DOS library */

   /***** Here we go ******************************************************/

   if (argc < 2)                       /* Not enough arguments, so quit */
      exit (RETURN_ERROR);

   Forbid();
   if (stch_i(*++argv,&task) < 3)              /* If user gave a string, */
      if ((task = FindTask(*argv)) == NULL) {  /* find the corresponding */
         Permit();                             /* task.                  */
         fprintf (stderr,"Can't find `%s'.\n",*argv);
         exit (RETURN_ERROR);
         }

   if (task == FindTask(NULL)) {              /* No suicide pills here */
      Permit();
      fprintf (stderr,"Can't kill self.\n");
      exit (RETURN_ERROR);
      }

#ifdef DEBUG
   Permit();
   printf ("Killing task %lx\n",task);
   Forbid();
#endif DEBUG

   /* Dig up the master list of processes - we'll need it later */

   rootnode = (struct RootNode *)DOSBase->dl_Root;
   TaskTable = (APTR *)BADDR(rootnode->rn_TaskArray);

   /* Find windows associated with the task */

   nWindow = 0;

#ifdef DEBUG
   IntuitionBase = NULL;
#else
   if (IntuitionBase = OpenLibrary("intuition.library",LIBRARY_VERSION)) {
         
      ilock = LockIBase(0);

      /* Loop through screens */

      screen = IntuitionBase->FirstScreen;
      while (screen && nWindow < MAX_WINDOWS) {

         /* Loop through windows */

         window = screen->FirstWindow;
         while (window && nWindow < MAX_WINDOWS) {

            /*  If this window has an IDCMP, then it points back to
             *  the task.  Check if this task is the one we want.
             */

            if (window->UserPort->mp_SigTask == task)
               WindowList[nWindow++] = window;

            window = window->NextWindow;

            }  /* windows */

         screen = screen->NextScreen;
         }  /* screens */

      UnlockIBase (ilock);

      }  /* IntuitionBase */

   /*  First we'll give the task a chance to die quietly.  Fake an Intuition
    *  CLOSEWINDOW message and send it to an IDCMP equipped to handle such
    *  an event.  Give the task a second to die and check if it's still
    *  around.  If so...
    */

   if (nWindow) {

      message = (struct IntuiMessage *)AllocMem(sizeof(struct IntuiMessage),
                                                MEMF_PUBLIC);

      i = nWindow;
      while (i)
         if (WindowList[--i]->IDCMPFlags & CLOSEWINDOW) {

            message->Class = CLOSEWINDOW;
            message->IDCMPWindow = WindowList[i];
            message->ExecMessage.mn_ReplyPort = WindowList[i]->WindowPort;
            message->ExecMessage.mn_Node.ln_Type = NT_MESSAGE;
            message->ExecMessage.mn_Length = sizeof(struct IntuiMessage) -
                                             sizeof(struct Message);

            Permit();
            PutMsg (WindowList[i]->UserPort,message);

            Delay (LIMIT1_OF_PATIENCE * TICKS_PER_SECOND);
            Forbid();

            FreeMem (message,sizeof(struct IntuiMessage));

            if (FindTask(*argv) != task) {
               CloseLibrary (IntuitionBase);
               Permit();
               exit (RETURN_OK);
               }
            }

      }

#endif DEBUG

   /*  Plan B.  Try breaking the task with the CTRL-C/CTRL-D combination.
    *  Wait a little while and check whether the task is still there.
    *  Even if the plan doesn't kill the task, it'll break out of the
    *  current CLI command and thereby allow us to close the CLI window.
    */

   Permit();

#ifdef DEBUG
   printf ("Sending break signals...");
#endif DEBUG

   Signal (task,SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D);
   Delay (LIMIT2_OF_PATIENCE * TICKS_PER_SECOND);
   Forbid();

   if (FindTask(*argv) != task) {
      CloseLibrary (IntuitionBase);
      Permit();
#ifdef DEBUG
      puts ("");
#endif DEBUG
      exit (RETURN_OK);
      }

#ifdef DEBUG
   Permit();
   printf ("done.\n");
   Forbid();
#endif DEBUG

   /*  Well, looks as though we'll have to pry it loose by hand. */

   if (task->tc_Node.ln_Type == NT_PROCESS) {

      process = (struct Process *)task;
      SegArray = (BPTR *)BADDR(process->pr_SegList);

#ifdef DEBUG
      Permit();
      printf ("Killing process %lx\n",&process->pr_MsgPort);
      Forbid();
#endif DEBUG

      /* Delete the process from AmigaDOS's list */

      for (i = 1; i <= (unsigned long)TaskTable[0]; i++)
         if (TaskTable[i] == (APTR)&process->pr_MsgPort) {
            TaskTable[i] = NULL;
            break;
            }

      if (process->pr_TaskNum) {

         /*  It's not a child of Workbench, so we will have to clean up.
          *  First, unlock the current directory.  Second, check whether
          *  the CLI is in the midst of executing a command.  If so,
          *  unload the command's code.  If not, close the CLI's I/O.
          *  Finally, unload the CLI's code.
          */

         UnLock (process->pr_CurrentDir);

         cli = (struct CommandLineInterface *)BADDR(process->pr_CLI);

         if (cli->cli_Module)
            UnLoadSeg (cli->cli_Module);
         else {

            /*  Close CLI I/O files, if the CLI isn't executing a command.
             *  I tried to close cli_StandardInput, et. al., and to check
             *  whether any other processes used these file handles as their
             *  CLI I/O.  These additional measures had no effect.  Under
             *  the present code, if you kill a CLI from which a background
             *  CLI had spawned, AmigaDOS will wait until the background
             *  process is done before closing the CLI window.
             *
             *  Closing files when the CLI was in the midst of executing a
             *  command crashed the system.  The CTRL-C/CTRL-D combination
             *  above helps.
             */

            if (cli->cli_Interactive) {
               if (process->pr_CIS) Close (process->pr_CIS);
               if (process->pr_COS) Close (process->pr_COS);
               }

            }  /* else cli->cli_Module */

         /* Remove process code and table thereof */
    
         nSeg = SegArray[0];
         while (--nSeg > 2)
            UnLoadSeg (SegArray[nSeg]);
         FreeMem (SegArray,SegArray[0] * sizeof(BPTR));

         }  /* if (process->pr_TaskNum) */

      else {

         /*  We're killing a child of Workbench.  Let Workbench mop up the
          *  mess.  Fake a message to Workbench in reply to its startup
          *  message.
          */

         startup = (struct WBStartup *)AllocMem(sizeof(struct WBStartup),
                                                MEMF_PUBLIC);

         startup->sm_Message.mn_Node.ln_Type = NT_MESSAGE;
         startup->sm_Message.mn_ReplyPort = FindPort("Workbench");
         startup->sm_Message.mn_Length = sizeof(struct WBStartup) -
                                         sizeof(struct Message);
         startup->sm_Process = &process->pr_MsgPort;
         startup->sm_Segment = SegArray[3];
         startup->sm_NumArgs = 0;
         startup->sm_ToolWindow = NULL;
         startup->sm_ArgList = NULL;
         Permit();

#ifdef DEBUG
         printf ("Sending message %lx...",startup);
#endif DEBUG

         ReplyMsg (startup);

#ifdef DEBUG
         printf ("done.\n");
#endif DEBUG

         Forbid();

         }  /* else (process->pr_TaskNum) */

      /* Close libraries opened by process */

#ifdef DEBUG
      Permit();
      printf ("Closing DOS...");
      Forbid();
#endif DEBUG

      dos = (struct DOSLibrary *)DOSBase;
      CloseLibrary (dos);

#ifdef DEBUG
      Permit();
      printf ("done.\n");
      Forbid();
#endif DEBUG

      /*  At last we can kill the task.  Besides pulling the task from
       *  Exec's sight, RemTask() seems to free the Task structure, the task
       *  stack, and memory that is specified in the task's tc_MemEntry list
       *  (verified empirically).  For processes, tc_MemEntry covers the
       *  Process structure and the process stack.
       */

#ifdef DEBUG
      Permit();
      printf ("Killing task...");
      Forbid();
#endif DEBUG

      RemTask (task);

#ifdef DEBUG
      Permit();
      printf ("done.\n");
      Forbid();
#endif DEBUG

      }  /* process */

   else {

      /*  I haven't experimented with non-processes yet.  For now, all
       *  we do is remove the task from Exec's list.
       */

      RemTask (task);
      FreeMem (task,sizeof(struct Task));

      }

   /* Close windows */

#ifdef DEBUG
   Permit();
   printf ("Closing windows...");
   Forbid();
#endif

   while (nWindow) {

      if (WindowList[--nWindow]->MenuStrip)
         ClearMenuStrip (WindowList[nWindow]);

      CloseWindow (WindowList[nWindow]);

      }

#ifdef DEBUG
   Permit();
   printf ("done.\n");
   Forbid();
#endif DEBUG

   Permit();

   if (IntuitionBase)
      CloseLibrary (IntuitionBase);

   exit (RETURN_OK);

}

