/*
 *  KILL version 1.01
 *  by George Musser Jr.
 *
 *  11 Jan 87 - initial version
 *  24 Jan 87 - send ACTION_DIE packet to ConsoleTask for Wbench processes
 *
 *  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           (log on at least biweekly)
 *    Plink OHS152             (log on at least monthly)
 *    ST801115@BROWNVM.BITNET  (log on at least weekly)
 */

/***** 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 LIMIT3_OF_PATIENCE  1  /* Time to wait for console to die  */
#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;
{
   void exit();
   struct IntuitionBase *OpenLibrary(char *,long);
   int Kill(char *);
   void CloseLibrary();

   int rc;

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

   IntuitionBase = OpenLibrary("intuition.library",LIBRARY_VERSION);

   rc = Kill(*++argv);

   if (IntuitionBase)
      CloseLibrary (IntuitionBase);

   exit (rc);
}

/***** Kill() *************************************************************
 *
 *  Kill() is a function so that we can experiment with recursion, which
 *  may be needed to kill the console task.
 *
 */

int Kill (name)
char *name;
{

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

   int stch_i();
   void Forbid();
   struct Task *FindTask(char *);
   void Permit();
   void fprintf();
   ULONG LockIBase(long);
   void UnlockIBase(long);
   APTR AllocMem(long,long);
   void PutMsg();
   void Delay(long);
   void CloseLibrary();
   void FreeMem();
   void Signal(struct Task *,long);
   void UnLock(BPTR);
   void UnLoadSeg(BPTR);
   void Close(BPTR);
   struct Message *GetMsg(struct MsgPort *);
   struct MsgPort *FindPort(char *);
   void ReplyMsg();
   void RemTask(struct Task *);
   void ClearMenuStrip(struct Window *);
   void CloseWindow(struct Window *);

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

   ULONG ilock;           /* Lock on IntuitionBase, so rug stays under us */

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

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

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

   unsigned long i;               /* Loop counter */
   struct IntuiMessage *message;  /* Our forged IntuiMessage */

   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 MsgPort *myport;          /* Reply port for packet */
   struct StandardPacket *packet;   /* Message to shut down console */
   struct WBStartup *startup;       /* Forged message to Workbench */

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

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

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

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

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

   /* Find windows associated with the task */

   nWindow = 0;

   if (IntuitionBase) {
         
      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(name) != task) {
               Permit();
               return (RETURN_OK);
               }
            }

      }

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

      /*  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(name) != task) {
         Permit();
#ifdef DEBUG
         puts ("");
#endif DEBUG
         return (RETURN_OK);
         }

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

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

      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.  First, dig up the master
       *  list of processes.  Second, scan through the list for our task and
       *  reset its pointer to NULL.
       */

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

      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 {

         /*  Send a death message to the ConsoleTask associated with this
          *  process.  I'm not sure if this actually does anything, but
          *  but it's part of an attempt to close the standard I/O window
          *  opened by the startup code.
          */

         if (process->pr_ConsoleTask) {
            myport = &((struct Process *)FindPort(NULL))->pr_MsgPort;

            packet = (struct StandardPacket *)
                     AllocMem(sizeof(struct StandardPacket),MEMF_PUBLIC);

            packet->sp_Msg.mn_Node.ln_Type = NT_MESSAGE;
            packet->sp_Msg.mn_Node.ln_Name = (char *)&(packet->sp_Pkt);
            packet->sp_Msg.mn_ReplyPort = myport;
            packet->sp_Msg.mn_Length = sizeof(struct DosPacket);
            packet->sp_Pkt.dp_Link = &(packet->sp_Msg);
            packet->sp_Pkt.dp_Port = packet->sp_Msg.mn_ReplyPort;
            packet->sp_Pkt.dp_Type = ACTION_DIE;

            Permit();

#ifdef DEBUG
            printf ("Sending message %lx to %lx...",
                    packet,process->pr_ConsoleTask);
#endif DEBUG

            PutMsg (process->pr_ConsoleTask,packet);
            Delay (LIMIT3_OF_PATIENCE * TICKS_PER_SECOND);
            if (GetMsg(myport) == &packet->sp_Msg)
               FreeMem (packet,sizeof(struct StandardPacket));

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

            Forbid();

            }

         /*  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();

   return (RETURN_OK);

}

