diff options
Diffstat (limited to 'pkg/ecl/bkg.c')
-rw-r--r-- | pkg/ecl/bkg.c | 649 |
1 files changed, 649 insertions, 0 deletions
diff --git a/pkg/ecl/bkg.c b/pkg/ecl/bkg.c new file mode 100644 index 00000000..8e0ceddf --- /dev/null +++ b/pkg/ecl/bkg.c @@ -0,0 +1,649 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#define import_spp +#define import_libc +#define import_stdio +#define import_knames +#define import_xwhen +#define import_ctype +#include <iraf.h> + +#include "config.h" +#include "clmodes.h" +#include "operand.h" +#include "clmodes.h" +#include "mem.h" +#include "errs.h" +#include "param.h" +#include "task.h" +#include "proto.h" + + +/* + * BKG -- All the functions relating to background ("&" asychronous) jobs. + * + * Here's how it works: yyparse() compiles code into the stack in the usual + * way, incrementing pc as it goes. If an '&' is seen, a snapshot of the + * dictionary, the stack, and all related pointers is written to a file + * immediately. The new code is discarded (by putting the pc back where it was) + * and yyparse() is called again. See the forever() loop in main. + * When started as a background cl, the snapshot file is read in and main + * jumps immediately to run() as though yyparse() had just finished compiling. + * Thus, background code is compiled in the parent but sent to the child cl + * to be executed. The t_flags T_BATCH bit is set in the new cl's currentask + * as well as firstask. The former is used by bkg_abort() to abort + * grandchildren. + * + * bkg_init () setup bkg job + * bkg_spawn (cmd) spawn bkg job + * bkg_wait (job) wait for termination + * bkg_kill (job) kill bkg job + * bkg_jobstatus (fp, job) print job status + * bool = bkg_jobactive (job) job is active + * bkg_update (pmsg) update bkg job status + * + * bkg_startup () called in bkg job to startup + * bkg_abort () called by bkg job on interrupt + * + * Job numbers start at 1 and count up to the maximum number of bkg jobs + * permitted. In all of the above commands, the function will be performed + * for a single job if job>0. If job=0 the function is applied to all jobs. + */ + +extern int cldebug; + +/* We need to pass the pipe file names along to the bkg cl because the name + * of the pipe file to use is determined AT PARSE TIME, not when the file + * gets opened. Without this, rmpipe() doesn't have the right names and + * dreg pipe files will be left around. + */ +extern int pipetable[]; /* pipe stack (pipecodes) */ +extern int nextpipe; /* pipe stack pointer (next index) */ +extern int dobkg; /* flag bkg execution */ + +extern memel cl_dictbuf[]; /* static dictionary area */ +extern long c_clktime(); +extern char *findexe(); + +#define BKGHDRSIZ (sizeof (struct bkgfilehdr)) +#define SZ_CMD 40 /* command in jobs table */ +#define SZ_BKCMD 80 /* command in bkg file */ +#define SZ_ENVDEF 1024 /* max size environment define */ +#define WAIT_PERIOD 5 /* bkg_wait wait interval */ +#define BKG_MAGIC 237 +#define SZ_BKGMSG 64 +#define CLDIR "iraf$pkg/ecl/" + +char bkgmsg[SZ_BKGMSG+1]; /* passed to kernel */ +int lastjobno; /* last job slot used */ +int bkgno; /* job no. assigned by parent */ +int ppid; /* pid of parent CL */ + +/* Template for all the junk that goes into the background status file. + * Following this is the dictionary, then the stack. + * TODO: avoid copying binary images of the stack and dictionary + * areas to permit use of dynamic memory allocation. + */ +struct bkgfilehdr { + int b_magic; /* file identification */ + int b_bkgno; /* bkg job number of new CL */ + int b_ppid; /* pid of parent CL */ + char b_cmd[SZ_BKCMD]; /* command entered by user */ + int b_pipetable[MAXPIPES]; /* pipefile database */ + int b_nextpipe; /* more pipefile database */ + int b_szstack; /* size of stack area, bytes */ + int b_szdict; /* size of dictionary, bytes */ + memel *b_dict; /* ptr to start of dict */ + XINT b_topd, /* dict ptr */ + b_maxd, /* top of dict */ + b_pachead, /* head of package list */ + b_parhead, /* head of param list */ + b_pc, /* pointer to compiled metacode */ + b_topos, /* top of operand stack */ + b_basos, /* base of operand stack */ + b_topcs; /* top of control stack */ + struct task *b_firstask, /* first task struct */ + *b_currentask; /* current task struct */ + struct package *b_curpack; /* current package */ +}; + + +/* Job table. Associate the ordinal job number with the job number returned + * by the system. Record the command string which caused the bkg job to be + * submitted, for output with bkg_jobstatus(). + */ +struct _bkgjob { + int b_jobno; /* job no. assigned by system */ + short b_flags; /* job state flags */ + short b_exitcode; /* exit status of job */ + long b_clock; /* start time; elapsed time */ + char b_cmd[SZ_CMD+1]; /* command entered by user */ + int b_verbose; /* print output from job */ +} jobtable[NBKG]; + +#define J_RUNNING 01 /* job is running or queued */ +#define J_SERVICE 02 /* job needs service */ +#define J_KILLED 04 /* job was killed */ +#define busy(job) (jobtable[(job)-1].b_flags & J_RUNNING) + +static void bkg_close(); + + +/* BKG_INIT -- Setup to execute a background job. Called by the lexical + * analyzer when the & is seen. Read in the bkg control string (anything + * following the & to end of line) and set the dobkg flag to flag background + * execution of the command block currently being parsed. + */ +void +bkg_init ( + char *bcs /* background control string */ +) +{ + strncpy (bkgmsg, bcs, SZ_BKGMSG); + dobkg++; +} + + +/* BKG_SPAWN -- Spawn a new background job. Called by main() when we have + * seen an '&'. + */ +void +bkg_spawn ( + char *cmd /* command entered by user to spawn job */ +) +{ + register struct _bkgjob *bk; + register int jobno, stat; + char clprocess[SZ_PATHNAME]; + char *wbkgfile(); + char *bkgfile; + + /* Find first unused slot in a circular search. + */ + bkg_update (1); + jobno = (lastjobno == NBKG) ? 1 : lastjobno + 1; + while (jobno != lastjobno) { + if (!busy (jobno)) + break; + if (jobno++ >= NBKG) + jobno = 1; + } + if (jobno == lastjobno) + cl_error (E_UERR, "no more background job slots"); + + /* Write bkgfile. Delete any dreg bkg communication files. + */ + bkg_delfiles (jobno); + bkgfile = wbkgfile (jobno, cmd, NULL); + + /* Spawn bkg job. + */ + sprintf (clprocess, "%s%s", CLDIR, CLPROCESS); + intr_disable(); + jobtable[jobno-1].b_jobno = stat = + c_propdpr (findexe (firstask->t_curpack, clprocess), + bkgfile, bkgmsg); + + if (stat == NULL) { + c_delete (bkgfile); + intr_enable(); + cl_error (E_IERR, "cannot spawn background CL"); + } else { + bk = &jobtable[jobno-1]; + bk->b_flags = J_RUNNING; + bk->b_clock = c_clktime (0L); + bk->b_verbose = 2; + strncpy (bk->b_cmd, cmd, SZ_CMD); + *(bk->b_cmd+SZ_CMD) = EOS; + intr_enable(); + } + + eprintf ("[%d]\n", lastjobno = jobno); + + /* Make a logfile entry, saying we started the background job. + */ + if (keeplog() && log_background()) { + char buf[SZ_LINE]; + sprintf (buf, "Start [%d]", jobno); + putlog (0, buf); + } +} + + +/* BKG_WAIT -- Wait for a background job to terminate. If job=0, wait for + * all bkg jobs to terminate. + */ +void +bkg_wait (register int job) +{ + register int j; + int active_jobs; + + if (job < 0 || job > NBKG) + return; + + do { + bkg_update (1); + if (job && !busy(job)) + return; + else { + for (active_jobs=0, j=1; j <= NBKG; j++) + if (busy (j)) { + active_jobs++; + c_tsleep (WAIT_PERIOD); + break; + } + } + } while (active_jobs); +} + + +/* BKG_KILL -- Kill a background job. If job=0, kill all background jobs. + * If the job cannot be killed assume it is because it died unexpectedly. + */ +void +bkg_kill (int job) +{ + register struct _bkgjob *bk; + register int j; + + bkg_update (1); + if (job < 0 || job > NBKG) + eprintf ("[%d] invalid job number\n", job); + else { + for (bk=jobtable, j=1; j <= NBKG; j++, bk++) { + if ((job == 0 && busy(j)) || job == j) { + if (!busy(j)) + eprintf ("[%d] not in use\n", j); + else if (c_prkill (bk->b_jobno) == ERR) + bkg_close (j, 2); + else { + bk->b_flags |= J_KILLED; + bkg_close (j, 2); + } + } + } + } +} + + +/* BKG_JOBSTATUS -- Print the status of one or more background jobs. + * format jobno, elapsed clock time, status, user command, e.g.: + * + * [1] 1:34 Running command_1 + * [2] 14:09 Stopped command_2 + * [3] 1:34 +Done command_3 + * [4] 1:34 Exit 23 command_4 + * + * A job will remain in the job table until another job is submitted which uses + * the same slot. + */ +void +bkg_jobstatus ( + FILE *fp, /* output file */ + int job /* job(s) */ +) +{ + register struct _bkgjob *bk; + register int j, n, ch; + register char *ip; + long seconds; + char *outstr = NULL; + + bkg_update (1); + for (bk=jobtable, j=1; j <= NBKG; j++, bk++) + if ((job == 0 && bk->b_jobno) || job == j) { + /* Print jobno. */ + fprintf (fp, " [%d] ", j); + + /* If the clock is still running b_clock contains the start + * time. If the job terminated it contains the elapsed time + * at job termination. + */ + if (busy(j)) + seconds = c_clktime (bk->b_clock); + else + seconds = bk->b_clock; + fprintf (fp, "%6.0m ", (float)seconds / 60.0); + fputc ((j == lastjobno) ? '+' : ' ', fp); + + /* Print job status. + */ + if (busy(j)) { + if (bk->b_flags & J_SERVICE) + outstr = "Stopped"; + else + outstr = "Running"; + } else if (bk->b_flags & J_KILLED) { + outstr = "Killed"; + } else if (bk->b_exitcode == OK) { + outstr = "Done"; + } else + sprintf (outstr, "Exit %d", bk->b_exitcode); + fprintf (fp, "%-10s", outstr); + + /* Finally, print user command followed by newline. + */ + n = c_envgeti ("ttyncols") - (8 + 8 + 10) - 1; + ip = bk->b_cmd; + while (--n >= 0 && (ch = *ip++) != EOS) + if (ch == '\n' || ch == '\t') + fputc (' ', fp); + else + fputc (ch, fp); + fputc ('\n', fp); + } +} + + +/* BKG_JOBACTIVE -- Determine if a background job is active, i.e., if the + * job is still running. It does not matter if the job is waiting for + * service. + */ +int +bkg_jobactive (int job) +{ + bkg_update (1); + return (busy (job)); +} + + +/* BKG_UPDATE -- Update the jobtable. Examine each running process to see if + * has terminated or if it needs service. Set the appropriate bits in the + * state flag in the job table. When job termination is detected compute the + * elapsed time and leave it in the table, along with the exit status. If + * the notify option is off the done or wait message will not have been printed + * by the bkg job, so we output the message ourselves. + */ +void +bkg_update ( + int pmsg /* print event messages */ +) +{ + register struct _bkgjob *bk; + register int j; + + for (bk=jobtable, j=1; j <= NBKG; j++, bk++) { + if (busy(j)) { + if (c_prdone (bk->b_jobno)) { + bkg_close (j, pmsg); + } else if (bkg_wfservice (j)) { + if (pmsg && !notify() && !(bk->b_flags & J_SERVICE)) + eprintf ("[%d] stopped waiting for parameter input\n", + j); + bk->b_flags |= J_SERVICE; + } else + bk->b_flags &= ~J_SERVICE; + } + } +} + + +/* BKG_CLOSE -- Close a bkg job. Called after determining that the job has + * terminated. + */ +static void +bkg_close ( + int job, /* job ordinal */ + int pmsg /* print termination message */ +) +{ + register struct _bkgjob *bk = &jobtable[job-1]; + + bk->b_clock = c_clktime (bk->b_clock); + bk->b_exitcode = c_prcldpr (bk->b_jobno); + bk->b_flags &= ~(J_RUNNING|J_SERVICE); + + if (bk->b_verbose && (pmsg > 1 || (pmsg == 1 && !notify()))) { + if (bk->b_exitcode != OK) + eprintf ("[%d] exit %d\n", job, bk->b_exitcode); + else + eprintf ("[%d] done\n", job); + } + + /* Make a logfile entry, saying the background job ended. + */ + if (keeplog() && log_background()) { + char buf[SZ_LINE]; + sprintf (buf, "Stop [%d]", job); + putlog (0, buf); + } +} + + +/* BKG_WFSERVICE -- Determine if a bkg job is waiting for service (for the + * user to answer a query). + */ +int +bkg_wfservice (int job) +{ + char bkg_query_file[SZ_PATHNAME]; + char query_response_file[SZ_PATHNAME]; + + get_bkgqfiles (job, c_getpid(), bkg_query_file, query_response_file); + return (c_access (bkg_query_file,0,0)); +} + + +/* BKG_DELFILES -- Called when a background job is spawned to make sure there + * are no dreg query service files lying about from a prior job which did not + * complete normally. + */ +void +bkg_delfiles (int job) +{ + char bkg_query_file[SZ_PATHNAME]; + char query_response_file[SZ_PATHNAME]; + + get_bkgqfiles (job, c_getpid(), bkg_query_file, query_response_file); + c_delete (bkg_query_file); + c_delete (query_response_file); +} + + +/* BKG_STARTUP -- Called by a background CL during process startup. Read in + * the bkgfile and restore runtime context of the parent. + */ +void +bkg_startup (char *bkgfile) +{ + rbkgfile (bkgfile); + setclmodes (firstask); + currentask->t_flags = firstask->t_flags = T_BATCH; +} + + +/* 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); +} + + +/* WBKGFILE -- Create a unique file, write and close the background file. + * Jobno is the job number the new cl is to think its running for. + * We don't use the global bkgno because that's OUR number, if we ourselves + * are background. + * Return pointer to the new name. + * No error return, but we may call error() and never return. + */ +char * +wbkgfile ( + int jobno, /* ordinal jobnumber of child */ + char *cmd, /* command to be run in bkg */ + char *fname /* filename for env file */ +) +{ + static char *bkgwerr = "error writing background job file"; + static char bkgfile[SZ_PATHNAME]; + struct bkgfilehdr bh; + int n, show_redefs=NO; + FILE *fp; + + + /* If we're a normal background job no name was specified so + * create a unique uparm file. Otherwise, use the specified + * filename (e.g. for special onerr handling). + */ + if (fname == (char *)NULL) + c_mktemp ("uparm$bkg", bkgfile, SZ_PATHNAME); + else { + if (c_access (fname,0,0) == YES) + c_delete (fname); + strncpy (bkgfile, fname, strlen(fname)); + } + + /* Open the file. */ + if ((fp = fopen (bkgfile, "wb")) == NULL) + cl_error (E_IERR, "unable to create background job file `%s'", + bkgfile); + + for (n=0; n < MAXPIPES; n++) + bh.b_pipetable[n] = pipetable[n]; + bh.b_nextpipe = nextpipe; + + strncpy (bh.b_cmd, cmd, SZ_BKCMD); + + bh.b_magic = BKG_MAGIC; + bh.b_bkgno = jobno; + bh.b_ppid = c_getpid(); + bh.b_szstack = STACKSIZ * BPI; + bh.b_szdict = topd * BPI; + bh.b_dict = dictionary; + bh.b_topd = topd; + bh.b_maxd = maxd; + bh.b_parhead = parhead; + bh.b_pachead = pachead; + bh.b_pc = pc; + bh.b_topos = topos; + bh.b_basos = basos; + bh.b_topcs = topcs; + bh.b_firstask = firstask; + bh.b_currentask = currentask; + bh.b_curpack = curpack; + + /* Write the header structure, followed by the stack area and the + * dictionary. + */ + if (fwrite ((char *)&bh, BKGHDRSIZ, 1, fp) == NULL) + cl_error (E_IERR|E_P, bkgwerr); + if (fwrite ((char *)stack, STACKSIZ, BPI, fp) == NULL) + cl_error (E_IERR|E_P, bkgwerr); + if (fwrite ((char *)dictionary, topd, BPI, fp) == NULL) + cl_error (E_IERR|E_P, bkgwerr); + + /* Write the environment as a sequence of SET statements in binary. + * Append a blank line as a terminator. + */ + c_envlist (fileno(fp), "set ", show_redefs); + fputs ("\n", fp); + + fclose (fp); + return (bkgfile); +} + + +/* RBKGFILE -- Read in and use background status file with given name. + * Do not remove the file -- the system does that upon process termination + * to signal the parent. If an error occurs do not call cl_error since + * we are called during process startup and error recovery is not yet + * possible (a memory fault will result). + */ +void +rbkgfile (char *bkgfile) +{ + char set[SZ_ENVDEF]; + struct bkgfilehdr bh; + int n; + FILE *fp; + + + if ((fp = fopen (bkgfile, "rb")) == NULL) { + fprintf (stderr, + "[B] ERROR: unable to open background job file `%s'\n", + bkgfile); + clexit(); + } + + if (fread ((char *)&bh, BKGHDRSIZ, 1, fp) == NULL) + goto abort_; + if (bh.b_magic != BKG_MAGIC) { + fprintf (stderr, "[B] ERROR: bad magic in bkgfile '%s'\n", bkgfile); + clexit(); + } + + /* The following assumes that the dictionary is statically allocated + * and cannot move around. + */ + if (bh.b_dict != cl_dictbuf) { + fprintf (stderr, + "BKG ERROR: new CL installed; logout and try again\n"); + clexit(); + } + + intr_disable(); + + for (n=0; n < MAXPIPES; n++) + pipetable[n] = bh.b_pipetable[n]; + nextpipe = bh.b_nextpipe; + + bkgno = bh.b_bkgno; + ppid = bh.b_ppid; + dictionary = bh.b_dict; + topd = bh.b_topd; + maxd = bh.b_maxd; + pachead = bh.b_pachead; + parhead = bh.b_parhead; + pc = bh.b_pc; + topos = bh.b_topos; + basos = bh.b_basos; + topcs = bh.b_topcs; + firstask = bh.b_firstask; + currentask = bh.b_currentask; + curpack = bh.b_curpack; + + /* Read stack area and dictionary. + */ + if (fread ((char *)stack, bh.b_szstack, 1, fp) == NULL) + goto abort_; + if (fread ((char *)dictionary, bh.b_szdict, 1, fp) == NULL) + goto abort_; + + /* Read and restore the environment. + */ + do { + if (fgets (set, SZ_ENVDEF, fp) == NULL) + goto abort_; + } while (c_envscan (set)); + + intr_enable(); + fclose (fp); + return; +abort_: + intr_enable(); + eprintf ("[B] ERROR: error reading background file\n"); + clexit(); +} |