/* * 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 * KILL * * 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 #include #include #include #include #include #include #include #include #include #include #include /***** 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); }