/* KILLTASK -- Abort the currently executing task. Only call this when a task * is to be killed spontaneously, as from interrupt, not when it is just dying * due to a "bye" or eof. * Close all pipes and pseudofiles, being careful not to close any that * are real stdio files. * Note that our function is to kill an external task, not the process in which * it resides. The process is left running in the cache in case it is needed * again. */ void killtask ( register struct task *tp ) { char buf[128]; /* Print stack trace, with arguments. */ if (!(tp->t_ltp->lt_flags<_INVIS) && !(firstask->t_flags&T_BATCH) && !(strcmp (tp->t_ltp->lt_lname, "error") == 0)) printcall (currentask->t_stderr, tp); /* If task is running in a subprocess, interrupt it and read the ERROR * message. Not certain there isn't some case where this could cause * deadlock, but it does not seem so. Interrupts are disabled during * process startup. If task issues ERROR then it is popped before * we are called, without issuing the signal. */ if (tp->t_pid != -1) { fflush (tp->t_out); c_prsignal (tp->t_pid, X_INT); fgets (buf, 128, tp->t_in); } iofinish (tp); }
/* SHUTDOWN -- Call this to exit gracefully from the whole cl; never return. * Write out any remaining PF_UPDATE'd pfiles by restoring topd to just above * first task unless we are in batch mode, then just flush io and die.. * So that the restor will include the cl's pfile and any other pfiles that * might have been cached or assigned into, we force its topd to be * below its pfile head. See the "pfp < topdp" loop in restor(). * Don't bother with restor'ing if BATCH since we don't want to write out * anything then anyway. */ static void shutdown (void) { float cpu, clk; pr_dumpcache (0, YES); /* flush process cache */ clgflush(); /* flush graphics output */ if (firstask->t_flags & T_BATCH) { iofinish (currentask); if (notify()) { cpu = (float)c_cputime(cpustart) / 1000.; clk = (float)c_clktime(clkstart); fprintf (stderr, "\n[%d] done %.1f %.0m %d%%\n", bkgno, cpu, clk/60., (int)((clk > 0 ? cpu / clk : 0.) * 100.)); } } else { firstask->t_topd = dereference (firstask->t_ltp) + LTASKSIZ; restor (firstask); } yy_startblock (LOG); /* flush and close log */ close_logfile (logfile()); clexit(); }
/* ARGSUSED */ void onint ( int *vex, /* virtual exception code */ int (**next_handler)(void) /* next handler to be called */ ) { if (firstask->t_flags & T_BATCH) { /* Batch task. */ iofinish (currentask); bkg_abort(); clexit(); } else if (currentask->t_flags & (T_SCRIPT|T_CL|T_BUILTIN)) { /* CL task. */ cl_error (E_UERR, "interrupt!!!"); } else { /* External task connected via IPC. Pass the interrupt on to * the child. */ c_prsignal (currentask->t_pid, X_INT); /* Cancel any output and disable i/o on the tasks pseudofiles. * This is necessary to cancel any i/o still buffered in the * IPC channel. Commonly when the task is writing to STDOUT, * for example, the CL will be writing the last buffer sent * to the terminal, while the task waits after having already * pushed the next buffer into the IPC. When we resume reading * from the task we will see this buffered output on the next * read and we wish to discard it. Leave STDERR connected to * give a path to the terminal for recovery actions such as * turning standout or graphics mode off. This gives the task * a chance to cleanup but does not permit full recovery. The * pseudofiles will be reconnected for the next task run. */ c_fseti (fileno(stdout), F_CANCEL, OK); c_fseti (fileno(currentask->t_in), F_CANCEL, OK); c_fseti (fileno(currentask->t_out), F_CANCEL, OK); c_prredir (currentask->t_pid, STDIN, 0); c_prredir (currentask->t_pid, STDOUT, 0); /* If a subprocess is repeatedly interrupted we assume that it * is hung in a loop and abort, advising the user to kill the * process. */ if (++ninterrupts >= MAX_INTERRUPTS) cl_error (E_UERR, "subprocess is hung; should be killed"); else longjmp (intenv, 1); } *next_handler = NULL; }
/* ONEOF -- "on eof" (not "one of"): * The current task has issued eof, either directly or via the "bye" command. * Flush out all pending io, copy working pfile back to original if have one, * pop a state back to the previous state and restore its environment. * Avoid calling effecmode() if called from a builtin task since builtins * do not have the "mode" parameter. * * If currentask is the first cl or we are batch, then we are truely done. * Return true to the caller (EXECUTE), causing a return to the main. */ void oneof (void) { register struct pfile *pfp; register struct package *pkp; static int nerrs = 0; int flags; if (cldebug) eprintf ("received `%s' from `%s'\n", yeof ? "eof" : "bye", currentask == firstask ? "root" : currentask->t_ltp->lt_lname); if (!(firstask->t_flags & T_BATCH)) if (currentask == firstask && !gologout && !loggingout && isatty (fileno (stdin)) && nerrs++ < 8) cl_error (E_UERR, "use `logout' to log out of the CL"); flags = currentask->t_flags; if (!(flags & (T_BUILTIN|T_CL|T_SCRIPT|T_BATCH))) fflush (currentask->t_out); iofinish (currentask); /* Copy back the main pfile and any pset-param files. If the task * which has terminated is a package script task, fix up the pfile * pointer in the package descriptor to point to the updated pset. */ if (currentask->t_ltp->lt_flags & LT_PFILE) { pfcopyback (pfp = currentask->t_pfp); if (currentask->t_ltp->lt_flags & LT_DEFPCK) if ((pkp = pacfind(currentask->t_ltp->lt_lname))) if (pkp->pk_pfp == pfp) pkp->pk_pfp = pfp->pf_oldpfp; for (pfp = pfp->pf_npset; pfp != NULL; pfp = pfp->pf_npset) pfcopyback (pfp); } if (currentask == firstask) alldone = 1; else { currentask = poptask(); if (currentask->t_flags & T_BATCH) alldone = 1; } restor (currentask); /* restore environment */ }
/* BKG_ABORT -- Called by onint() in main.c when we get interrupted while * running as a bkg job. Kill any and all background CL's WE may have * started, flush io, close any open pipe files, remove our job seq lock * file, kill all tasks back to the one that started us as background and * write a message on stderr. */ void bkg_abort (void) { register int job; register struct task *tp; for (job=1; job <= NBKG; job++) if (busy (job)) bkg_kill (job); iofinish (currentask); delpipes (0); tp = currentask; while (!(tp->t_flags & T_BATCH)) { killtask (tp); tp = poptask(); } fprintf (stderr, "\n[%d] killed\n", bkgno); }
void cl_error (int errtype, char *diagstr, ...) { va_list args; register struct task *tp; static int nfatal = 0; static int break_locks = 1; va_start (args, diagstr); /* (Re)-initialize the error action. */ erract_init(); /* Safety measure, in the event of error recursion. */ if (err_abort) { if (nfatal) clexit(); if (errlev++ > 2) { nfatal++; eprintf ("Error recursion. Cl dies.\n"); clexit(); } } /* The first setjmp(errenv) is not done until we start the main loop. * Set validerrenv when start the first interactive cl to indicate that * we may safely longjmp back to main's loop on an error. ERRENV is * not set for bkg jobs since error restart is not permitted. */ if (!validerrenv && !(firstask->t_flags & T_BATCH)) { nfatal++; u_doprnt (diagstr, &args, currentask->t_stderr); if (errtype & E_P) perror ("\nOS errmsg"); else eprintf ("\n"); eprintf ("Fatal startup error. CL dies.\n"); clexit(); } /* Any error occurring during logout is fatal. */ if (loggingout || gologout) { nfatal++; u_doprnt (diagstr, &args, currentask->t_stderr); if (errtype & E_P) perror ("\nOS errmsg"); else eprintf ("\n"); eprintf ("Fatal logout error. CL dies.\n"); clexit(); } /* Perform any ONERROR error recovery in the vos first. Initialize * the error recovery mechanism (necessary since the iraf main is not * being allowed to do error recovery). */ c_xonerr (1); XER_RESET(); /* TODO: move into LIBC interface */ /* Clear terminal raw mode if still set. */ c_fseti ((XINT)STDIN, F_RAW, NO); if (firstask->t_flags & T_BATCH) eprintf ("\n[%d] ", bkgno); if (errtype & E_IERR) eprintf ("INTERNAL "); if (errtype & E_FERR) eprintf ("FATAL "); /* Disable error tracing if requested. */ if (err_trace == YES || (errtype & E_UERR)) { if (currentask->t_flags & T_SCRIPT && currentask->t_flags & T_INTERACTIVE) eprintf ("ERROR on line %d: ", errorline); else eprintf ("ERROR: "); u_doprnt (diagstr, &args, currentask->t_stderr); if (errtype & E_P) perror ("\nOS errmsg"); else eprintf ("\n"); } /* Log the error message if from a script or an executable. */ if (!errlog && keeplog() && log_errors()) { if (currentask->t_flags & T_SCRIPT || currentask->t_pid != -1) { PKCHAR buf[SZ_LINE+1]; FILE *fp; int fd; fd = c_stropen (buf, SZ_LINE, NEW_FILE); fp = fdopen (fd, "w"); fprintf (fp, "ERROR on line %d: ", errorline); u_doprnt (diagstr, &args, fp); fclose (fp); c_close (fd); putlog (currentask, c_strpak (buf, (char *)buf, SZ_LINE)); } } errlog = 0; /* Initialize the current command block but do not log the command * which aborted. If we're only trapping errors and not fully * recovering, don't reset the command block so we have the option * to continue execution. */ if ((err_abort == YES && do_error == NO) || (do_error == YES || (errtype & E_UERR))) yy_startblock (NOLOG); /* Delete all pipefiles. Call iofinish() first as some OS's may * require that the files be closed before they can be deleted. */ for (tp=currentask; !(tp->t_flags & T_INTERACTIVE); tp=next_task(tp)) { iofinish (tp); if (tp == firstask) break; } delpipes (0); /* Do not go on if this is a fatal error or we are unattended. */ if (errtype & E_FERR) { nfatal++; pr_dumpcache (0, break_locks); clexit(); } else if (firstask->t_flags & T_BATCH) clshutdown(); /* Reset state variables. */ /* Most of these probably needn't be reset, but we'll play * it safe. */ nestlevel = 0; /* set nesting to 0 */ offsetmode (0); /* offset mode to index */ ncaseval = 0; /* number of case values */ n_indexes = 0; imloopset = 0; /* in an implicit loop */ n_oarr = 0; /* implicit loop indicators */ i_oarr = 0; maybeindex = 0; /* sexagesimal/index range */ parse_state = PARSE_FREE; if (last_parm) { /* have we tried to add a param */ last_parm->p_np = NULL; currentask->t_pfp->pf_lastpp = last_parm; last_parm = NULL; } /* Set the error flag. */ errcom.errflag++; errcom.nhandlers++; /* Get back to an interactive state. We simply return if we're * trapping errors except when processing a E_UERR. These type * messages come from the CL itself and require user attention to * correct (e.g. task not found, parameter type/syntax errors, etc). * The calling procedure is not expecting us to return, so we cannot * properly trap without rewriting the calling code. */ if (cltrace) { eprintf ("cl_error: abort=%d beep=%d trace=%d flpr=%d\n", err_abort, err_beep, err_trace, err_flpr); eprintf ("cl_error: code=%d do_err=%d errtype=%d/%d task='%s'\n", errcom.errcode, do_error, errtype, errtype&E_UERR, currentask->t_ltp->lt_lname); } if ((err_abort == YES && do_error == NO) || (do_error == YES || (errtype & E_UERR))) { extern ErrCom errcom; register struct param *pp; if (!errcom.errcode && (errtype & E_UERR)) { errcom.errcode = errtype; strcpy (errcom.errmsg, diagstr); strcpy (errcom.task, currentask->t_ltp->lt_lname); pp = paramfind (firstask->t_pfp, "$errno", 0, YES); pp->p_val.v_i = errcom.errcode; pp = paramfind (firstask->t_pfp, "$errmsg", 0, YES); pp->p_val.v_s = errcom.errmsg; pp = paramfind (firstask->t_pfp, "$errtask", 0, YES); pp->p_val.v_s = errcom.task; } taskunwind(); /* If an abort occurs while interrupts are disabled they will * never get reenabled unless we do so here. */ intr_reset(); /* Go back to main loop in main(). */ va_end (args); longjmp (errenv, 1); } else { va_end (args); return; } }